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,11 +22,21 @@ from .conftest import load_real_instance_seed, make_fake_syslog_bridge
|
||||
# ── Module loader ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _load_real_ntlmssp():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"ntlmssp", "decnet/templates/_shared/ntlmssp.py"
|
||||
)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def _load_rdp():
|
||||
for key in ("rdp_server", "syslog_bridge", "instance_seed"):
|
||||
for key in ("rdp_server", "syslog_bridge", "instance_seed", "ntlmssp"):
|
||||
sys.modules.pop(key, None)
|
||||
sys.modules["syslog_bridge"] = make_fake_syslog_bridge()
|
||||
sys.modules["instance_seed"] = load_real_instance_seed()
|
||||
sys.modules["ntlmssp"] = _load_real_ntlmssp()
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"rdp_server", "decnet/templates/rdp/server.py"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user