# Service Realism Audit > Live-tested against `192.168.1.200` (omega-decky, full-audit.ini). > Every result below is from an actual `nc` or `nmap` probe, not code reading. --- ## nmap -sV Summary ``` 21/tcp ftp vsftpd (before 2.0.8) or WU-FTPD ← WRONG: banner says "Twisted 25.5.0" 23/tcp telnet (unrecognized — Cowrie) 25/tcp smtp Postfix smtpd ✓ 80/tcp http Apache httpd 2.4.54 ((Debian)) ✓ BUT leaks Werkzeug 110/tcp pop3 (unrecognized) 143/tcp imap (unrecognized) 389/tcp ldap Cisco LDAP server 445/tcp microsoft-ds ✓ 1433/tcp ms-sql-s? (partially recognized) 1883/tcp mqtt ✓ 2375/tcp docker Docker 24.0.5 ✓ 3306/tcp mysql MySQL 5.7.38-log ✓ 3389/tcp ms-wbt-server xrdp 5060/tcp sip SIP endpoint; Status: 401 Unauthorized ✓ 5432/tcp postgresql? (partially recognized) 5900/tcp vnc VNC protocol 3.8 ✓ 6379/tcp redis? (partially recognized) 6443/tcp (unrecognized) — K8s not responding at all 9200/tcp wap-wsp? (completely unrecognized — ES) 27017/tcp mongod? (partially recognized) 502/tcp CLOSED — Conpot Modbus not on this port ``` --- ## Service-by-Service --- ### SMTP — port 25 **Probe:** ``` 220 omega-decky ESMTP Postfix (Debian/GNU) 250-PIPELINING / SIZE / VRFY / AUTH PLAIN LOGIN / ENHANCEDSTATUSCODES / 8BITMIME / DSN 250 2.1.0 Ok ← MAIL FROM accepted 250 2.1.5 Ok ← RCPT TO accepted for any domain ✓ (open relay bait) 354 End data with... ← DATA opened 502 5.5.2 Error: command not recognized ← BUG: each message line fails 221 2.0.0 Bye ``` **Verdict:** Banner and EHLO are perfect. DATA handler is broken — server reads the socket line-by-line but the asyncio handler dispatches each line as a new command instead of buffering until `.\r\n`. The result is every line of the email body gets a 502 and the message is silently dropped. **Fixes needed:** - Buffer DATA state until `\r\n.\r\n` terminator - Return `250 2.0.0 Ok: queued as <8-hex-id>` after message accepted - Don't require AUTH for relay (open relay is the point) - Optionally: store message content so IMAP can serve it later --- ### IMAP — port 143 **Probe:** ``` * OK [omega-decky] IMAP4rev1 Service Ready A1 OK CAPABILITY completed A2 NO [AUTHENTICATIONFAILED] Invalid credentials ← always, for any user/pass A3 BAD Command not recognized ← LIST, SELECT, FETCH all unknown ``` **Verdict:** Login always fails. No mailbox commands implemented. An attacker who tries credential stuffing or default passwords (admin/admin, root/root) gets nothing and moves on. This is the biggest missed opportunity in the whole stack. **Fixes needed:** - Accept configurable credentials (default `admin`/`admin` or pulled from persona config) - Implement: SELECT, LIST, FETCH, UID FETCH, SEARCH, LOGOUT - Serve seeded fake mailboxes with bait content (see IMAP_BAIT.md) - CAPABILITY should advertise `LITERAL+`, `SASL-IR`, `LOGIN-REFERRALS`, `ID`, `ENABLE`, `IDLE` - Banner should hint at Dovecot: `* OK Dovecot ready.` --- ### POP3 — port 110 **Probe:** ``` +OK omega-decky POP3 server ready +OK ← USER accepted -ERR Authentication failed ← always -ERR Unknown command ← STAT, LIST, RETR all unknown ``` **Verdict:** Same problem as IMAP. CAPA only returns `USER`. Should be paired with IMAP fix to serve the same fake mailbox. **Fixes needed:** - Accept same credentials as IMAP - Implement: STAT, LIST, RETR, DELE, TOP, UIDL, CAPA - CAPA should return: `TOP UIDL RESP-CODES AUTH-RESP-CODE SASL USER` --- ### HTTP — port 80 **Probe:** ``` HTTP/1.1 403 FORBIDDEN Server: Werkzeug/3.1.8 Python/3.11.2 ← DEAD GIVEAWAY Server: Apache/2.4.54 (Debian) ← duplicate Server header ``` **Verdict:** nmap gets the Apache fingerprint right, but any attacker who looks at response headers sees two `Server:` headers — one of which is clearly Werkzeug/Flask. The HTTP body is also a bare `

403 Forbidden

