feat(creds): DEBT-040 Phase 3 — RDP NLA / CredSSP NTLMv2 capture
When RDP_ENABLE_NLA=true (service_cfg.nla=true on the topology side), confirm PROTOCOL_HYBRID on the X.224 Connection Confirm, upgrade the socket to TLS using a self-signed cert generated at first start by the entrypoint, then drive a tiny CredSSP loop: - Read inbound TSRequest DER (bounded to MAX_TSREQUEST_LEN). - Scan for the NTLMSSP signature, dispatch on message type: Type 1 -> respond with a hand-built TSRequest carrying our Type 2 challenge. Type 3 -> parse_type3() and emit auth_attempt with the universal credential SD shape (secret_kind = ntlmssp_v2). - Hand-built DER: no pyasn1 dependency. Also folds in a small fix-up to commit 1: SMB SERVER_CHALLENGE was hardcoded to 0x11..0x88 across the fleet, which would let a scanner fingerprint every DECNET decky by its NTLM challenge. Both SMB and RDP now derive the 8-byte challenge from instance_seed.random_bytes(8, "ntlm_challenge"), giving each decky a deterministic-but-distinct value. SMB Dockerfile gets the instance_seed.py copy too (was synced into the build context but not COPYed into the image). - decnet/services/rdp.py: optional service_cfg.nla bool flips RDP_ENABLE_NLA in the compose env. - decnet/templates/rdp/Dockerfile + entrypoint.sh: openssl install + per-decky cert generation gated on RDP_ENABLE_NLA. - 9 NLA unit tests cover the DER reader/builder, _handle_nla round- trip with Type 1 / Type 3, oversized-DER rejection, and per- NODE_NAME challenge divergence. - DEBT.md: DEBT-040 closed; full TS_INFO_PACKET capture documented as a follow-up if attacker telemetry justifies it.
This commit is contained in:
@@ -22,6 +22,7 @@ import asyncio
|
||||
import os
|
||||
import struct
|
||||
|
||||
import instance_seed
|
||||
from ntlmssp import find_ntlmssp, parse_type3
|
||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||
|
||||
@@ -44,10 +45,13 @@ SMB2_SESSION_SETUP = 0x0001
|
||||
SMB2_MAGIC = b"\xfeSMB"
|
||||
NBSS_SESSION_MESSAGE = 0x00
|
||||
|
||||
# Server's fixed 8-byte NTLM challenge (random-looking; honeypot, not crypto)
|
||||
SERVER_CHALLENGE = b"\x11\x22\x33\x44\x55\x66\x77\x88"
|
||||
# Stable server GUID — 16 zero bytes is fine for a honeypot.
|
||||
SERVER_GUID = b"\x00" * 16
|
||||
# Per-instance NTLM challenge: deterministic-per-decky-but-different-
|
||||
# across-the-fleet. Derived from NODE_NAME so two captures from the
|
||||
# same decky reuse the same challenge (lets offline attackers retry
|
||||
# wordlists), while every decky in the fleet differs (looks like a
|
||||
# real population of hosts to a scanner).
|
||||
SERVER_CHALLENGE = instance_seed.random_bytes(8, "ntlm_challenge")
|
||||
SERVER_GUID = instance_seed.random_bytes(16, "smb_server_guid")
|
||||
|
||||
# Read caps; an attacker shouldn't be able to make us allocate
|
||||
# unbounded memory just by lying about NetBIOS frame length.
|
||||
|
||||
Reference in New Issue
Block a user