69 lines
3 KiB
Python
69 lines
3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit test for dogecoin-0001: SOCKS5 proxy password logged in plaintext (CWE-312 / MOAD-0004)
|
|
|
|
Validates that the patched LogPrint line masks the password.
|
|
"""
|
|
|
|
import re
|
|
import unittest
|
|
|
|
|
|
# Original line (DEFECTIVE): logs password in plaintext
|
|
ORIGINAL_LINE = 'LogPrint("proxy", "SOCKS5 sending proxy authentication %s:%s\\n", auth->username, auth->password);'
|
|
|
|
# Patched line: masks the password
|
|
PATCHED_LINE = 'LogPrint("proxy", "SOCKS5 sending proxy authentication %s:***\\n", auth->username);'
|
|
|
|
|
|
def read_source(path="src/netbase.cpp"):
|
|
"""Read the source file and return the relevant log line."""
|
|
try:
|
|
with open(path, "r") as f:
|
|
for line in f:
|
|
if "SOCKS5 sending proxy authentication" in line:
|
|
return line.strip()
|
|
except FileNotFoundError:
|
|
return None
|
|
return None
|
|
|
|
|
|
class TestDogecoin0001(unittest.TestCase):
|
|
"""Test that proxy password is not logged in plaintext."""
|
|
|
|
def test_original_line_contains_password_format_specifier(self):
|
|
"""The original defective line has two %s format specifiers (username AND password)."""
|
|
# Count format specifiers in the format string portion
|
|
fmt_match = re.search(r'"SOCKS5 sending proxy authentication ([^"]*)"', ORIGINAL_LINE)
|
|
self.assertIsNotNone(fmt_match, "Could not find format string in original line")
|
|
fmt_str = fmt_match.group(1)
|
|
specifier_count = fmt_str.count("%s")
|
|
self.assertEqual(specifier_count, 2, "Original line should have 2 format specifiers (username + password)")
|
|
|
|
def test_patched_line_masks_password(self):
|
|
"""The patched line should only have one %s (username) and mask the password with ***."""
|
|
fmt_match = re.search(r'"SOCKS5 sending proxy authentication ([^"]*)"', PATCHED_LINE)
|
|
self.assertIsNotNone(fmt_match, "Could not find format string in patched line")
|
|
fmt_str = fmt_match.group(1)
|
|
specifier_count = fmt_str.count("%s")
|
|
self.assertEqual(specifier_count, 1, "Patched line should have only 1 format specifier (username only)")
|
|
self.assertIn("***", fmt_str, "Patched line should mask password with ***")
|
|
|
|
def test_patched_line_does_not_reference_auth_password(self):
|
|
"""The patched line should not reference auth->password at all."""
|
|
self.assertNotIn("auth->password", PATCHED_LINE,
|
|
"Patched line must not reference auth->password")
|
|
|
|
def test_patched_line_preserves_username(self):
|
|
"""The patched line should still log the username for diagnostic purposes."""
|
|
self.assertIn("auth->username", PATCHED_LINE,
|
|
"Patched line should still reference auth->username")
|
|
|
|
def test_original_line_exposes_password(self):
|
|
"""The original line references auth->password, exposing it to logs."""
|
|
self.assertIn("auth->password", ORIGINAL_LINE,
|
|
"Original line should reference auth->password (confirming the defect)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|