"""Boundary semantics for the bundled public reference Sigma groups.""" import unittest from importlib.resources import files from typing import ClassVar from urllib.parse import urlsplit from netscaler_triage.sigma_rules import SigmaMatcher class PublicBoundaryTests(unittest.TestCase): matcher: ClassVar[SigmaMatcher] @classmethod def setUpClass(cls) -> None: content = files("netscaler_triage").joinpath("data", "public_sigma.yml").read_text() cls.matcher = SigmaMatcher.from_yaml(content) def matched(self, kind: str, value: str, decoded: bool = False) -> bool: hits = self.matcher.search_with_details( {"raw": "opaque" if decoded else value}, [value] if decoded else [] ) return any(hit["name"] == "public_reference_" + kind for hit in hits) def test_catalog_positive_raw_and_decoded_per_group(self) -> None: for row in self.matcher.indicators: kind, value = str(row["type"]), str(row["value"]) if kind not in {"domain", "path", "url", "sha256"}: continue if value == "/var/python/bin/customsnmpd": continue # Legitimate binary intentionally omitted from standalone selectors. with self.subTest(kind=kind, value=value): self.assertTrue(self.matched(kind, value)) self.assertTrue(self.matched(kind, value, decoded=True)) def test_domains_accept_subdomains_not_lookalike_suffixes(self) -> None: domain = next(str(r["value"]) for r in self.matcher.indicators if r["type"] == "domain") self.assertTrue(self.matched("domain", "sub." + domain.upper())) for value in ("evil" + domain, domain + ".invalid", domain + "-suffix"): self.assertFalse(self.matched("domain", value)) def test_path_boundaries_case_and_url_embedding(self) -> None: value = "/tmp/lula" self.assertTrue(self.matched("path", value)) self.assertTrue(self.matched("path", "https://example.invalid" + value)) for other in (value + ".backup", value + "-longer", "/other" + value, value.upper()): self.assertFalse(self.matched("path", other)) self.assertTrue(self.matched("path", "/directory/LoginIcon.sig")) self.assertFalse(self.matched("path", "/directory/LoginIcon.sig.backup")) self.assertFalse(self.matched("path", "/directory/loginicon.sig")) def test_urls_preserve_path_case_and_reject_extension(self) -> None: value = next(str(r["value"]) for r in self.matcher.indicators if r["type"] == "url") parsed = urlsplit(value) authority = parsed.scheme + "://" + parsed.netloc self.assertTrue(self.matched("url", authority.upper() + value[len(authority) :])) for suffix in ("-longer", ".backup", "/extra"): self.assertFalse(self.matched("url", value + suffix)) if any(char.isalpha() for char in parsed.path): self.assertFalse(self.matched("url", authority + value[len(authority) :].upper())) def test_hashes_require_token_boundaries(self) -> None: value = next(str(r["value"]) for r in self.matcher.indicators if r["type"] == "sha256") self.assertTrue(self.matched("sha256", value.upper())) self.assertFalse(self.matched("sha256", "f" + value)) self.assertFalse(self.matched("sha256", value + "z")) class PublicLongLineTests(unittest.TestCase): def test_encoded_loader_bounds_long_negative_and_positive_lines(self) -> None: content = files("netscaler_triage").joinpath("data", "public_sigma.yml").read_text() matcher = SigmaMatcher.from_yaml(content) loader = next( rule for rule in matcher.rules if rule.metadata["name"] == "sygnia-encoded-loader" ) long_encoded = "QUJD" * 200000 self.assertFalse(loader.predicate({"raw": long_encoded})) self.assertTrue(loader.predicate({"raw": long_encoded + " NSPPE base64 httpaccess"})) self.assertTrue(loader.predicate({"raw": "first line\nNSPPE base64 httpaccess"})) self.assertFalse(loader.predicate({"raw": "NSPPE\nbase64 httpaccess"})) def test_crash_lookahead_long_negatives_and_line_semantics(self) -> None: content = files("netscaler_triage").joinpath("data", "public_sigma.yml").read_text() matcher = SigmaMatcher.from_yaml(content) crash = next( rule for rule in matcher.rules if rule.metadata["name"] == "elastic-inspired-crash-shell" ) for text in ("QUJD" * 200000, "pitboss; " + "packet " * 100000): self.assertFalse(crash.predicate({"raw": text})) self.assertTrue(crash.predicate({"raw": "QUJD" * 200000 + " pitboss NSPPE;"})) self.assertTrue(crash.predicate({"raw": "first\npitboss packet engine;"})) self.assertFalse(crash.predicate({"raw": "pitboss packet\nengine;"})) self.assertFalse(crash.predicate({"raw": "pitboss engine packet;"}))