165 lines
5.4 KiB
Python
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")
|