620 lines
28 KiB
Markdown
620 lines
28 KiB
Markdown
|
|
# Archipelago Attack Surface Analysis
|
||
|
|
|
||
|
|
**Target:** 192.168.1.228
|
||
|
|
**Date:** 2026-03-18
|
||
|
|
**Scope:** Authorized security assessment — full infrastructure
|
||
|
|
**Assessor:** Automated recon + source code review
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Target Overview
|
||
|
|
|
||
|
|
### Technologies Detected
|
||
|
|
|
||
|
|
| Layer | Technology | Version |
|
||
|
|
|-------|-----------|---------|
|
||
|
|
| OS | Debian 12 (Bookworm) | x86_64, kernel unknown |
|
||
|
|
| Web Server | nginx | 1.22.1 |
|
||
|
|
| Reverse Proxy (alt) | OpenResty | (port 81, Nginx Proxy Manager) |
|
||
|
|
| Backend | Rust (custom binary) | 0.1.0 (`archipelago`) |
|
||
|
|
| Frontend | Vue 3 + TypeScript + Vite 7 | SPA at `/` |
|
||
|
|
| Container Runtime | Podman (rootless) | — |
|
||
|
|
| Lightning | LND | auto-generated TLS cert |
|
||
|
|
| Bitcoin | Bitcoin Core/Knots | mainnet, block 941146 |
|
||
|
|
| Monitoring | Grafana | 10.2.0 |
|
||
|
|
| Uptime | Uptime Kuma | (port 3001) |
|
||
|
|
| Proxy Manager | Nginx Proxy Manager | 2.14.0 |
|
||
|
|
| SSH | OpenSSH | 9.2p1 Debian 2+deb12u7 |
|
||
|
|
| TLS | Self-signed cert | CN=archipelago.local, expires 2027-02-17 |
|
||
|
|
|
||
|
|
### Open Ports and Services
|
||
|
|
|
||
|
|
| Port | Service | Protocol | Direct Access |
|
||
|
|
|------|---------|----------|---------------|
|
||
|
|
| 22 | SSH (OpenSSH 9.2p1) | TCP | Yes |
|
||
|
|
| 80 | Nginx (main reverse proxy) | HTTP | Yes |
|
||
|
|
| 81 | Nginx Proxy Manager (OpenResty) | HTTP | Yes |
|
||
|
|
| 443 | Nginx (HTTPS, self-signed) | HTTPS | Yes |
|
||
|
|
| 3000 | Grafana | HTTP | Yes |
|
||
|
|
| 3001 | Uptime Kuma | HTTP | Yes |
|
||
|
|
| 5678 | Archipelago Rust backend | HTTP | Yes (behind nginx) |
|
||
|
|
| 7777 | IndeedHub (nginx 1.29.6) | HTTP | Yes |
|
||
|
|
| 8080 | LND REST API | HTTPS (TLS) | Yes |
|
||
|
|
| 8334 | Bitcoin UI (custom nginx) | HTTP | Inferred from config |
|
||
|
|
| 8083 | FileBrowser | HTTP | Inferred from config |
|
||
|
|
| 8888 | SearXNG | HTTP | Inferred from config |
|
||
|
|
| 9000 | Portainer | HTTP | Yes |
|
||
|
|
| 11434 | Ollama (local LLM) | HTTP | Inferred from config |
|
||
|
|
| 3141/3142 | Claude OAuth Proxy | HTTP | Internal |
|
||
|
|
|
||
|
|
### Subdomains Discovered
|
||
|
|
|
||
|
|
- `archipelago.local` (self-signed cert SAN)
|
||
|
|
- No external subdomains (internal LAN deployment)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Complete Endpoint Map
|
||
|
|
|
||
|
|
### 2.1 Nginx HTTP Routes (Port 80/443)
|
||
|
|
|
||
|
|
#### Unauthenticated Endpoints
|
||
|
|
|
||
|
|
| Method | Path | Backend | Source | Auth Enforced |
|
||
|
|
|--------|------|---------|--------|---------------|
|
||
|
|
| GET | `/health` | 127.0.0.1:5678 | nginx config line ~45 | **None** |
|
||
|
|
| GET | `/electrs-status` | 127.0.0.1:5678 | nginx config, CORS `*` | **None** |
|
||
|
|
| GET | `/lnd-connect-info` | 127.0.0.1:5678 | nginx config, CORS `*` | **None** |
|
||
|
|
| GET | `/content` | 127.0.0.1:5678 | nginx config | **None** (P2P) |
|
||
|
|
| GET | `/content/*` | 127.0.0.1:5678 | nginx config | **None** (P2P) |
|
||
|
|
| POST | `/dwn` | 127.0.0.1:5678 | nginx config | **None** (P2P) |
|
||
|
|
| GET | `/dwn/health` | 127.0.0.1:5678 | nginx config | **None** |
|
||
|
|
| POST | `/archipelago/node-message` | 127.0.0.1:5678 | nginx config | **None** (P2P) |
|
||
|
|
| GET | `/` | Static SPA | nginx config | **None** |
|
||
|
|
| GET | `/assets/*` | Static files | nginx config | **None** |
|
||
|
|
| GET | `/nostr-provider.js` | Static file | nginx config | **None** |
|
||
|
|
|
||
|
|
#### Authenticated Endpoints (Session Cookie Required)
|
||
|
|
|
||
|
|
| Method | Path | Backend | Source | Notes |
|
||
|
|
|--------|------|---------|--------|-------|
|
||
|
|
| POST | `/rpc/v1` | 127.0.0.1:5678 | nginx config | Rate limited: 20r/s, burst 40. 1MB body. 600s timeout |
|
||
|
|
| WS | `/ws/db` | 127.0.0.1:5678 | nginx config | WebSocket upgrade. 86400s timeout |
|
||
|
|
| GET | `/api/container/logs*` | 127.0.0.1:5678 | handler.rs | Query: `?app_id=&lines=` |
|
||
|
|
| GET | `/proxy/lnd/*` | 127.0.0.1:8080 | handler.rs | Proxies to LND REST API |
|
||
|
|
| GET | `/aiui/api/claude/*` | 127.0.0.1:3141 | nginx config | Streaming. 300s timeout |
|
||
|
|
| GET | `/aiui/api/ollama/*` | 127.0.0.1:11434 | nginx config | Streaming. 300s timeout |
|
||
|
|
| GET | `/aiui/api/openrouter/*` | openrouter.ai | nginx config | External API proxy |
|
||
|
|
| GET | `/aiui/api/web-search` | 127.0.0.1:8888 | nginx config | SearXNG. 30s timeout |
|
||
|
|
|
||
|
|
#### App Proxy Routes (`/app/*`)
|
||
|
|
|
||
|
|
All inject `nostr-provider.js`, strip X-Frame-Options, re-apply SAMEORIGIN.
|
||
|
|
|
||
|
|
| Path | Backend Port | Timeout | Special |
|
||
|
|
|------|-------------|---------|---------|
|
||
|
|
| `/app/bitcoin-ui/` | 8334 | 5s | — |
|
||
|
|
| `/app/electrumx/` | 50002 | 5s | — |
|
||
|
|
| `/app/grafana/` | 3000 | 5s | — |
|
||
|
|
| `/app/uptime-kuma/` | 3001 | 5s | — |
|
||
|
|
| `/app/searxng/` | 8888 | 5s | — |
|
||
|
|
| `/app/portainer/` | 9000 | 5s | — |
|
||
|
|
| `/app/filebrowser/` | 8083 | 5s | 10GB upload limit; path traversal check |
|
||
|
|
| `/app/jellyfin/` | 8096 | 5s | — |
|
||
|
|
| `/app/photoprism/` | 2342 | 5s | — |
|
||
|
|
| `/app/onlyoffice/` | 9980 | 5s | — |
|
||
|
|
| `/app/tailscale/` | 8240 | 5s | — |
|
||
|
|
| `/app/ollama/` | 11434 | 5s | — |
|
||
|
|
| `/app/nginx-proxy-manager/` | 81 | 5s | — |
|
||
|
|
| `/app/lnd/` | 8081 | 300s | Long timeout |
|
||
|
|
| `/app/mempool/` | 4080 | 300s | Long timeout |
|
||
|
|
| `/app/fedimint/` | 8175 | 300s | Long timeout |
|
||
|
|
| `/app/fedimint-gateway/` | 8176 | 300s | Long timeout |
|
||
|
|
| `/app/nextcloud/` | 8085 | 300s | — |
|
||
|
|
| `/app/vaultwarden/` | 8082 | 300s | Password manager |
|
||
|
|
| `/app/immich/` | 2283 | 300s | — |
|
||
|
|
| `/app/penpot/` | 9001 | 300s | — |
|
||
|
|
| `/app/indeedhub/` | 7777 | 5s | Complex URL rewriting, WebSocket |
|
||
|
|
|
||
|
|
#### External Site Proxies (Separate Ports)
|
||
|
|
|
||
|
|
| Port | Upstream | Purpose |
|
||
|
|
|------|----------|---------|
|
||
|
|
| 8901 | botfights.net | Nostr proxy |
|
||
|
|
| 8902 | 484.kitchen | Nostr proxy |
|
||
|
|
| 8903 | present.l484.com | Nostr proxy |
|
||
|
|
|
||
|
|
### 2.2 Rust Backend RPC Methods (`POST /rpc/v1`)
|
||
|
|
|
||
|
|
**Protocol:** JSON-RPC 2.0
|
||
|
|
**Content-Type:** `application/json`
|
||
|
|
**Auth:** Session cookie (except where noted)
|
||
|
|
|
||
|
|
#### Unauthenticated RPC Methods (No Session Required)
|
||
|
|
|
||
|
|
| Method | Parameters | Returns | Source |
|
||
|
|
|--------|-----------|---------|--------|
|
||
|
|
| `auth.login` | `password` | Sets session cookie | `api/rpc/auth.rs` |
|
||
|
|
| `auth.login.totp` | `token`, `code` | Session | `api/rpc/auth.rs` |
|
||
|
|
| `auth.login.backup` | `token`, `backup_code` | Session | `api/rpc/auth.rs` |
|
||
|
|
| `auth.isOnboardingComplete` | — | `boolean` | `api/rpc/auth.rs` |
|
||
|
|
| `auth.isSetup` | — | `boolean` | `api/rpc/auth.rs` |
|
||
|
|
| `backup.restore-identity` | `backup_file`, `password` | `{did}` | `api/rpc/mod.rs` |
|
||
|
|
| `federation.get-state` | — | `{state}` | P2P inter-node |
|
||
|
|
| `federation.peer-joined` | `peer_did`, `address` | — | P2P inter-node |
|
||
|
|
| `federation.peer-address-changed` | `peer_did`, `new_address` | — | P2P inter-node |
|
||
|
|
|
||
|
|
#### Authenticated RPC Methods (150+ total, grouped by domain)
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Authentication & Session (12 methods)</b></summary>
|
||
|
|
|
||
|
|
| Method | Parameters | Purpose |
|
||
|
|
|--------|-----------|---------|
|
||
|
|
| `auth.logout` | — | Invalidate session |
|
||
|
|
| `auth.changePassword` | `currentPassword`, `newPassword` | Change password |
|
||
|
|
| `auth.onboardingComplete` | — | Mark onboarding done |
|
||
|
|
| `auth.resetOnboarding` | — | Reset onboarding |
|
||
|
|
| `auth.totp.setup.begin` | — | Get TOTP secret + QR |
|
||
|
|
| `auth.totp.setup.confirm` | `code` | Confirm TOTP setup |
|
||
|
|
| `auth.totp.disable` | `password` | Disable 2FA |
|
||
|
|
| `auth.totp.status` | — | Check 2FA enabled |
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Container Management (10 methods)</b></summary>
|
||
|
|
|
||
|
|
| Method | Parameters | Purpose |
|
||
|
|
|--------|-----------|---------|
|
||
|
|
| `container-install` | `image`, `name` | Install container |
|
||
|
|
| `container-start` | `app_id` | Start container |
|
||
|
|
| `container-stop` | `app_id` | Stop container |
|
||
|
|
| `container-remove` | `app_id` | Remove container |
|
||
|
|
| `container-list` | — | List all containers |
|
||
|
|
| `container-status` | `app_id` | Container status |
|
||
|
|
| `container-logs` | `app_id`, `lines` | Container logs |
|
||
|
|
| `container-health` | `app_id` | Container health |
|
||
|
|
| `bundled-app-start` | `app_id` | Start bundled app |
|
||
|
|
| `bundled-app-stop` | `app_id` | Stop bundled app |
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Package Management (5 methods)</b></summary>
|
||
|
|
|
||
|
|
| Method | Parameters | Purpose |
|
||
|
|
|--------|-----------|---------|
|
||
|
|
| `package.install` | `package_id`, `version` | Install from marketplace |
|
||
|
|
| `package.start` | `package_id` | Start package |
|
||
|
|
| `package.stop` | `package_id` | Stop package |
|
||
|
|
| `package.restart` | `package_id` | Restart package |
|
||
|
|
| `package.uninstall` | `package_id` | Uninstall |
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Bitcoin & Lightning (15 methods)</b></summary>
|
||
|
|
|
||
|
|
| Method | Parameters | Purpose |
|
||
|
|
|--------|-----------|---------|
|
||
|
|
| `bitcoin.getinfo` | — | Bitcoin Core info |
|
||
|
|
| `lnd.getinfo` | — | LND node info |
|
||
|
|
| `lnd.listchannels` | — | List channels |
|
||
|
|
| `lnd.openchannel` | `peer_pubkey`, `local_funding_amount` | Open channel |
|
||
|
|
| `lnd.closechannel` | `channel_point` | Close channel |
|
||
|
|
| `lnd.newaddress` | — | Generate address |
|
||
|
|
| `lnd.sendcoins` | `address`, `amount_sats` | Send BTC |
|
||
|
|
| `lnd.createinvoice` | `amount_sats`, `memo` | Create invoice |
|
||
|
|
| `lnd.payinvoice` | `payment_request` | Pay invoice |
|
||
|
|
| `lnd.create-psbt` | `inputs`, `outputs` | Create PSBT |
|
||
|
|
| `lnd.finalize-psbt` | `psbt` | Broadcast PSBT |
|
||
|
|
| `lnd.create-raw-tx` | `inputs`, `outputs` | Raw transaction |
|
||
|
|
| `lnd.gettransactions` | — | Wallet history |
|
||
|
|
| `lnd.connect-info` | — | LND connection string |
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Identity & Crypto (30+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: identity CRUD, DID resolution, Nostr key operations, NIP-04/NIP-44 encryption/decryption, verifiable credentials (issue/verify/revoke), presentations, DHT DID, NIP-05 names, key export.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Node & P2P (15+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: node DID, challenge signing, backup creation, Tor address, Nostr publishing, peer management, message sending, peer discovery.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Federation (10 methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: invite generation, joining, node listing, node removal, trust scoring, state sync, app deployment.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Mesh Networking (20+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: status, peers, messaging, broadcast, LoRa configuration, invoice relay, GPS coordinates, emergency alerts, deadman switch, Bitcoin tx relay, Lightning relay, block headers, X3DH prekey rotation.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Ecash Wallet (6 methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: balance, mint, melt, send, receive, transaction history.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Content Sharing (7 methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: list own content, add/remove files, pricing, availability, browse/download from peers.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>DWN (7 methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: status, sync, protocol management, message query/write.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Network & Infrastructure (20+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: network interfaces, WiFi scan/config, Ethernet config, DNS config, UPnP router discovery/forwarding, Tor service management (list/create/delete/rotate), Nostr relay management, VPN config.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>System Management (15+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: system stats, processes, temperature, USB detection, disk status/cleanup, factory reset, monitoring (current/history/alerts), updates (check/download/apply/rollback), backup (create/list/verify/restore/USB/S3).
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary><b>Other (10+ methods)</b></summary>
|
||
|
|
|
||
|
|
Covers: server naming, analytics opt-in/out, webhook config, security secret rotation, marketplace discovery/publishing.
|
||
|
|
|
||
|
|
</details>
|
||
|
|
|
||
|
|
### 2.3 Direct HTTP Endpoints (Backend)
|
||
|
|
|
||
|
|
| Method | Path | Auth | Source |
|
||
|
|
|--------|------|------|--------|
|
||
|
|
| GET | `/health` | None | `handler.rs:~120` |
|
||
|
|
| GET | `/electrs-status` | None | `handler.rs` |
|
||
|
|
| GET | `/lnd-connect-info` | None | `handler.rs` |
|
||
|
|
| GET | `/content` | None | `handler.rs` |
|
||
|
|
| GET | `/content/*` | None | `handler.rs` (Range header support) |
|
||
|
|
| POST | `/archipelago/node-message` | P2P validation | `handler.rs` |
|
||
|
|
| GET | `/dwn/health` | None | `handler.rs` |
|
||
|
|
| POST | `/dwn` | None (P2P) | `handler.rs` |
|
||
|
|
| WS | `/ws/db` | Session cookie | `handler.rs:514-625` |
|
||
|
|
| GET | `/api/container/logs*` | Session | `handler.rs` |
|
||
|
|
| GET | `/proxy/lnd/*` | Session | `handler.rs` |
|
||
|
|
|
||
|
|
### 2.4 Direct Port Services
|
||
|
|
|
||
|
|
| Port | Service | Own Auth | Notes |
|
||
|
|
|------|---------|----------|-------|
|
||
|
|
| 3000 | Grafana | Session/Basic | Login page directly accessible |
|
||
|
|
| 3001 | Uptime Kuma | Session | Redirects to /dashboard |
|
||
|
|
| 81 | Nginx Proxy Manager | Session | Login page directly accessible |
|
||
|
|
| 7777 | IndeedHub | Nostr NIP-07 | Full app accessible |
|
||
|
|
| 8080 | LND REST | TLS + Macaroon | Requires valid macaroon header |
|
||
|
|
| 8334 | Bitcoin UI | None/Basic Auth on `/bitcoin-rpc/` | Hardcoded creds in nginx config |
|
||
|
|
| 9000 | Portainer | Session | Redirects to timeout (possibly unconfigured) |
|
||
|
|
|
||
|
|
### 2.5 WebSocket Endpoints
|
||
|
|
|
||
|
|
| Path | Auth | Protocol | Features |
|
||
|
|
|------|------|----------|----------|
|
||
|
|
| `/ws/db` | Session cookie | JSON Patch | 30s ping, 5min inactivity timeout, state streaming |
|
||
|
|
| `/app/indeedhub/ws/` | Nostr | WebSocket | 86400s timeout |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Attack Surface Map
|
||
|
|
|
||
|
|
### 3.1 Input Vectors
|
||
|
|
|
||
|
|
| Vector | Endpoint(s) | Input Type | Validation |
|
||
|
|
|--------|------------|------------|------------|
|
||
|
|
| Password login | `auth.login` | JSON body (`password`) | Bcrypt comparison, rate limited (5/min) |
|
||
|
|
| TOTP code | `auth.login.totp` | JSON body (`code`) | Constant-time comparison, 5 attempts |
|
||
|
|
| RPC method dispatch | `/rpc/v1` | JSON body (`method`, `params`) | Switch on method name, typed params |
|
||
|
|
| Container image install | `container-install` | JSON body (`image`) | Image name passed to Podman |
|
||
|
|
| File upload | `/app/filebrowser/` | Multipart/file body | 10GB limit, path traversal check |
|
||
|
|
| P2P messages | `/archipelago/node-message` | JSON body | Source validation (Tor onion) |
|
||
|
|
| DWN writes | `/dwn` | JSON body | Protocol validation |
|
||
|
|
| Content download | `/content/*` | URL path + Range header | Path-based content ID lookup |
|
||
|
|
| Bitcoin transactions | `lnd.sendcoins`, `lnd.payinvoice` | JSON body (address, amount) | Address validation |
|
||
|
|
| DNS configuration | `network.configure-dns` | JSON body (servers) | Server address validation |
|
||
|
|
| WiFi config | `network.configure-wifi` | JSON body (ssid, password) | — |
|
||
|
|
| Package install | `package.install` | JSON body (id, version, url) | marketplace URL fetched |
|
||
|
|
| Federation join | `federation.join` | JSON body (invite code) | Code validation |
|
||
|
|
| Webhook config | `webhook.configure` | JSON body (url, events) | URL stored, callbacks sent |
|
||
|
|
| Bitcoin RPC proxy | `8334:/bitcoin-rpc/` | JSON body (method, params) | Basic Auth (hardcoded) |
|
||
|
|
| Factory reset | `system.factory-reset` | JSON body (`confirm: true`) | Auth + confirm flag |
|
||
|
|
|
||
|
|
### 3.2 Authentication Mechanisms
|
||
|
|
|
||
|
|
| Mechanism | Used By | Strength |
|
||
|
|
|-----------|---------|----------|
|
||
|
|
| Password + bcrypt (cost 12) | Main login | Strong (rate limited) |
|
||
|
|
| TOTP (RFC 6238) | 2FA | Strong (constant-time, replay-protected) |
|
||
|
|
| Session cookie (256-bit random) | All authenticated endpoints | Strong (HttpOnly, SameSite=Strict) |
|
||
|
|
| Remember-me (HMAC-SHA256) | Session persistence | Medium (derived from machine-id) |
|
||
|
|
| CSRF token | State-changing operations | Present but enforcement unclear |
|
||
|
|
| Macaroon (LND) | LND REST API | Strong (but exposed via endpoint) |
|
||
|
|
| Basic Auth (hardcoded) | Bitcoin UI RPC proxy | **Weak** (hardcoded in config) |
|
||
|
|
| Default creds (Grafana) | Grafana admin | **Weak** (admin:admin works) |
|
||
|
|
| No auth | 8 HTTP endpoints, 6 RPC methods | **N/A** |
|
||
|
|
|
||
|
|
### 3.3 Data Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
User Browser
|
||
|
|
│
|
||
|
|
├─[Session Cookie]──→ Nginx (80/443)
|
||
|
|
│ ├──→ /rpc/v1 ──→ Rust Backend (5678) ──→ Podman containers
|
||
|
|
│ ├──→ /ws/db ──→ WebSocket state stream
|
||
|
|
│ ├──→ /app/* ──→ Container UIs (iframes)
|
||
|
|
│ └──→ /aiui/* ──→ Claude Proxy (3141) ──→ Anthropic API
|
||
|
|
│
|
||
|
|
├─[No Auth]──→ /health, /electrs-status, /lnd-connect-info, /content, /dwn
|
||
|
|
│
|
||
|
|
├─[Direct Port]──→ Grafana:3000 (admin:admin)
|
||
|
|
├─[Direct Port]──→ NPM:81 (session)
|
||
|
|
├─[Direct Port]──→ LND:8080 (TLS + macaroon)
|
||
|
|
└─[Direct Port]──→ Bitcoin UI:8334 (Basic Auth hardcoded)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Interesting Findings
|
||
|
|
|
||
|
|
### CRITICAL
|
||
|
|
|
||
|
|
#### 4.1 Unauthenticated LND Admin Macaroon Exposure
|
||
|
|
|
||
|
|
- **Endpoint:** `GET /lnd-connect-info` (no auth required)
|
||
|
|
- **Confirmed:** Returns full admin macaroon (base64url), TLS certificate, gRPC port (10009), REST port (8080)
|
||
|
|
- **Macaroon permissions:** `address:rw`, `info:rw`, `invoices:rw`, `macaroon:generate/rw`, `message:rw`, `offchain:rw`, `onchain:rw`, `peers:rw`, `signer:generate/read`
|
||
|
|
- **Impact:** Any host on the LAN can retrieve the admin macaroon and gain **full control** of the Lightning node — send all funds, open/close channels, create invoices, sign messages. This is the equivalent of exposing a root password to the Bitcoin wallet.
|
||
|
|
- **CORS:** `Access-Control-Allow-Origin: *` (any origin)
|
||
|
|
|
||
|
|
**Proof:**
|
||
|
|
```bash
|
||
|
|
curl -sk http://192.168.1.228/lnd-connect-info
|
||
|
|
# Returns: {"cert_base64url":"MIIC...","grpc_port":10009,"macaroon_base64url":"AgED...","rest_port":8080}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4.2 Grafana Default Credentials (admin:admin)
|
||
|
|
|
||
|
|
- **Endpoint:** `http://192.168.1.228:3000`
|
||
|
|
- **Confirmed:** `admin:admin` returns full organization data
|
||
|
|
- **Version:** Grafana 10.2.0 (commit 895fbafb7a)
|
||
|
|
- **Impact:** Full access to monitoring dashboards, data sources, alert rules. Can potentially access connected databases, execute queries, and pivot to other services via configured data sources.
|
||
|
|
|
||
|
|
**Proof:**
|
||
|
|
```bash
|
||
|
|
curl -sk http://192.168.1.228:3000/api/org -u admin:admin
|
||
|
|
# Returns: {"id":1,"name":"Main Org.","address":{...}}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4.3 Bitcoin RPC Full Access via Hardcoded Credentials
|
||
|
|
|
||
|
|
- **Endpoint:** `POST http://192.168.1.228:8334/bitcoin-rpc/`
|
||
|
|
- **Credentials:** `archipelago:archipelago123` (hardcoded in `docker/bitcoin-ui/nginx.conf`)
|
||
|
|
- **Confirmed:** Returns full `getblockchaininfo` — mainnet, block 941146, 828GB on disk
|
||
|
|
- **Impact:** Full Bitcoin Core RPC access. Depending on wallet configuration, could call `sendtoaddress`, `dumpprivkey`, `listunspent`, or any other RPC method. Mainnet node with real funds.
|
||
|
|
|
||
|
|
**Proof:**
|
||
|
|
```bash
|
||
|
|
curl -sk -X POST http://192.168.1.228:8334/bitcoin-rpc/ \
|
||
|
|
-u archipelago:archipelago123 \
|
||
|
|
-H 'Content-Type: application/json' \
|
||
|
|
-d '{"jsonrpc":"1.0","id":"test","method":"getblockchaininfo","params":[]}'
|
||
|
|
# Returns: {"result":{"chain":"main","blocks":941146,...},"error":null}
|
||
|
|
```
|
||
|
|
|
||
|
|
### HIGH
|
||
|
|
|
||
|
|
#### 4.4 Unauthenticated Content Catalog Exposure
|
||
|
|
|
||
|
|
- **Endpoint:** `GET /content`
|
||
|
|
- **Confirmed:** Returns complete file catalog — filenames, sizes, MIME types, UUIDs
|
||
|
|
- **Data leaked:** Personal music files with full paths (`/Music/1 - Govcucks.wav`, etc.)
|
||
|
|
- **Impact:** Information disclosure of personal files shared via P2P. File UUIDs could be used to download content via `/content/{id}`.
|
||
|
|
|
||
|
|
#### 4.5 Nginx Proxy Manager Accessible on LAN
|
||
|
|
|
||
|
|
- **Endpoint:** `http://192.168.1.228:81`
|
||
|
|
- **API Status:** `{"status":"OK","setup":false,"version":{"major":2,"minor":14,"revision":0}}`
|
||
|
|
- **`setup: false`** — Unclear if this means initial setup hasn't completed (would allow admin takeover) or refers to some other state
|
||
|
|
- **Impact:** NPM controls reverse proxy routing for all services. Compromise = ability to redirect traffic, intercept credentials, or add malicious proxy rules.
|
||
|
|
|
||
|
|
### MEDIUM
|
||
|
|
|
||
|
|
#### 4.6 Version and Service Information Disclosure
|
||
|
|
|
||
|
|
| Source | Information Exposed |
|
||
|
|
|--------|-------------------|
|
||
|
|
| HTTP `Server` header | `nginx/1.22.1` |
|
||
|
|
| Port 81 `Server` header | `openresty` |
|
||
|
|
| Port 3000 `/api/health` | Grafana 10.2.0, commit hash, database status |
|
||
|
|
| Port 81 `/api/` | NPM version 2.14.0 |
|
||
|
|
| Port 8080 TLS cert | `lnd autogenerated cert`, internal IPs, Tailscale IPs |
|
||
|
|
| Port 443 TLS cert | SANs include: 192.168.1.228, 192.168.1.198, 10.0.0.1, archipelago.local |
|
||
|
|
| SSH banner | OpenSSH 9.2p1 Debian 2+deb12u7, ECDSA + ED25519 host keys |
|
||
|
|
| `/electrs-status` | Blockchain sync: 99%, index size 124.8GB, network height |
|
||
|
|
| `/dwn/health` | 1027 messages, 10 protocols, 551KB storage |
|
||
|
|
| `auth.isOnboardingComplete` | Node setup state (returns `true`) |
|
||
|
|
| Error responses | "Password Incorrect" (confirms account exists) |
|
||
|
|
|
||
|
|
#### 4.7 LND TLS Certificate Leaks Internal Network Topology
|
||
|
|
|
||
|
|
The LND auto-generated TLS cert (port 8080) exposes SANs including:
|
||
|
|
- Internal IPs: `192.168.1.228`, `10.88.0.1` (Podman bridge)
|
||
|
|
- Tailscale IPs: `2A00:23C5:E31:A001:572F:29BF:5A00:2D46` (IPv6)
|
||
|
|
- Link-local IPs: 5 different `FE80::` addresses (reveals all network interfaces)
|
||
|
|
|
||
|
|
#### 4.8 CSP Allows `unsafe-inline`
|
||
|
|
|
||
|
|
```
|
||
|
|
script-src 'self' 'unsafe-inline'
|
||
|
|
style-src 'self' 'unsafe-inline'
|
||
|
|
```
|
||
|
|
|
||
|
|
While necessary for the Vue SPA, `unsafe-inline` for scripts significantly weakens XSS protection. If any injection point exists, inline script execution is possible.
|
||
|
|
|
||
|
|
#### 4.9 `connect-src` Allows Broad Connections
|
||
|
|
|
||
|
|
```
|
||
|
|
connect-src 'self' ws: wss: http://192.168.1.228:* https:
|
||
|
|
```
|
||
|
|
|
||
|
|
Allows JavaScript to connect to ANY port on the host and ANY HTTPS endpoint. An XSS payload could exfiltrate data to external servers or interact with any local service port.
|
||
|
|
|
||
|
|
#### 4.10 DWN Endpoint Accepts Unauthenticated Queries
|
||
|
|
|
||
|
|
- **Endpoint:** `POST /dwn`
|
||
|
|
- **Confirmed:** Accepts JSON queries and returns results
|
||
|
|
- **Impact:** Remote parties can query DWN records. While designed for P2P, the lack of access control means any network-adjacent attacker can enumerate stored data.
|
||
|
|
|
||
|
|
### LOW / INFORMATIONAL
|
||
|
|
|
||
|
|
#### 4.11 Login Rate Limiting Works
|
||
|
|
|
||
|
|
Rate limiting triggers after 4 failed attempts (returns HTTP 429). Effective against brute force. However, the limit is per-IP, not per-account — an attacker with multiple IPs could parallelize attempts.
|
||
|
|
|
||
|
|
#### 4.12 CORS Properly Restricts Origins
|
||
|
|
|
||
|
|
CORS preflight for `Origin: http://evil.com` returns no `Access-Control-Allow-Origin` header. Only configured origins (`http://192.168.1.228`, `http://localhost:8100`) are allowed. WebSocket also returns 401 without valid session.
|
||
|
|
|
||
|
|
#### 4.13 Path Traversal Mitigated
|
||
|
|
|
||
|
|
`/content/../../../etc/passwd` returns the SPA index.html (nginx catches it). URL-encoded traversal (`%2f..%2f`) returns 400 Bad Request. FileBrowser has explicit `..` regex checks in nginx config.
|
||
|
|
|
||
|
|
#### 4.14 Git/Env Files Not Exposed
|
||
|
|
|
||
|
|
`/.git/HEAD` and `/.env` both return the SPA index.html (Vue Router catch-all). No source code or credential leakage.
|
||
|
|
|
||
|
|
#### 4.15 Security Headers Present
|
||
|
|
|
||
|
|
All security headers are properly set: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, X-DNS-Prefetch-Control. This is above average for self-hosted applications.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Priority Targets
|
||
|
|
|
||
|
|
### Rank 1: LND Admin Macaroon via `/lnd-connect-info` (CRITICAL)
|
||
|
|
|
||
|
|
- **What:** Unauthenticated HTTP endpoint returns full admin macaroon for LND Lightning node
|
||
|
|
- **Why it's critical:** Grants complete control over Lightning funds — send payments, drain channels, create invoices. No authentication required. Accessible to any device on the LAN.
|
||
|
|
- **Category:** Broken Access Control (OWASP A01:2021)
|
||
|
|
- **Remediation:** Require session authentication on `/lnd-connect-info`. Use read-only macaroon for status checks; only expose admin macaroon via authenticated RPC.
|
||
|
|
|
||
|
|
### Rank 2: Bitcoin RPC via Hardcoded Credentials (CRITICAL)
|
||
|
|
|
||
|
|
- **What:** Port 8334 proxies Bitcoin Core RPC with hardcoded Basic Auth `archipelago:archipelago123`
|
||
|
|
- **Why it's critical:** Mainnet Bitcoin node. If wallet is loaded, attacker can send transactions, export private keys, or manipulate the mempool. Credentials are in version-controlled nginx config.
|
||
|
|
- **Category:** Security Misconfiguration (OWASP A05:2021), Hardcoded Credentials
|
||
|
|
- **Remediation:** Remove hardcoded credentials from nginx config. Proxy Bitcoin RPC through the authenticated Rust backend only. Restrict port 8334 to localhost.
|
||
|
|
|
||
|
|
### Rank 3: Grafana Default Credentials (HIGH)
|
||
|
|
|
||
|
|
- **What:** Grafana on port 3000 accepts `admin:admin`
|
||
|
|
- **Why it's critical:** Full admin access to monitoring infrastructure. Grafana can query connected data sources (Prometheus, InfluxDB), potentially exposing system metrics, logs, and providing a pivot point. Version 10.2.0 may have known CVEs.
|
||
|
|
- **Category:** Identification and Authentication Failures (OWASP A07:2021)
|
||
|
|
- **Remediation:** Change default password. Restrict Grafana to localhost access only (proxy through authenticated nginx). Consider enabling Grafana's built-in auth proxy mode.
|
||
|
|
|
||
|
|
### Rank 4: Unauthenticated Content Catalog (HIGH)
|
||
|
|
|
||
|
|
- **What:** `GET /content` exposes personal files (names, sizes, UUIDs) without authentication
|
||
|
|
- **Why it's concerning:** Reveals personal data. UUIDs may allow direct file download via `/content/{id}`. Designed for P2P but accessible from any LAN host.
|
||
|
|
- **Category:** Broken Access Control (OWASP A01:2021)
|
||
|
|
- **Remediation:** Require peer authentication (DID signature verification) for content catalog access, not just content downloads.
|
||
|
|
|
||
|
|
### Rank 5: Nginx Proxy Manager Direct Access (HIGH)
|
||
|
|
|
||
|
|
- **What:** Port 81 serves NPM admin interface directly on LAN with `setup: false` status
|
||
|
|
- **Why it's concerning:** NPM controls all reverse proxy rules. If the "setup" state allows initial admin creation by anyone, an attacker could take over routing. Even with auth, direct port access bypasses the main nginx security headers.
|
||
|
|
- **Category:** Security Misconfiguration (OWASP A05:2021)
|
||
|
|
- **Remediation:** Restrict port 81 to localhost. Only expose NPM through the authenticated `/app/nginx-proxy-manager/` proxy path.
|
||
|
|
|
||
|
|
### Rank 6: Service Ports Directly Accessible on LAN (MEDIUM)
|
||
|
|
|
||
|
|
- **What:** Ports 3000, 3001, 7777, 8080, 8334, 9000 are directly accessible, bypassing the main nginx proxy and its security headers/CSP/CORS
|
||
|
|
- **Why it's concerning:** Each service has its own (potentially weaker) authentication. Direct access bypasses rate limiting, security headers, and session validation at the nginx layer.
|
||
|
|
- **Category:** Security Misconfiguration (OWASP A05:2021)
|
||
|
|
- **Remediation:** Bind container ports to `127.0.0.1` or Podman bridge network only. All external access should flow through the nginx proxy on port 80/443.
|
||
|
|
|
||
|
|
### Rank 7: RPC Input Injection Surface (MEDIUM)
|
||
|
|
|
||
|
|
- **What:** 150+ RPC methods accept JSON parameters that control container operations, system commands, network config, file operations, and Bitcoin transactions
|
||
|
|
- **Why it's concerning:** Methods like `container-install` (image name → Podman), `network.configure-dns` (DNS servers), `webhook.configure` (arbitrary URL callbacks), `package.install` (marketplace URL fetch) all accept user-controlled strings that interact with system commands or external services.
|
||
|
|
- **Category:** Injection (OWASP A03:2021), SSRF (OWASP A10:2021)
|
||
|
|
- **Remediation:** Audit each method for proper input sanitization. Especially: container image names (prevent registry confusion), webhook URLs (prevent SSRF), DNS servers (prevent DNS rebinding), marketplace URLs (prevent SSRF).
|
||
|
|
|
||
|
|
### Rank 8: CSP `unsafe-inline` + Broad `connect-src` (MEDIUM)
|
||
|
|
|
||
|
|
- **What:** CSP allows inline scripts and connections to any port on the host or any HTTPS endpoint
|
||
|
|
- **Why it's concerning:** If any XSS vector exists (e.g., in app iframe content, reflected parameters, or injected HTML), the attacker can execute arbitrary JavaScript and exfiltrate data to external servers or interact with all local services.
|
||
|
|
- **Category:** XSS / Security Misconfiguration (OWASP A03/A05:2021)
|
||
|
|
- **Remediation:** Migrate to nonce-based CSP. Restrict `connect-src` to specific required ports/domains.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Appendix: Security Headers (Full)
|
||
|
|
|
||
|
|
```http
|
||
|
|
X-Content-Type-Options: nosniff
|
||
|
|
X-Frame-Options: SAMEORIGIN
|
||
|
|
Referrer-Policy: strict-origin-when-cross-origin
|
||
|
|
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
|
||
|
|
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||
|
|
X-DNS-Prefetch-Control: off
|
||
|
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';
|
||
|
|
style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:;
|
||
|
|
font-src 'self' data:; connect-src 'self' ws: wss: http://192.168.1.228:* https:;
|
||
|
|
frame-src 'self' http://192.168.1.228:* https:; frame-ancestors 'self';
|
||
|
|
base-uri 'self'; form-action 'self';
|
||
|
|
Server: nginx/1.22.1
|
||
|
|
```
|
||
|
|
|
||
|
|
## Appendix: Rate Limiting Configuration
|
||
|
|
|
||
|
|
| Layer | Target | Rate | Burst |
|
||
|
|
|-------|--------|------|-------|
|
||
|
|
| Nginx | `/rpc/` | 20 req/s | 40 |
|
||
|
|
| Backend | `auth.login` | 5 per 60s per IP | — |
|
||
|
|
| Backend | Financial ops (send, pay) | 5-10 per 300s | — |
|
||
|
|
| Backend | Auth changes (password, TOTP) | 3 per 300s | — |
|
||
|
|
| Backend | Container ops | 5 per 300s | — |
|
||
|
|
| Backend | Federation join | 5 per 60s | — |
|
||
|
|
|
||
|
|
## Appendix: Authentication Summary
|
||
|
|
|
||
|
|
| What's Good | What Needs Work |
|
||
|
|
|-------------|-----------------|
|
||
|
|
| Bcrypt cost 12 for passwords | `/lnd-connect-info` unauthenticated |
|
||
|
|
| Argon2id for TOTP key derivation | Bitcoin RPC hardcoded creds |
|
||
|
|
| ChaCha20-Poly1305 for TOTP secret encryption | Grafana default admin:admin |
|
||
|
|
| 256-bit random session tokens | Service ports directly accessible |
|
||
|
|
| HttpOnly + SameSite=Strict cookies | CSP unsafe-inline |
|
||
|
|
| Rate limiting on login (5/min) | NPM port 81 open on LAN |
|
||
|
|
| CORS origin validation | connect-src too permissive |
|
||
|
|
| Session rotation on password change | Initial password only 8 chars |
|
||
|
|
| TOTP replay protection | Error messages confirm account existence |
|
||
|
|
| AES-256-GCM secrets at rest | Rate limiter enforcement unclear for some methods |
|