Initial RPCScope release
This commit is contained in:
commit
9dc7289652
40 files changed
+6688
No files matched your search
@@ -0,0 +1,104 @@
|
||||
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()
|
||||
|
||||
Reference in new issue
Block a user