Expand offline review checks and fix relative output finalization

This commit is contained in:
StasonJatham committed 2026-10-05 16:18:18 +02:00
1 parent 83f53adb29
commit b4fd5fb6ff
12 files changed
+571 -6

No files matched your search

+17 -1
View File
@@ -32,5 +32,21 @@ Prüfkontext, nicht automatisch eine veröffentlichte kampagnenspezifische IOC.
Die neuen Regeln ersetzen keine vollständige Datenflussanalyse einer Webshell.
404 und fehlende temporäre Dateien sind keine sicheren Misserfolgsbeweise.
149 automatisierte Tests bestanden. Dies bestätigt die dokumentierte Funktion,
152 automatisierte Tests bestanden. Dies bestätigt die dokumentierte Funktion,
keine universelle Erkennungsrate oder vollständige Unversehrtheit.
Ergänzung: Zehn weitere Sigma-Prüfungen und elf Inhaltsprüfungen im
Dateisystemmodus ergänzen die bisherige Abdeckung. Neu geprüft werden unter
anderem kommentierte Apache-Schutzdirektiven, Startup-Python, Core-Löschbefehle,
ungewöhnliche ACS-/WS-Federation-Felder sowie erfasste Prozess-/Open-File-Listen.
PHP/XHTML in Webverzeichnissen und SIG/DEB-Kandidaten werden als Namenshinweis
markiert. Bekannte admin_ui-Verzeichnisse und PHP unter gui/websocketd sind von
der pauschalen Namensprüfung ausgenommen, nicht von Inhalts- oder Hashprüfungen.
BLX-Konfiguration /etc/blx/nshttpd.conf wird ebenfalls erfasst.
Byte-Anomalien werden in Originalbytes geprüft; UTF-8-Ersatzzeichen in Logs
sind nur ein zusätzlicher Suchhinweis. HTTP-Fehlermuster gelten im Dateisystemmodus
nur für httperror-Dateien, nicht für Binärpakete mit ähnlichen Textfragmenten.
Private Suchindikatoren bleiben separat. Die Existenz eines Indikators in einer
zugelieferten Liste ist kein unabhängig bestätigter Malware- oder C2-Nachweis.
+33
View File
@@ -302,3 +302,36 @@ der oben beschriebenen CLI-Berichtsstruktur.
Der Programmcode steht unter der [MIT-Lizenz](LICENSE.txt). Für Abhängigkeiten und
zitierte Materialien gelten deren eigene Lizenzen. Quellenangaben bedeuten keine
Zugehörigkeit zu Citrix oder den genannten Forschenden.
## Ergänzende Prüfungen und ihre Grenzen
Die zusätzlichen Sigma-Regeln prüfen ungewöhnliche ACS-/WS-Federation-Felder,
Core-Datei-Löschbefehle, PHP-Handler, kommentierte Schutzdirektiven und auffällige
Webartefakt-Antworten. Sie gelten auch für sicher dekodierte Textansichten.
Erfasste Prozess- und Open-File-Listen können nach Prozessen unter Webkonten und
NSPPE-Dateireferenzen durchsucht werden. Das Werkzeug erhebt diese Listen nicht
live: Ohne entsprechende Sicherung ist dieser Zustand nicht prüfbar.
Im Modus `filesystem` kommen Pfadprüfungen für PHP-/XHTML-Dateien in Webverzeichnissen,
SIG-/DEB-Kandidaten, Startup-Python und BLX-Konfiguration hinzu. Die bestehenden
Hash- und SUID/SGID-Prüfungen bleiben aktiv. Ursprüngliche Rechte und Eigentümer
sind nur mit verlässlichen Archivmetadaten bewertbar; lokale Kopien können diese
verändern. Logregeln werden im Dateisystemmodus auf passende Logpfade begrenzt,
damit Zeichenketten in Herstellerpaketen nicht als Logereignisse erscheinen.
Alle ergänzenden Verhaltensprüfungen liefern Prüfkandidaten. Ein HTTP-404, ein
PHP-Dateiname, eine auskommentierte Direktive oder nicht-ASCII-Text ist allein kein
Angriffsnachweis. Mit einem vertrauenswürdigen Stand desselben Builds vergleichen.
Fehlende Logs, Prozesslisten oder Dateien bedeuten fehlende Prüfbarkeit.
Zusätzliche private IOCs als CSV mit `type,value,status,description` laden:
```sh
netscaler-triage ./logs --ioc-file ./private/indicators.csv --output ./new-log-review
netscaler-triage filesystem ./export-root --ioc-file ./private/indicators.csv --output ./new-file-review
```
Nicht unabhängig bestätigte Indikatoren als `reference_only` kennzeichnen.
Sie werden gesucht, aber nicht automatisch als bösartig bewertet oder als
öffentliche Threat Intelligence weitergegeben.
+41
View File
@@ -188,3 +188,44 @@ Die Erweiterung verarbeitet Archive ohne Ausführung enthaltener Programme.
Unvollständige gzip-Streams werden als Verarbeitungsfehler ausgewiesen.
Zusätzliche strukturierte Logkorrelation ist optional; der Batch-Helfer aktiviert sie nicht.
Erkennungsgrenzen: FILESYSTEM_COVERAGE.txt. Validierung ist keine Clean-Garantie.
SUPPLEMENTAL REVIEW CHECKS — 2026-10-05
Version 1.0.2 adds ten low-severity Sigma review rules and eleven filesystem
content checks, plus candidate web-tree file names and BLX configuration paths.
The default Sigma catalog validates with 57 rules; public indicators remain
246. Unverified additional indicators are kept in an optional private pack.
All 151 tests passed with warnings treated as errors. Ruff lint, formatting and
strict mypy passed. Positive and negative controls exercise each new Sigma rule
on raw and decoded views. Filesystem tests cover original anomalous bytes,
configuration path restrictions and suppression of an error-message lookalike
inside a binary package. Eleven filesystem tests passed after increasing the
stream overlap to 8 KiB for bounded patterns spanning a block boundary.
An isolated installed package also validated all 57 rules. All 25 supplemental
private indicator values matched raw and Base64-decoded text in a separate
private check. This demonstrates matching behavior, not malicious attribution.
Two existing configuration/program exports were rescanned: 5,361 and 5,319
regular files, with no read errors and no hits from the added checks. Existing
heuristic candidates remain separate from newly added checks. A retained larger
snapshot inventory was compared against all 12 supplemental SHA-256 values:
53,696 file hashes, no match. This inventory comparison did not reread the
original larger archive and does not prove current system integrity.
RELATIVE OUTPUT REGRESSION
A real-data run completed analysis but failed while finalizing a relative output
path. Preparation resolves paths internally; the CLI now resolves its preparation
root before constructing relative provenance references. A regression verifies
SUMMARY.txt and each manifest input root when --output is relative. All 152 tests
then passed with warnings treated as errors; Ruff and strict mypy passed.
The completed run's output was recovered only after every one of its 851 input
inventory entries matched a freshly prepared copy. It processed 1,117,532 records:
two missing-file review events, no supplemental IOC or file-hash hit. Status
remains partial: ten binary-file read stops and 106,842 decoding failures, with
no matching or inventory errors. These counts are not compromise indicators.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "netscaler-triage"
version = "1.0.1"
version = "1.0.2"
description = "Offline NetScaler evidence analysis with inspectable decoding and provenance"
readme = "README.md"
license = "MIT"
+1 -1
View File
@@ -1,3 +1,3 @@
"""Offline analysis; no discovered payload is ever executed."""
__version__ = "1.0.1"
__version__ = "1.0.2"
+1 -1
View File
@@ -369,7 +369,7 @@ def main(argv: list[str] | None = None) -> int:
if args.output.exists():
raise FileExistsError("Output already exists")
args.output.parent.mkdir(parents=True, exist_ok=True)
prepared = fresh_destination(args.output.parent, ".triage-preparation")
prepared = fresh_destination(args.output.parent, ".triage-preparation").resolve()
inputs, preparation_manifest = prepare_inputs(
[path for _, path in inputs],
prepared,
@@ -97,6 +97,118 @@
"required_any_literal": [
"nsconfig"
]
},
{
"id": "httpd_commented_security_directive",
"regex": "^[ \\t]*#[ \\t]*(?:Require[ \\t]+all[ \\t]+denied|php_flag[ \\t]+engine[ \\t]+off)\\b",
"description": "An Apache restriction is commented out. Compare with a trusted configuration of the same build; comments can be vendor defaults.",
"source": "",
"required_any_literal": [
"#"
],
"path_regex": "(?:^|/)(?:httpd|nshttpd)\\.conf$"
},
{
"id": "httpd_php_handler_review",
"regex": "^[ \\t]*AddHandler[ \\t]+application/x-httpd-php\\b",
"description": "PHP handler declaration. Check the mapped extensions and vendor baseline; a handler alone is not malicious.",
"source": "",
"required_any_literal": [
"addhandler"
],
"path_regex": "(?:^|/)(?:httpd|nshttpd)\\.conf$"
},
{
"id": "startup_python_reference",
"regex": "(?:/flash)?/nsconfig/rc\\.netscaler[^\\r\\n]{0,300}\\bpython(?:[23](?:\\.\\d+)?)?\\b",
"description": "Python is referenced alongside the startup configuration path. Verify actual file contents and administrative intent.",
"source": "",
"required_any_literal": [
"rc.netscaler"
]
},
{
"id": "core_file_removal",
"regex": "\\brm[ \\t]+(?:-[a-z]+[ \\t]+){0,3}[\\x22\\x27]?/var/core/\\*",
"description": "A command references removal of core files. This can be maintenance or evidence deletion; a logged command alone does not prove execution.",
"source": "",
"required_any_literal": [
"/var/core/"
],
"path_regex": "(?:^|/)(?:notice|sh|bash)\\.log(?:\\.|$)"
},
{
"id": "wsfed_request_id_anomaly",
"regex": "WSFed:[ \\t]*request_id[^\\r\\n]{0,2048}(?:[\\x80-\\xff\\xfe]|\\^[^\\r\\n]{0,256}\\^)",
"description": "WS-Federation request ID contains unusual characters. Encoding, corruption and malformed input are alternatives; this does not prove memory disclosure.",
"source": "",
"required_any_literal": [
"wsfed:"
],
"path_regex": "(?:^|/)ns\\.log(?:\\.|$)"
},
{
"id": "acs_control_marker",
"regex": "acs=<[^\\r\\n]{0,2048}\\xf0\\^M[^\\r\\n]{0,2048}>,[ \\t]*for",
"description": "ACS field contains an unusual byte/control representation. Inspect original bytes and authentication context; not proof of memory disclosure.",
"source": "",
"required_any_literal": [
"acs=<"
],
"path_regex": "(?:^|/)ns\\.log(?:\\.|$)"
},
{
"id": "web_artifact_response_review",
"regex": "(?:nsginstaller\\.deb|nsgclient[^\\s\\x22?]*|[0-9a-f]{6}\\.ico)[^\\r\\n\\x22]{0,512}HTTP/1\\.[01]\\x22[ \\t]+(?:404|202)[ \\t]+[0-9]{4}\\b",
"description": "A candidate artifact returned 202 or 404 with a four-digit response size. Status and size do not establish a web shell; inspect response content and filesystem evidence.",
"source": "",
"required_any_literal": [
"nsginstaller",
"nsgclient",
".ico"
],
"path_regex": "(?:^|/)httpaccess[^/]*$"
},
{
"id": "web_artifact_error_review",
"regex": "(?:File does not exist|not found|\\[error\\])[^\\r\\n]{0,1024}(?:nsginstaller\\.deb|nsgclient|[0-9a-f]{6}/\\.sig)",
"description": "Web server error mentions a candidate artifact. A missing-file error usually supports absence at that moment, not successful installation.",
"source": "",
"required_any_literal": [
"nsginstaller",
"nsgclient",
"/.sig"
],
"path_regex": "(?:^|/)httperror[^/]*$"
},
{
"id": "packet_engine_open_file_review",
"regex": "^NSPPE\\S*[ \\t]+\\d+[^\\r\\n]{0,1024}(?:/var/netscaler/|callhome)",
"description": "A captured open-file listing associates NSPPE with a candidate path. Inspect file type and process context; legitimate open files can match.",
"source": "",
"required_any_literal": [
"nsppe"
]
},
{
"id": "web_user_process_review",
"regex": "^(?:nobody|daemon)[ \\t]+\\d+[ \\t]+(?!(?:[^\\r\\n]*)(?:/bin/httpd|/usr/sbin/nshttpd)\\b)[^\\r\\n]{1,2048}$",
"description": "A captured process listing contains a non-httpd process under a web-service account. Validate the executable and parent; legitimate helper processes can match.",
"source": "",
"required_any_literal": [
"nobody",
"daemon"
]
},
{
"id": "startup_python_content",
"regex": "^[ \\t]*(?!#)[^\\r\\n]{0,512}\\bpython(?:[23](?:\\.\\d+)?)?\\b",
"path_regex": "(?:^|/)(?:flash/)?nsconfig/rc\\.netscaler$",
"description": "Python in startup configuration; compare with authorized configuration.",
"source": "",
"required_any_literal": [
"python"
]
}
]
}
+203
View File
@@ -13162,3 +13162,206 @@ indicators:
A match does not establish compromise.
matching: exact
context_only: false
---
title: NetScaler Httpd Commented Security Directive
id: 0da9523e-6cf7-560d-b6ba-11169fbb53bc
name: httpd_commented_security_directive
status: experimental
description: An Apache restriction is commented out. Compare with a trusted configuration
of the same build; comments can be vendor defaults.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?im)^[ \t]*#[ \t]*(?:Require[ \t]+all[ \t]+denied|php_flag[ \t]+engine[
\t]+off)\b
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Httpd Php Handler Review
id: 060a060d-71cf-5632-aaab-61cf04128ab2
name: httpd_php_handler_review
status: experimental
description: PHP handler declaration. Check the mapped extensions and vendor baseline;
a handler alone is not malicious.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?im)^[ \t]*AddHandler[ \t]+application/x-httpd-php\b
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Startup Python Reference
id: 1b9bc1d2-914e-5ca0-8989-911f0860e0ec
name: startup_python_reference
status: experimental
description: Python is referenced alongside the startup configuration path. Verify
actual file contents and administrative intent.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)(?:/flash)?/nsconfig/rc\.netscaler[^\r\n]{0,300}\bpython(?:[23](?:\.\d+)?)?\b
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Core File Removal
id: 6affc1b7-cc98-5d60-b822-5ca306a0f5ac
name: core_file_removal
status: experimental
description: A command references removal of core files. This can be maintenance or
evidence deletion; a logged command alone does not prove execution.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)\brm[ \t]+(?:-[a-z]+[ \t]+){0,3}[\x22\x27]?/var/core/\*
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Wsfed Request Id Anomaly
id: 04997de6-7489-5045-9d89-ca58c6a6cda2
name: wsfed_request_id_anomaly
status: experimental
description: WS-Federation request ID contains unusual characters. Encoding, corruption
and malformed input are alternatives; this does not prove memory disclosure.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)WSFed:[ \t]*request_id[^\r\n]{0,2048}(?:[\x80-\xff\ufffd]|\^[^\r\n]{0,256}\^)
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Acs Control Marker
id: 1fd8ae59-7fa2-5996-8fb7-c22a175e4b59
name: acs_control_marker
status: experimental
description: ACS field contains an unusual byte/control representation. Inspect original
bytes and authentication context; not proof of memory disclosure.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)acs=<[^\r\n]{0,2048}(?:\xf0|\ufffd)\^M[^\r\n]{0,2048}>,[ \t]*for
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Web Artifact Response Review
id: 7e7dbc21-2824-5f7d-a230-849fc84eeede
name: web_artifact_response_review
status: experimental
description: A candidate artifact returned 202 or 404 with a four-digit response size.
Status and size do not establish a web shell; inspect response content and filesystem
evidence.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)(?:nsginstaller\.deb|nsgclient[^\s\x22?]*|[0-9a-f]{6}\.ico)[^\r\n\x22]{0,512}HTTP/1\.[01]\x22[
\t]+(?:404|202)[ \t]+[0-9]{4}\b
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Web Artifact Error Review
id: 2a24ad72-0b49-5717-98cf-400d0b0e164c
name: web_artifact_error_review
status: experimental
description: Web server error mentions a candidate artifact. A missing-file error
usually supports absence at that moment, not successful installation.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?i)(?:File does not exist|not found|\[error\])[^\r\n]{0,1024}(?:nsginstaller\.deb|nsgclient|[0-9a-f]{6}/\.sig)
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Packet Engine Open File Review
id: 89dd38c0-6b27-5931-ac71-fa8be5da8e61
name: packet_engine_open_file_review
status: experimental
description: A captured open-file listing associates NSPPE with a candidate path.
Inspect file type and process context; legitimate open files can match.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?im)^NSPPE\S*[ \t]+\d+[^\r\n]{0,1024}(?:/var/netscaler/|callhome)
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
---
title: NetScaler Web User Process Review
id: 2843041d-c85b-59e1-a8c5-b60fd87233d0
name: web_user_process_review
status: experimental
description: A captured process listing contains a non-httpd process under a web-service
account. Validate the executable and parent; legitimate helper processes can match.
author: NetScaler Triage contributors
logsource:
product: netscaler
detection:
selection:
raw|re: (?im)^(?:nobody|daemon)[ \t]+\d+[ \t]+(?!(?:[^\r\n]*)(?:/bin/httpd|/usr/sbin/nshttpd)\b)[^\r\n]{1,2048}$
condition: selection
level: low
falsepositives:
- Legitimate administration, vendor defaults, malformed input or collection artifacts.
indicator_status: behavior_pattern
references: []
+43 -1
View File
@@ -22,7 +22,7 @@ from pathlib import Path, PurePosixPath
from typing import Any, BinaryIO
BLOCK = 1024 * 1024
OVERLAP = 4096
OVERLAP = 8192
RARE = re.compile(
rb"\.ctxs\.receiver|\.local_journal|\.uxd(?:port|lock)|nsmon\.pl|sec_monitor|http_nsc_ldap|http_nsc_clienttype|http_x_ux|uxd_idle_exit|nsc_tass|rhfajaf1h992|7489a0f93c67fa5cdaeb4b921d90594d|update_c08937\.pl|update_result_3567cs\.tgz|id009\.txt|ww009\.php|wbnsisqmfj\.png"
)
@@ -69,6 +69,8 @@ EXACT = {
}
CAPTURE = {
"etc/httpd.conf",
"etc/blx/nshttpd.conf",
"usr/local/bin/blx/nshttpd/conf/httpd.conf",
"nsconfig/httpd.conf",
"flash/nsconfig/httpd.conf",
"nsconfig/crontab",
@@ -264,6 +266,11 @@ def scan_filesystem(
r["id"]: tuple(t.encode() for t in r.get("required_any_literal", []))
for r in rules_doc["rules"]
}
rule_paths = {
r["id"]: re.compile(r["path_regex"], re.I)
for r in rules_doc["rules"]
if r.get("path_regex")
}
known = {x["value"].lower(): x for x in intel["indicators"] if x["type"] == "sha256"}
known_paths = {x["value"].lstrip("/") for x in intel["indicators"] if x["type"] == "path"}
network_values = sorted(
@@ -345,6 +352,8 @@ def scan_filesystem(
if context(self.path) == "log":
patterns.append(("script_or_crash_log", LOG))
for rule, pattern in patterns:
if rule in rule_paths and not rule_paths[rule].search(self.path):
continue
gates = rule_gates.get(rule, ())
if gates and not any(token in lower for token in gates):
continue
@@ -482,6 +491,39 @@ def scan_filesystem(
note="Vendor binaries can legitimately have these bits",
)
if member.isfile():
web_path = path.lower()
web_roots = (
"var/netscaler/logon/",
"var/vpn/",
"netscaler/ns_gui/",
"var/netscaler/gui/",
"netscaler/portal/",
)
excluded = any(
web_path.startswith(prefix + "admin_ui/")
for prefix in ("netscaler/ns_gui/", "var/netscaler/gui/")
) or (
web_path.endswith(".php")
and web_path.startswith("var/netscaler/gui/websocketd/")
)
if (
web_path.startswith(web_roots)
and web_path.endswith((".php", ".xhtml"))
and not excluded
) or (
web_path.startswith("var/netscaler/gui/")
and (
web_path.endswith(".sig")
or PurePosixPath(web_path).name
in {"nsgclient18.deb", "nsginstaller.deb"}
)
):
finding(
path,
"web_tree_file_review",
metadata=row,
note="Candidate name/location only; compare with a vendor baseline",
)
counts["regular_files"] += 1
view = View(path, "raw")
unpacked: View | None = None
+12
View File
@@ -95,6 +95,18 @@ class EasyCliTests(unittest.TestCase):
self.invoke(["scan", str(folder)])
self.assertEqual(len(self.events()), 1)
def test_relative_output_preserves_preparation_manifest(self) -> None:
folder = self.case / "logs"
folder.mkdir()
(folder / "httpaccess.log").write_text(LOG, encoding="utf-8")
self.invoke(["scan", "logs", "--output", "results/new"])
output = self.case / "results/new"
manifest = json.loads((output / "details/preparation_manifest.json").read_text())
self.assertTrue((output / "SUMMARY.txt").is_file())
for root in manifest["roots"]:
self.assertTrue((output / root["path"]).is_dir())
self.assertEqual(len(self.events()), 1)
def test_missing_common_drive_uses_current_output_parent(self) -> None:
folder = self.case / "logs"
folder.mkdir()
+106
View File
@@ -0,0 +1,106 @@
"""Review heuristics must retain context and never turn a candidate into proof."""
import json
import tempfile
import unittest
from pathlib import Path
from typing import ClassVar
from netscaler_triage.filesystem import scan_filesystem
from netscaler_triage.matching import Matcher
class SupplementalChecksTests(unittest.TestCase):
matcher: ClassVar[Matcher]
@classmethod
def setUpClass(cls) -> None:
cls.matcher = Matcher([])
def test_review_rules_raw_decoded_and_negative_controls(self) -> None:
cases = {
"httpd_commented_security_directive": ("# Require all denied", "Require all denied"),
"httpd_php_handler_review": (
"AddHandler application/x-httpd-php .php",
"# AddHandler application/x-httpd-php .php",
),
"startup_python_reference": (
"/flash/nsconfig/rc.netscaler: python3 script.py",
"python3 script.py",
),
"core_file_removal": ("rm -f /var/core/*", "ls /var/core/*"),
"wsfed_request_id_anomaly": ("WSFed: request_id bad^M^M", "WSFed: request_id abc-123"),
"acs_control_marker": (
"acs=<bad\ufffd^M>, for login",
"acs=<https://example.invalid/>, for login",
),
"web_artifact_response_review": (
'GET /vpn/abcdef.ico HTTP/1.1" 404 1234',
'GET /vpn/abcdef.ico HTTP/1.1" 404 123',
),
"web_artifact_error_review": (
"File does not exist: /vpn/abcdef/.sig",
"File does not exist: /favicon.ico",
),
"packet_engine_open_file_review": (
"NSPPE 42 root 3r /var/netscaler/sample",
"other 42 root 3r /var/netscaler/sample",
),
"web_user_process_review": (
"nobody 42 0.0 0.0 /bin/helper",
"nobody 42 0.0 0.0 /bin/httpd -k start",
),
}
for name, (positive, negative) in cases.items():
with self.subTest(name=name):
hits = self.matcher.search_sigma({"raw": positive})
self.assertTrue(any(h["name"] == name and h["level"] == "low" for h in hits))
decoded = self.matcher.search_sigma({"raw": "opaque"}, [positive])
self.assertTrue(
any(h["name"] == name and "decoded:0" in h["basis"] for h in decoded)
)
self.assertFalse(
any(h["name"] == name for h in self.matcher.search_sigma({"raw": negative}))
)
def test_snapshot_paths_original_bytes_and_config_scope(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "export"
entries = {
"etc/blx/nshttpd.conf": (
b"# Require all denied\nAddHandler application/x-httpd-php .php\n"
),
"flash/nsconfig/rc.netscaler": b"python3 /opt/example.py\n",
"var/log/ns.log": b"WSFed: request_id \x81\nacs=<abc\xf0^M>, for login\n",
"var/vpn/test.PHP": b"inert",
"var/netscaler/gui/package.bin": b"not found\x00/opt/Citrix/NSGClient/a",
"var/netscaler/gui/test.sig": b"inert",
"var/netscaler/gui/admin_ui/vendor.php": b"inert",
"var/log/notes.log": b"# Require all denied\n",
}
for name, content in entries.items():
path = source / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
result = scan_filesystem(source, root / "results")
self.assertTrue(result["complete_scan"])
rows = [
json.loads(line)
for line in (root / "results/findings.jsonl").read_text().splitlines()
]
found = {(r["path"], r["rule"]) for r in rows}
for source_path, rule in [
("etc/blx/nshttpd.conf", "httpd_commented_security_directive"),
("flash/nsconfig/rc.netscaler", "startup_python_content"),
("var/log/ns.log", "wsfed_request_id_anomaly"),
("var/log/ns.log", "acs_control_marker"),
("var/vpn/test.PHP", "web_tree_file_review"),
("var/netscaler/gui/test.sig", "web_tree_file_review"),
]:
self.assertIn((source_path, rule), found)
self.assertNotIn(
("var/netscaler/gui/admin_ui/vendor.php", "web_tree_file_review"), found
)
self.assertNotIn(("var/log/notes.log", "httpd_commented_security_directive"), found)
self.assertNotIn(("var/netscaler/gui/package.bin", "web_artifact_error_review"), found)
Generated
+1 -1
View File
@@ -546,7 +546,7 @@ wheels = [
[[package]]
name = "netscaler-triage"
version = "1.0.1"
version = "1.0.2"
source = { editable = "." }
dependencies = [
{ name = "py7zr" },