86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
"""Regression checks for independent review findings; synthetic evidence only."""
|
|||
|
|
|
||
|
|
import base64
|
||
|
|
import csv
|
||
|
|
import tempfile
|
||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from netscaler_triage.engine import scan
|
||
|
|
from netscaler_triage.matching import Matcher
|
||
|
|
|
||
|
|
|
||
|
|
class ReviewTests(unittest.TestCase):
|
||
|
|
def test_ipv4_with_port_and_embedded_address_boundary(self) -> None:
|
||
|
|
matcher = Matcher(
|
||
|
|
[{"indicators": [{"type": "ipv4", "value": "192.0.2.1"}]}],
|
||
|
|
defaults=False,
|
||
|
|
)
|
||
|
|
self.assertTrue(matcher.search("http://192.0.2.1:443/a")[1])
|
||
|
|
self.assertFalse(matcher.search("192.0.2.10:443")[1])
|
||
|
|
self.assertFalse(matcher.search("1192.0.2.1")[1])
|
||
|
|
|
||
|
|
def test_base64_query_without_percent_encoding(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as temporary:
|
||
|
|
root = Path(temporary).resolve()
|
||
|
|
source = root / "logs"
|
||
|
|
source.mkdir()
|
||
|
|
(source / "access.log").write_text(
|
||
|
|
"192.0.2.1 - - [01/Oct/2026:12:00:00 +0000] "
|
||
|
|
'"GET /test?x=aWQ= HTTP/1.1" 403 0 "-" "ordinary"\n',
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
result = scan(
|
||
|
|
[("appliance", source)],
|
||
|
|
root / "out",
|
||
|
|
[{"patterns": [{"id": "decoded_id", "pattern": "^id$"}]}],
|
||
|
|
defaults=False,
|
||
|
|
)
|
||
|
|
self.assertEqual(result["counts"].get("findings", 0), 1)
|
||
|
|
|
||
|
|
def test_authorized_status_survives_ioc_export(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as temporary:
|
||
|
|
root = Path(temporary).resolve()
|
||
|
|
source = root / "logs"
|
||
|
|
source.mkdir()
|
||
|
|
(source / "access.log").write_text(
|
||
|
|
"192.0.2.1 - - [01/Oct/2026:12:00:00 +0000] "
|
||
|
|
'"GET /test HTTP/1.1" 403 0 "-" "ordinary"\n',
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
scan(
|
||
|
|
[("appliance", source)],
|
||
|
|
root / "out",
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"indicators": [
|
||
|
|
{"type": "ipv4", "value": "192.0.2.1", "status": "authorized_test"}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
],
|
||
|
|
defaults=False,
|
||
|
|
)
|
||
|
|
with (root / "out/IOCS.csv").open(encoding="utf-8", newline="") as handle:
|
||
|
|
rows = list(csv.DictReader(handle))
|
||
|
|
match = next(row for row in rows if row["value"] == "192.0.2.1")
|
||
|
|
self.assertIn("authorized_test", match["status"])
|
||
|
|
|
||
|
|
def test_incomplete_decoding_marks_partial_analysis(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as temporary:
|
||
|
|
root = Path(temporary).resolve()
|
||
|
|
source = root / "logs"
|
||
|
|
source.mkdir()
|
||
|
|
payload = "x:" + base64.b64encode(b"hostname " * 50).decode() + "%20"
|
||
|
|
(source / "payload.log").write_text(payload + "\n", encoding="utf-8")
|
||
|
|
result = scan(
|
||
|
|
[("appliance", source)],
|
||
|
|
root / "out",
|
||
|
|
[{"patterns": [{"id": "candidate", "pattern": "^x:"}]}],
|
||
|
|
defaults=False,
|
||
|
|
max_line_bytes=1024,
|
||
|
|
)
|
||
|
|
self.assertEqual(result["status"], "partial")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|