ESPILON-CTF-2026-Writeups/IoT/Anesthesia_Gateway/README.md

3.7 KiB

Anesthesia Gateway

Field Value
Category IoT
Difficulty Medium-Hard
Points 500
Author Eun0us
CTF Espilon 2026

Description

The anesthesia monitoring gateway in Operating Room 13 at Clinique Sainte-Mika is broadcasting live data over MQTT.

Something unusual is being transmitted on the network. The contractor left a debug channel open.

Connect to the MQTT broker and investigate.

  • MQTT Broker: tcp/<host>:1883

Format: ESPILON{flag}


TL;DR

Subscribe to the MQTT wildcard topic sainte-mika/#. Find the debug firmware topic publishing a base64-encoded blob every 45 seconds. Reverse the encoding chain (base64 → XOR with key WIRED → zlib decompress → JSON) to extract the maintenance key N4V1-C4R3-0R13-L41N. Publish it to the unlock topic to receive the flag.


Tools

Tool Purpose
mosquitto_sub / mosquitto_pub MQTT client
Python 3 Decode base64, XOR, zlib, JSON

Solution

MQTT subscribe capturing all topics including admin/config

Step 1 — Connect and discover topics

mosquitto_sub -h <HOST> -t "sainte-mika/#" -v

📸 [screenshot: mosquitto_sub output listing all discovered topics and their messages]

Topics discovered:

Topic Content
sainte-mika/or13/vitals Patient vital signs (JSON)
sainte-mika/or13/sevoflurane Anesthetic gas data
sainte-mika/or13/propofol Infusion pump data
sainte-mika/or13/ventilator Ventilator data
sainte-mika/or13/alarms Alarm status — note "network": "WIRED-MED"
sainte-mika/or13/debug/firmware Base64 blob, published every 45s

Step 2 — Capture the firmware blob

Wait for a message on debug/firmware (up to 45 seconds). Save the base64 string.

Note the "network": "WIRED-MED" in the alarms topic — this is the XOR key hint.

📸 [screenshot: debug/firmware topic publishing the base64-encoded blob]

Step 3 — Reverse the encoding chain

The chain is: base64 → XOR("WIRED") → zlib → JSON

import base64, zlib, json

blob = "<base64 string from debug/firmware>"

# Step 1: base64 decode
raw = base64.b64decode(blob)

# Step 2: XOR with key "WIRED"
key = b"WIRED"
xored = bytes(b ^ key[i % len(key)] for i, b in enumerate(raw))

# Verify: first two bytes after XOR should be 0x78 0x9C (zlib magic)
assert xored[:2] == b'\x78\x9C', "Key is wrong — magic bytes don't match"

# Step 3: zlib decompress
decompressed = zlib.decompress(xored)

# Step 4: parse JSON
config = json.loads(decompressed.decode())
print(config)

📸 [screenshot: Python script printing the decoded JSON configuration]

Step 4 — Extract the maintenance key

The decoded JSON contains:

{
    "maintenance_key": "N4V1-C4R3-0R13-L41N"
}

The key is a leetspeak encoding of "Navi Care OR13 Lain".

Step 5 — Publish the key and get the flag

mosquitto_pub -h <HOST> -t "sainte-mika/or13/maintenance/unlock" -m "N4V1-C4R3-0R13-L41N"

Subscribe to the flag topic:

mosquitto_sub -h <HOST> -t "sainte-mika/or13/maintenance/flag"

📸 [screenshot: flag topic publishing the ESPILON flag after unlock]

Key insights

  • The XOR key WIRED is derivable from the alarms topic which includes "network": "WIRED-MED"
  • After XOR decryption, the zlib magic bytes 0x78 0x9C confirm the correct key
  • MQTT wildcard subscription # enumerates all topics in one command
  • An open MQTT broker with no authentication is a real-world IoT misconfiguration

Flag

ESPILON{mQtt_g4tw4y_4n3sth3s14}