java-topology/defects/pidgin-0002/test/test_pidgin_0002.py

165 lines
5.4 KiB
Python

"""
pidgin-0002: SIP SIMPLE Authorization header logged verbatim (CWE-312)
libpurple/protocols/simple/simple.c lines 661-669: after constructing our
Authorization or Proxy-Authorization header via auth_header(), our SIMPLE
plugin calls purple_debug(PURPLE_DEBUG_MISC, "simple", "header %s", auth)
which logs our full credential string to debug output.
Our auth string contains either:
- Digest response (offline replay/preimage attack risk)
- NTLM Type3 blob (offline crack risk via hashcat mode 5600)
Fix: remove or redact our purple_debug() calls, logging only our auth type
and method rather than our full credential value.
CWE-312: Cleartext Storage of Sensitive Information
UNDF: UNDF-2026-000001142
"""
import re
import os
PYTHONUNBUFFERED = 1 # ensure unbuffered output
# --- Defective model ---
def build_digest_auth(username: str, realm: str, nonce: str, response: str) -> str:
"""Build a Digest Authorization header value."""
return (
f'Digest username="{username}", realm="{realm}", '
f'nonce="{nonce}", response="{response}"'
)
def build_ntlm_auth(ntlm_blob: str) -> str:
"""Build an NTLM Authorization header value."""
return f'NTLM gssapi-data="{ntlm_blob}"'
def defective_send_register(auth_value: str, log_sink: list[str]) -> str:
"""
Models defective code:
auth = g_strdup_printf("Authorization: %s\r\n", buf)
purple_debug(PURPLE_DEBUG_MISC, "simple", "header %s", auth)
"""
header = f"Authorization: {auth_value}\r\n"
# defective: log the full header including credential
log_sink.append(f"[DEBUG simple] header {header}")
return header
def fixed_send_register(method: str, auth_type: str, log_sink: list[str]) -> str:
"""
Models fixed code:
purple_debug(PURPLE_DEBUG_MISC, "simple",
"sending auth header type=%d for method=%s\n", type, method)
Logs only auth type and method, never credential value.
"""
log_sink.append(f"[DEBUG simple] sending auth header type={auth_type} for method={method}")
return f"Authorization: <redacted>\r\n"
# --- Tests ---
def test_defective_leaks_credential():
"""Defective code emits credential to debug log."""
log = []
response_hash = "a3f2c1b9deadbeef"
auth = build_digest_auth("alice", "corp.example.com", "nonce123", response_hash)
defective_send_register(auth, log)
assert len(log) == 1
# credential appears in log
assert response_hash in log[0], "Expected response hash in defective log"
assert "alice" in log[0], "Expected username in defective log"
print(f"defective log entry: {log[0][:80]}...")
print("defective leaks credential: CONFIRMED")
def test_defective_leaks_ntlm():
"""Defective code leaks NTLM hash blob to debug log."""
log = []
ntlm_blob = "TlRMTVNTUAADAAAA" + "A" * 60 # simulated NTLM Type3
auth = build_ntlm_auth(ntlm_blob)
defective_send_register(auth, log)
assert len(log) == 1
assert ntlm_blob in log[0], "Expected NTLM blob in defective log"
print(f"defective NTLM log contains blob: CONFIRMED")
def test_fixed_does_not_leak_credential():
"""Fixed code logs only auth type and method, no credential value."""
log = []
fixed_send_register("REGISTER", "Digest", log)
assert len(log) == 1
entry = log[0]
# no credential value in log
assert "response=" not in entry, "Fixed log must not contain response hash"
assert "gssapi-data=" not in entry, "Fixed log must not contain NTLM blob"
assert "nonce=" not in entry, "Fixed log must not contain nonce"
# does log method info
assert "REGISTER" in entry, "Fixed log should contain method name"
assert "Digest" in entry, "Fixed log should contain auth type"
print(f"fixed log entry: {entry}")
print("fixed does not leak credential: PASS")
def test_fixed_does_not_leak_ntlm():
"""Fixed code for NTLM path also redacts credential."""
log = []
fixed_send_register("REGISTER", "NTLM", log)
assert "gssapi-data=" not in log[0]
assert "NTLM" in log[0]
print("fixed does not leak NTLM blob: PASS")
def test_pattern_detection():
"""
Verify our fix pattern: scan for purple_debug calls logging 'auth' or
'Authorization' header values. Our fix removes these calls.
"""
# Defective pattern: purple_debug with auth variable containing header
defective_pattern = re.compile(
r'purple_debug\s*\(.*"header\s+%s"\s*,\s*auth\s*\)',
re.DOTALL
)
defective_code = '''
auth = g_strdup_printf("Authorization: %s\\r\\n", buf);
g_free(buf);
purple_debug(PURPLE_DEBUG_MISC, "simple", "header %s", auth);
'''
fixed_code = '''
auth = g_strdup_printf("Authorization: %s\\r\\n", buf);
g_free(buf);
purple_debug(PURPLE_DEBUG_MISC, "simple",
"sending auth header type=%d for method=%s\\n",
sip->registrar.type, method);
'''
assert defective_pattern.search(defective_code), "Should detect defective pattern"
assert not defective_pattern.search(fixed_code), "Should not match fixed code"
print("pattern detection: PASS")
if __name__ == "__main__":
os.environ["PYTHONUNBUFFERED"] = "1"
print("pidgin-0002: SIP SIMPLE Authorization header logged verbatim (CWE-312)")
print("=" * 70)
test_defective_leaks_credential()
test_defective_leaks_ntlm()
test_fixed_does_not_leak_credential()
test_fixed_does_not_leak_ntlm()
test_pattern_detection()
print()
print("ALL TESTS PASS")