Files
rpcscope/tests/test_rules.py
T
2026-10-15 12:34:26 -04:00

105 lines
5.0 KiB
Python

import unittest
from rpcscope.models import HostEvidence, RpcInterface, Share
from rpcscope.rules import evaluate_host
class RuleTests(unittest.TestCase):
def test_rules_separate_exposure_from_confirmed_vulnerability(self):
host = HostEvidence(
address="10.10.10.25",
smb1=True,
signing=False,
null_session=True,
shares=[Share("Public", ["READ", "WRITE"])],
rpc_interfaces=[RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0")],
)
findings = evaluate_host(host)
ids = {finding.rule_id for finding in findings}
self.assertTrue({"SMB-001", "SMB-002", "SMB-003", "SMB-005"} <= ids)
rpc = next(finding for finding in findings if finding.rule_id.startswith("RPC-"))
self.assertIn("does not prove", rpc.impact)
self.assertNotIn("MSF-MS17-010", ids)
def test_msf_result_is_explicit(self):
host = HostEvidence(address="10.10.10.25", checks={"ms17-010": "vulnerable"})
self.assertTrue(any(f.rule_id == "CHECK-MS17-010" for f in evaluate_host(host)))
def test_catalog_check_becomes_detailed_finding(self):
host = HostEvidence(address="10.10.10.25", checks={"zerologon": "vulnerable"})
finding = next(f for f in evaluate_host(host) if f.rule_id == "CHECK-ZEROLOGON")
self.assertEqual(finding.severity, "critical")
self.assertIn("CVE-2020-1472", finding.title)
def test_rpc_registration_does_not_claim_remote_reachability(self):
host = HostEvidence("10.10.10.25", rpc_interfaces=[
RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0",
endpoint="ncalrpc:[local-spooler]")
])
finding = evaluate_host(host)[0]
self.assertEqual(finding.severity, "info")
self.assertIn("local bindings only", finding.title)
self.assertIn("reachability untested", finding.evidence)
def test_rpc_report_preserves_remote_binding_after_local_binding(self):
host = HostEvidence("10.10.10.25", rpc_interfaces=[
RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0",
bindings=["ncalrpc:[local-spooler]", "ncacn_ip_tcp:10.10.10.25[49665]"])
])
finding = evaluate_host(host)[0]
self.assertEqual(finding.severity, "medium")
self.assertIn("ncacn_ip_tcp", finding.evidence)
self.assertNotIn("local bindings only", finding.title)
def test_legacy_fingerprint_uses_version_boundaries(self):
for os in ("Windows 11", "Windows Server 2019", "Windows 80", "Samba 4.20.1"):
with self.subTest(os=os):
self.assertFalse(any(f.rule_id == "SMB-007" for f in evaluate_host(
HostEvidence("10.10.10.25", os=os)
)))
for os in ("Windows 7 Build 7601", "Windows 8.1", "Windows Server 2008 R2", "Samba 3.6.1"):
with self.subTest(os=os):
self.assertTrue(any(f.rule_id == "SMB-007" for f in evaluate_host(
HostEvidence("10.10.10.25", os=os)
)))
def test_acl_write_capability_does_not_claim_direct_write(self):
host = HostEvidence("10.10.10.25", shares=[Share("Data", ["READ", "WRITE (ACL)"])])
finding = evaluate_host(host)[0]
self.assertEqual(finding.rule_id, "SMB-005")
self.assertIn("permission-control", finding.title)
self.assertIn("WRITE (ACL)", finding.evidence)
def test_suspected_checker_indicator_is_preserved_with_low_confidence(self):
host = HostEvidence("10.10.10.25", checks={"smbghost": "suspected"},
check_details={"smbghost": "SMB 3.1.1 compression capability observed"})
finding = evaluate_host(host)[0]
self.assertEqual(finding.confidence, "low")
self.assertIn("requires validation", finding.title)
self.assertEqual(finding.evidence, host.check_details["smbghost"])
self.assertIn("does not distinguish patched", finding.impact)
def test_checker_positive_includes_actual_evidence(self):
host = HostEvidence("10.10.10.25", checks={"ms17-010": "vulnerable"},
check_details={"ms17-010": "positive checker marker from raw artifact"})
finding = evaluate_host(host)[0]
self.assertEqual(finding.confidence, "high")
self.assertEqual(finding.evidence, host.check_details["ms17-010"])
def test_rpc_evidence_preserves_version_to_binding_mapping(self):
host = HostEvidence("10.10.10.25", rpc_interfaces=[
RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0",
endpoint="ncalrpc:[local-spooler]"),
RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "2.0",
endpoint="ncacn_ip_tcp:10.10.10.25[49665]"),
])
finding = evaluate_host(host)[0]
self.assertIn("v1.0 advertised via ncalrpc:[local-spooler]", finding.evidence)
self.assertIn("v2.0 advertised via ncacn_ip_tcp:10.10.10.25[49665]", finding.evidence)
if __name__ == "__main__":
unittest.main()