` with no Apache default page styling. **Fixes needed:** - Strip Werkzeug from Server header (set `SERVER_NAME` on the Flask app or use middleware to overwrite) - Apache default 403 page should be the actual Apache HTML, not a bare `

` tag - Per-path routing for fake apps: `/wp-login.php`, `/wp-admin/`, `/xmlrpc.php`, etc. - POST credential capture on login endpoints --- ### FTP — port 21 **Probe:** ``` 220 Twisted 25.5.0 FTP Server ← terrible: exposes framework 331 Guest login ok... 550 Requested action not taken ← after login, nothing works 503 Incorrect sequence of commands: must send PORT or PASV before RETR ``` **Verdict:** Banner immediately identifies this as Twisted's built-in FTP server. No directory listing. PASV mode not implemented so clients hang. Real FTP honeypots should expose anonymous access with a fake directory tree containing interesting-sounding files. **Fixes needed:** - Override banner to: `220 (vsFTPd 3.0.3)` or similar - Implement anonymous login (no password required) - Implement PASV and at minimum LIST — return a fake directory with files: `backup.tar.gz`, `db_dump.sql`, `config.ini`, `credentials.txt` - Log any RETR attempts (file name, client IP) --- ### MySQL — port 3306 **Probe:** ``` HANDSHAKE: ...5.7.38-log... Version: 5.7.38-log ``` **Verdict:** Handshake is excellent. nmap fingerprints it perfectly. Always returns `Access denied` which is correct behavior. The only issue is the hardcoded auth plugin data bytes in the greeting — a sophisticated scanner could detect the static challenge. **Fixes needed (low priority):** - Randomize the 20-byte auth plugin data per connection --- ### PostgreSQL — port 5432 **Probe:** ``` R\x00\x00\x00\x0c\x00\x00\x00\x05\xde\xad\xbe\xef ``` That's `AuthenticationMD5Password` (type=5) with salt `0xdeadbeef`. **Verdict:** Correct protocol response. Salt is hardcoded and static — `deadbeef` is trivially identifiable as fake. **Fixes needed (low priority):** - Randomize the 4-byte MD5 salt per connection --- ### MSSQL — port 1433 **Probe:** No response to standard TDS pre-login packets. Server drops connection immediately. **Verdict:** Broken. TDS pre-login handler is likely mismatching the packet format we sent. **Fixes needed:** - Debug TDS pre-login response — currently silent - Verify the hardcoded TDS response bytes are valid --- ### Redis — port 6379 **Probe:** ``` +OK ← AUTH accepted (any password!) $150 redis_version:7.2.7 / os:Linux 5.15.0 / uptime_in_seconds:864000 ... *0 ← KEYS * returns empty ``` **Verdict:** Accepts any AUTH password (intentional for bait). INFO looks real. But `KEYS *` returns nothing — a real Redis exposed to the internet always has data. An attacker who gets `+OK` on AUTH will immediately run `KEYS *` or `SCAN 0` and leave when they find nothing. **Fixes needed:** - Add fake key-value store: session tokens, JWT secrets, cached user objects, API keys - `KEYS *` → `["sessions:user:1234", "cache:api_key", "jwt:secret", "user:admin"]` - `GET sessions:user:1234` → JSON user object with credentials - `GET jwt:secret` → a plausible JWT signing key --- ### MongoDB — port 27017 **Probe:** No response to OP_MSG `isMaster` command. **Verdict:** Broken or rejecting the wire protocol format we sent. **Fixes needed:** - Debug the OP_MSG/OP_QUERY handler --- ### Elasticsearch — port 9200 **Probe:** ```json {"name":"omega-decky","cluster_uuid":"xC3Pr9abTq2mNkOeLvXwYA","version":{"number":"7.17.9",...}} /_cat/indices → [] ← empty: dead giveaway ``` **Verdict:** Root response is convincing. But `/_cat/indices` returns an empty array — a real exposed ES instance has indices. nmap doesn't recognize port 9200 as Elasticsearch at all ("wap-wsp?"). **Fixes needed:** - Add fake indices: `logs-2024.01`, `users`, `products`, `audit_trail` - `/_cat/indices` → return rows with doc counts, sizes - `/_search` on those indices → return sample documents (bait data: user records, API tokens) --- ### Docker API — port 2375 **Probe:** ```json /version → {Version: "24.0.5", ApiVersion: "1.43", GoVersion: "go1.20.6", ...} ✓ /containers/json → [{"Id":"a1b2c3d4e5f6","Names":["/webapp"],"Image":"nginx:latest",...}] ``` **Verdict:** Version response is perfect. Container list is minimal (one hardcoded container). No `/images/json` data, no exec endpoint. An attacker will immediately try `POST /containers/webapp/exec` to get RCE. **Fixes needed:** - Add 3-5 containers with realistic names/images: `db` (postgres:14), `api` (node:18-alpine), `redis` (redis:7) - Add `/images/json` with corresponding images - Add exec endpoint that captures the command and returns `{"Id":""}` then a fake stream --- ### SMB — port 445 **Probe:** SMB1 negotiate response received (standard `\xff\x53\x4d\x42r` header). **Verdict:** Impacket SimpleSMBServer responds. nmap IDs it as `microsoft-ds`. Functional enough for credential capture. --- ### VNC — port 5900 **Probe:** ``` RFB 003.008 ✓ ``` **Verdict:** Correct RFB 3.8 handshake. nmap fingerprints it as VNC protocol 3.8. The 16-byte DES challenge is hardcoded — same bytes every time. **Fixes needed (trivial):** - Randomize the 16-byte challenge per connection (`os.urandom(16)`) --- ### RDP — port 3389 **Probe:** ``` 0300000b06d00000000000 ← X.224 Connection Confirm (connection closed) ``` **Verdict:** nmap identifies it as "xrdp" which is correct enough. The X.224 CC is fine. But the server closes immediately after — no NLA/CredSSP negotiation, no credential capture. This is the single biggest missed opportunity for credential harvesting after SSH. **Fixes needed:** - Implement NTLM Type-1/Type-2/Type-3 exchange to capture NTLMv2 hashes - Alternatively: send a fake TLS certificate then disconnect (many scanners fingerprint by the cert) --- ### SIP — port 5060 **Probe:** ``` SIP/2.0 401 Unauthorized WWW-Authenticate: Digest realm="omega-decky", nonce="decnet0000", algorithm=MD5 ``` **Verdict:** Functional. Correctly challenges with 401. But `nonce="decnet0000"` is a hardcoded string — a Shodan signature would immediately pick this up. **Fixes needed (low effort):** - Generate a random hex nonce per connection --- ### MQTT — port 1883 **Probe:** `CONNACK` with return code `0x05` (not authorized). **Verdict:** Rejects all connections. For an ICS/water-plant persona, this should accept connections and expose fake sensor topics. See `ICS_SCADA.md`. **Fixes needed:** - Return CONNACK 0x00 (accepted) - Implement SUBSCRIBE: return retained sensor readings for bait topics - Implement PUBLISH: log any published commands (attacker trying to control plant) --- ### SNMP — port 161/UDP Not directly testable without sudo for raw UDP send, but code review shows BER encoding is correct. **Verdict:** Functional. sysDescr is a generic Linux string — should be tuned per archetype. --- ### LDAP — port 389 **Probe:** BER response received (code 49 = invalidCredentials). **Verdict:** Correct protocol. nmap IDs it as "Cisco LDAP server" which is fine. No rootDSE response for unauthenticated enumeration. --- ### Telnet — port 23 (Cowrie) **Probe:** ``` login: Password: Login incorrect ← for all tried credentials ``` **Verdict:** Cowrie is running but rejecting everything. Default Cowrie credentials (root/1234, admin/admin, etc.) should work. May be a config issue with the decky hostname or user database. --- ### Conpot — port 502 **Verdict:** Not responding on port 502 (Modbus TCP). Conpot may use a different internal port that gets NAT'd, or it's not configured for Modbus. Needs investigation. --- ## Bug Ledger | # | Service | Bug | Severity | |---|------------|-------------------------------------------|----------| | 1 | SMTP | DATA handler returns 502 for every line | Critical | | 2 | HTTP | Werkzeug in Server header + bare 403 body | High | | 3 | FTP | "Twisted 25.5.0" in banner | High | | 4 | MSSQL | No response to TDS pre-login | High | | 5 | MongoDB | No response to OP_MSG isMaster | High | | 6 | K8s | Not responding (TLS setup?) | Medium | | 7 | IMAP/POP3 | Always rejects, no mailbox ops | Critical (feature gap) | | 8 | Redis | Empty keyspace after AUTH success | Medium | | 9 | SIP/VNC | Hardcoded nonce/challenge | Low | | 10| MQTT | Rejects all connections | High (ICS feature gap) | | 11| Conpot | No Modbus response | Medium | | 12| PostgreSQL | Hardcoded salt `deadbeef` | Low | --- ## Related Plans - [`SMTP_RELAY.md`](SMTP_RELAY.md) — Fix DATA handler, implement open relay persona - [`IMAP_BAIT.md`](IMAP_BAIT.md) — Auth + seeded mailboxes + POP3 parity - [`ICS_SCADA.md`](ICS_SCADA.md) — MQTT water plant, SNMP tuning, Conpot - [`BUG_FIXES.md`](BUG_FIXES.md) — HTTP header leak, FTP banner, MSSQL, MongoDB, Redis keys