Initial release: offline NetScaler triage with Sigma rules and MIT license
This commit is contained in:
commit
711c42b270
52 files changed
+61222
No files matched your search
@@ -0,0 +1,86 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user