# ============================================================================ # cve2026_82078_suricata.rules — Xpectra Security Research (2026-09-11) # Wire-layer detection for the PaperCut NG chain # CVE-2026-81578 (Tapestry complex-direct auth bypass, un-authed ConfigEditor) # -> CVE-2026-82078 (unsafe JDBC driver class loading in external user # lookup: user-lookup.db-driver / db-url / id-to-username-sql in # tbl_config; Derby SYSCS export drops a .class on the classpath, the # class is then loaded as the "driver"). # Affects PaperCut NG/MF before 24.1.10 / 25.0.12-PO-4560 (EPR) / 25.0.13 (MR) # / 26.0.5. Lab arms: vulnerable 25.0.11.75758; fixed EPR3 25.0.12-PO-4560.76533 # and patched 25.0.13.76605 (both bounce forged direct-service POSTs with # HTTP 302 -> Location: /app?service=page/Home via ValidatingDirectService). # # ATTEMPT vs EXECUTION LABELING (phase-06 doctrine, measured in this lab): # The FIX is authz-side: the forged request still ARRIVES on patched arms — # phase05 run-08/run-06 pcaps hold the byte-identical forged POST, answered # 302. Therefore every request-content rule below is labelled `attempt`: # it fires identically on vulnerable and patched hosts and proves only that # the bytes arrived. Execution is proven by (a) the 200-response pairing # rule sid 202682047 (bypass actually accepted on THIS host), (b) the # stage rules 202682042/3/8 that require the post-bypass legs, and (c) the # connect-back rule sid 202682045, which only code that RAN can produce. # Host-side proof stays with the Sigma/YARA layer (canary file, server.log # DatabaseUtils line, pc-app -> /bin/sh -i process lineage). # # WHAT WE MEASURED AND THEREFORE DO NOT USE (honest rule-design notes): # * NO "class load failed" text exists on the success path — the working # chain logs `DatabaseUtils - Database error looking up cardID: ...` # (an EXPECTED side-effect of the two-phase trigger), never a load error. # Rules must not key on load-failure strings (blueprint A3/A4 do NOT # fire by design on successful runs). # * The victim's memory Derby DB is created at the server's CWD; nothing # on the wire distinguishes success from the 302-bounce for the FIRST # forged POST — hence the flowbits request/response pairing below. # # Tuning: $PCUT_PORTS / $PCUT_SERVERS / $RSH_PORTS are declared in # detection/suricata-lab.yaml vars: sections — this Suricata 8.0.6 build # rejects in-rule-file `var` declarations (measured, case 002). Production # sets $EXTERNAL_NET=!$HOME_NET; our sensor sits on the lab bridge with the # attacker inside the mirrored /24, so the lab yaml pins EXTERNAL_NET:any # (same posture note as case 002). # Tested on: Suricata 8.0.6 RELEASE; loads clean under -T (see # evidence/rule-fires/suricata-*-fire.txt for the live-fire transcripts). # ============================================================================ # --------------------------------------------------------------------------- # 1. SID 202682041 — ATTEMPT (fires on PATCHED too — proven vs phase05 negctl # pcaps). Forged Tapestry complex-direct POST to Home/ConfigEditor's # quickFind whose $TextField names a user-lookup.* config key. This is # step 3/11 of the chain (CVE-2026-81578 bypass leg, first sink-facing # write). MEASURED raw bytes (phase04/05 pcaps): the client form-encodes # '$' -> body carries "%24TextField=user-lookup.db-driver&doQuickFind=Go"; # the content below keys on the encoding-agnostic substring # "TextField=user-lookup." so it catches raw-$ and %24 senders alike. # fp note: an AUTHENTICATED admin using the real ConfigEditor page posts to # a different (rewind) URL shape, never service=direct/ from an # unauthenticated session; still labelled attempt: identical bytes on a # 25.0.13 arm are inert. # --------------------------------------------------------------------------- alert http $EXTERNAL_NET any -> $HOME_NET $PCUT_PORTS ( \ msg:"CVE-2026-82078 PaperCut attempt - forged service=direct ConfigEditor quickFind POST naming user-lookup config key (attempt: fires on patched arms too)"; \ flow:to_server,established; \ content:"POST"; http_method; \ content:"service=direct/"; http_uri; \ content:"Home/ConfigEditor"; http_uri; \ content:"TextField=user-lookup."; http_client_body; \ flowbits:set,pcut_82078_forged_direct; \ classtype:attempted-admin; \ sid:202682041; rev:1; \ metadata:cve 2026_82078 2026_81578, stage attempt, severity high, confidence high, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 2. SID 202682042 — STAGE (post-bypass): forged ConfigEditor $Form WRITE # setting the driver / JDBC-URL / SYSCS export SQL. Raw wire bytes are # form-urlencoded; matched strings contain no percent-escapes. On fixed # arms these POSTs NEVER ARRIVE (authz dies first at ...041), so a 042 hit # means the 81578 leg was ACCEPTED on this host — but it is still request # content, so it stays attempt/stage, not execution. # --------------------------------------------------------------------------- alert http $EXTERNAL_NET any -> $HOME_NET $PCUT_PORTS ( \ msg:"CVE-2026-82078 PaperCut stage - forged direct ConfigEditor $Form write setting user-lookup driver/JDBC value (bypass accepted)"; \ flow:to_server,established; \ content:"POST"; http_method; \ content:"service=direct/"; http_uri; \ content:"Home/ConfigEditor"; http_uri; \ content:"$Form"; http_uri; \ content:"Submit=Update"; http_client_body; \ flowbits:set,pcut_82078_forged_direct; \ classtype:web-application-attack; \ sid:202682042; rev:1; \ metadata:cve 2026_82078 2026_81578, stage stage-post-bypass, severity high, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 3. SID 202682043 — STAGE: SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE # arriving as the id-to-username-sql value (Derby stored procedure that # drops the attacker's hex BLOB onto disk: tmp/.csv + lib/.class). # Blueprint anchor A8/A9; msf-module analogue at papercut_ng_external_user_ # lookup_rce.rb:264. Same labeling logic as 202682042. # --------------------------------------------------------------------------- alert http $EXTERNAL_NET any -> $HOME_NET $PCUT_PORTS ( \ msg:"CVE-2026-82078 PaperCut stage - SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE config write (class-file drop attempt)"; \ flow:to_server,established; \ content:"POST"; http_method; \ content:"service=direct/"; http_uri; \ content:"SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE"; http_client_body; \ classtype:web-application-attack; \ sid:202682043; rev:1; \ metadata:cve 2026_82078, stage stage-post-bypass, severity high, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 4. SID 202682044 — STAGE (the second trigger lookup): hex-BLOB card value. # Raw wire: %24TextField%3DVALUES+CAST%28X%27CAFEBABE... (CAFEBABE = Java # classfile magic inside a SQL X'..' literal — absurd shape, near-zero FP; # blueprint anchor A9). Matches the UserList/$QuickFind.$Form POST; on the # msf-family attacker the class bytes differ but VALUES CAST(X' is stable. # --------------------------------------------------------------------------- alert http $EXTERNAL_NET any -> $HOME_NET $PCUT_PORTS ( \ msg:"CVE-2026-82078 PaperCut stage - quickFind card value is a VALUES CAST(X' hex-BLOB class payload (A9)"; \ flow:to_server,established; \ content:"POST"; http_method; \ content:"service=direct/"; http_uri; \ content:"VALUES+CAST%28X%27"; http_client_body; \ classtype:web-application-attack; \ sid:202682044; rev:1; \ metadata:cve 2026_82078, stage stage-post-bypass, severity high, confidence high, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 5. SID 202682046 — RESPONSE-SIDE: forged direct POST (flowbit from # 202682041/42) answered 302 with Location: /app?service=page/Home. This is # the ValidatingDirectService bounce of the FIX (phase05: byte-identical # spec override on EPR3 + patched). An operator signal: "someone tried the # 81578 bypass on a PATCHED host" — attempt, blocked. # --------------------------------------------------------------------------- alert http $HOME_NET $PCUT_PORTS -> $EXTERNAL_NET any ( \ msg:"CVE-2026-82078 PaperCut BLOCKED - forged direct POST answered 302 page-bounce (ValidatingDirectService: patched host, bypass refused)"; \ flow:to_client,established; \ flowbits:isset,pcut_82078_forged_direct; \ content:"302"; http_stat_code; \ content:"Location: /app?service=page/Home"; http_header; \ threshold:type limit, count 1, seconds 60, track by_src_ip; \ classtype:attempted-admin; \ sid:202682046; rev:1; \ metadata:cve 2026_81578 2026_82078, stage attempt-blocked, severity medium, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 6. SID 202682047 — RESPONSE-SIDE: forged direct POST answered 200. On these # builds a 200 to service=direct means the listener ran WITHOUT a session: # the 81578 auth bypass SUCCEEDED on this host (measured: stock answers 200 # + Page: Home body; fixed arms answer 302). Strongest wire-side statement # possible from the REQUEST/RESPONSE pair; still not proof the payload # class executed — that is 202682045 + the host layer. # --------------------------------------------------------------------------- alert http $HOME_NET $PCUT_PORTS -> $EXTERNAL_NET any ( \ msg:"CVE-2026-82078 PaperCut BYPASS ACCEPTED - forged direct POST answered 200 (un-authed listener executed - vulnerable build)"; \ flow:to_client,established; \ flowbits:isset,pcut_82078_forged_direct; \ content:"200"; http_stat_code; \ threshold:type limit, count 1, seconds 60, track by_src_ip; \ classtype:successful-admin; \ priority:1; \ sid:202682047; rev:1; \ metadata:cve 2026_81578 2026_82078, stage bypass-accepted, severity critical, created 09_11_2026, author Xpectra_Security_Research; \ ) # --------------------------------------------------------------------------- # 7. SID 202682045 — EXECUTION-LEVEL wire rule (scoped, honest limits below): # SYN from the PaperCut server toward a shell-listener port. A JVM that # merely RECEIVED the bytes can never produce this; only code that LOADED # and RAN opens the socket (blueprint A11). Lab measurement: fresh # --reverse-shell fire produced 172.30.77.20 -> 172.30.77.1:4444 SYN inside # the attack window; dry-run stock pcaps and BOTH fixed-arm negctl pcaps # contain zero 4444 traffic (negative controls in evidence/rule-fires/). # HONEST LIMITS: (a) covers connect-back payload variants only — the # canary-file dry-run variant leaves NO wire trace (documented, that is # why the Sigma host rules exist); (b) port list is tunable litter, any # egress host using these ports alerts; correlate src with $PCUT_SERVERS. # --------------------------------------------------------------------------- alert tcp $PCUT_SERVERS any -> $EXTERNAL_NET $RSH_PORTS ( \ msg:"CVE-2026-82078 PaperCut EXECUTION - outbound SYN from PaperCut server to shell-listener port (connect-back, loaded payload)"; \ flags:S; \ reference:url,github.com/rapid7/metasploit-framework; \ classtype:trojan-activity; \ priority:1; \ sid:202682045; rev:1; \ metadata:cve 2026_82078, stage execution, severity critical, scope reverse-shell-variant-only, created 09_11_2026, author Xpectra_Security_Research; \ )