Malcolm NSM wave (6 GHSAs): the Lua RBAC gate reads bytes nginx never routes on¶
Wave in one line: The Lua RBAC gate reads bytes nginx never routes on, one fall-through location is unauthenticated with a trusted identity header, and the shipped upload server fetches any URL for you
Source: hourly offensive-security scan of GitHub Security Advisories, 2026-10-08 (wave published 2026-10-08T18:32Z, six advisories for the Malcolm network-security-monitoring stack — idapyramid/Malcolm).
Malcolm is a widely deployed open NSM appliance (Arkime + Zeek + OpenSearch + a custom nginx reverse proxy with a Lua RBAC layer). The wave is a compact catalogue of reverse-proxy composition bugs — every layer of the shipped proxy makes a different decision from different input, and each mismatch is an authorization bypass. For an operator, NSM/monitoring appliances are high-value targets: they hold full packet captures, auth interfaces, and management scripts, usually on a flatter network segment than production.
Advisory table¶
| Leg | GHSA | CVE | Sev | Boundary |
|---|---|---|---|---|
| Lua RBAC pattern-matches the raw percent-encoded URI; nginx routes on the decoded, normalized URI | GHSA-rgrh-q687-4gc8 | CVE-2026-107334 | medium | /%68tadmin.php matches no restriction pattern in the gate but nginx still routes it to the restricted location. Affects every RBAC-protected path family: upload, PHP server, htadmin, auth management. |
"Dashboards → Arkime shortcut" location: case-insensitive regex matcher, case-sensitive rewrite; miss falls through to proxy_pass — the one proxied location with no per-location auth include — and X-Forwarded-User passes un-overwritten while Arkime trusts it as the authenticated username | GHSA-c366-pv2g-9685 | CVE-2026-107336 | medium | /IDDASH2ARK/... skips the redirect rewrite, reaches Arkime unauthenticated with a forged, auto-provisioned full-access identity. |
Kiosk Flask app: POST /script_call/<script> with zero authentication + wildcard CORS | GHSA-hfc4-x7vw-3m49 | CVE-2026-107337 | high | Operator-browser CSRF executes arbitrary management commands — control.py --wipe permanently destroys all captured traffic/forensic logs, --stop blinds the monitoring. Also directly reachable if the kiosk port is exposed. |
Ships upstream FilePond PHP server nearly unmodified; index.php fetch route downloads any URL via curl (FOLLOWLOCATION), and the HEAD variant stores the fetched body in the transfer dir and returns the transfer ID | GHSA-6x93-f29r-jwj6 | CVE-2026-107362 | high | Full-readback SSRF from the upload container — internal services, cloud metadata, and (via file://-class scheme support in the route) local content, then retrieved by transfer ID. |
arkime-live runs network_mode: host, port 8005 on all interfaces, Arkime trusts X-Forwarded-User from any IP (userAuthIps=::,0.0.0.0/0), auto-creates users with full access, passwordSecret hardcoded to the public value "Malcolm" | GHSA-9jvx-84p5-48xr | CVE-2026-107361 | medium | A network-adjacent attacker bypasses the entire nginx+RBAC stack by connecting to 8005 directly with a forged identity header. |
Upload pipeline safe-extract.py applies entry-count/depth/byte limits to zip/tar/rar/7z via libarchive but not to single-stream compressed files (.gz/.bz2/.xz/.lzma/.lz) | GHSA-ffq8-c9hx-7wvf | CVE-2026-107335 | medium | Any permitted uploader: small gzip bomb → unbounded decompress onto the shared OpenSearch/Logstash/Arkime/Zeek volume. |
Why this is an operator page¶
- A reverse proxy is a multi-parser system — diff each layer's view of the URI before trusting the gate. Two legs here are the same bug family: the authorization decision consumes one representation of the path (raw encoded, or case-matched by a different function than the rewrite) and routing consumes another (decoded/normalized, case-sensitive rewrite). Probe battery for any nginx-fronted appliance with Lua/auth-by-path logic: percent-encode one character of a restricted path (
%68,%2e%2e,%5c), vary case of every segment, add trailing/.,;params, duplicate slashes — then diff the gate decision (allow/deny response) against the served content (did the restricted app answer?). A 200 from the restricted app while the gate stayed silent is the finding. This is the same parser-differential family as the Oct 8 Coraza ARGS legs and the Brocade Host-header leg: the check's input is not the router's input. - Enumerate per-location auth coverage, not proxy presence. The fall-through leg's root is config hygiene: every proxied location includes an auth file except one, picked because its matcher and its rewrite use different case rules. Recon rule on any multi-location proxy config you can read (appliance GitHub repo = pre-engagement reading): list all
proxy_passblocks and diff which include the auth snippet — the odd one out plus a routing quirk (case, encoding, alias-vs-root) is an unauthenticated path with no exploit needed. X-Forwarded-User-style identity headers are credentials when the backend can't tell proxies from callers. Two independent legs (fall-through proxy + direct-to-8005) both land on forged-identity full access. Audit rule for every Arkime/Dashboards/Kibana-style product fronted by a proxy: which ports answer without the proxy, and does the backend'strustedAuthIp/userAuthIpsrange cover those paths' peers? Decision-table probe from a host on the same segment: (no header, valid user header, garbage user header) × (proxied path, direct backend port). Auto-provisioning-on-first-sight (auto-creates users with full access) turns any forged name into an instant admin session.- Appliances inherit their vendored components' routes. The FilePond leg is the vendor-ship surface: the Dockerfile copies upstream PHP files and the appliance only overwrote two of them. Enumerate upload endpoints' full route family (
?[fetch|process|revert|head]-style query params on the uploader endpoint), not just the one the UI calls. Full-readback-by-transfer-ID turns a blind SSRF into a reading one in a second request — grep any upload/fetch API for "return the ID of what I stored." - Unauthenticated management-script endpoints + wildcard CORS = destruction from a phishing link.
script_call/<script>is the "kiosk convenience" class: a local helper endpoint that assumes its caller is the operator's browser. The wildcard CORS makes drive-by CSRF-to-wipe work from any page the operator visits. Evidence on authorized scope: OPTIONS preflight from an arbitrary origin + one read-only harmless script call shape (enumerate the script allowlist from the app's own source in the repo); never run--wipe/--stopon a live sensor, lab instance only. - Decompression guards are per-format — sweep every accepted container spelling. Limits enforced "when extracting archives" but not "when the file is a single compressed stream" is the guard-coverage shape (same rule as Heimdall's one-controller
SafeUrlFetcher, folded the same day). Upload fuzzing matrix: same payload wrapped as.zip,.tar.gz, and bare.gz/.xz/.lzma— asymmetric resource behavior across wrapper formats is the fingerprint.
Replayable validation boundaries (lab/authorized scope only)¶
- RBAC differentials: authenticated low-privilege lab user, restricted paths are your own lab's htadmin/auth pages; encode/case variants; evidence = gate-vs-route decision table. No admin actions beyond read.
- Fall-through + header forgery: own lab instance,
X-Forwarded-User: canary-operatormarker user; prove session provisioning + read of the Arkime UI index; stop before querying real PCAP indices. - Direct-port leg: connect to 8005 from a second lab host with your own marker identity; the hardcoded
"Malcolm"secret is a config-fingerprint claim from shipped defaults, do not use it against non-lab hosts. - FilePond fetch: own HTTP callback listener + owned metadata-mock, prove fetch and transfer-ID readback of the canary body only.
- Kiosk leg: enumerate script names from source; CSRF proof limited to an OPTIONS/shape test in your own browser against your own kiosk.
- Bomb leg: lab instance, modest ratio (e.g. 1 GB expandable), monitor volume, delete marker after; never fill a shared production sensor volume.
Cross-references¶
- Parser-differential gate-vs-route family: Oct 6/8 Coraza pages, Oct 8 Brocade page (Host-header-as-IP-ACL).
- Identity-header trust + sidecar direct-connect: July 30 credential-relay proxy page, Sept 21 client-supplied-proofs legs.
- Guard-coverage parity: July 30 Heimdall/URL-policy precedent, Sept 18 ignored-scope page.
- Vendored-component route inheritance: June 22 OpenAM legacy-route sweep rule.