Smi1e@Pentes7eam
漏洞信息:https://cwiki.apache.org/confluence/display/WW/S2-045
Struts2默认使用
org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
类对上传数据进行解析。其在处理Content-Type
时如果获得非预期的值的话,将会抛出一个异常,在此异常的处理中会对错误信息进行OGNL表达式解析。
漏洞复现
![322f07540d14e4b0c84827f85ac68d7e.png](https://img-blog.csdnimg.cn/img_convert/322f07540d14e4b0c84827f85ac68d7e.png)
影响范围
Struts 2.3.5 - 2.3.31, Struts 2.5 - 2.5.10
漏洞分析
Struts 2.3.5 - 2.3.31和Struts 2.5 - 2.5.10漏洞入口不同
前者调用栈
![df8decb2c691fb9ad2334ef2e7db96c5.png](https://img-blog.csdnimg.cn/img_convert/df8decb2c691fb9ad2334ef2e7db96c5.png)
在过滤器 org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
的dofilter
方法中下断点,跟进this.prepare.wrapRequest(request);
,它会封装request
对象。
![f58788fb2e5e703ca6e6364f63a1b2a0.png](https://img-blog.csdnimg.cn/img_convert/f58788fb2e5e703ca6e6364f63a1b2a0.png)
其中会判断 content_type
中是否含有字符串multipart/form-data
,如果有则实例化MultiPartRequestWrapper
对象,并传入对应参数来对请求进行解析。其中this.getMultiPartRequest;
会去获取用来处理文件上传请求的解析类,默认是org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
。我们可以通过配置struts.multipart.parser
属性来指定不同的解析类。
![3275db1782da4a60c9ef3f363668a6b6.png](https://img-blog.csdnimg.cn/img_convert/3275db1782da4a60c9ef3f363668a6b6.png)
跟进 this.multi.parse(request, saveDir);
,这里的this.multi
就是默认的JakartaMultiPartRequest
对象。
![99447316a7541bacd971150db9b30f82.png](https://img-blog.csdnimg.cn/img_convert/99447316a7541bacd971150db9b30f82.png)
这里由于对上传请求解析时发现错误所以会进入到下面的 catch
操作中,跟进this.buildErrorMessage
![ce05808bd4c8a3c0bc9662793e9cad27.png](https://img-blog.csdnimg.cn/img_convert/ce05808bd4c8a3c0bc9662793e9cad27.png)
它会调用 LocalizedTextUtil.findText
处理错误信息
![2b7b40f1de2f71019f48ad0541b122da.png](https://img-blog.csdnimg.cn/img_convert/2b7b40f1de2f71019f48ad0541b122da.png)
不断跟进会发现他会把message传入 TextParseUtil.translateVariables
进行表达式解析。
![58cc667a183955c4559741f7bf80345e.png](https://img-blog.csdnimg.cn/img_convert/58cc667a183955c4559741f7bf80345e.png)
后面就还是以前的流程了。
![a45b47e66b4feeb463d598201a4947f3.png](https://img-blog.csdnimg.cn/img_convert/a45b47e66b4feeb463d598201a4947f3.png)
而 Struts 2.5 - 2.5.10中 org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper.buildErrorMessage
并没有调用LocalizedTextUtil.findText
![003311b13e7c2385e323a44965bdc7db.png](https://img-blog.csdnimg.cn/img_convert/003311b13e7c2385e323a44965bdc7db.png)
它是在拦截器 FileUploadInterceptor
中调用的LocalizedTextUtil.findText(error.getClazz, error.getTextKey, ActionContext.getContext.getLocale, error.getDefaultMessage, error.getArgs);
从而对错误信息进行OGNL表达式解析
![0e5e999e852c6b2b09836a53366d28e5.png](https://img-blog.csdnimg.cn/img_convert/0e5e999e852c6b2b09836a53366d28e5.png)
在2.3.30/2.5.2之后的版本,我们可以用来bypass黑名单过滤的 _memberAccess
和DefaultMemberAccess
都进入到了黑名单中。
![2973581616c35a687f6b7efda07d193c.png](https://img-blog.csdnimg.cn/img_convert/2973581616c35a687f6b7efda07d193c.png)
不过个人认为沙盒修复的关键是Ognl3.0.18+和Ognl3.1.10+中 OgnlContext
删除了_memberAccess
这个key,从而禁止了对_memberAccess
的访问。Struts2.3.30开始使用的是Ognl3.0.19,Struts2.5.2开始使用的是Ognl3.1.10。从S2-045的payload中也能看出来,直接可以执行(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)
,后面才开始查看是否存在#_memberAccess
。
%{(#fuck='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames.clear).(#ognlUtil.getExcludedClasses.clear).(#context.setMemberAccess(#dm)))).(#cmd='CMD').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase.contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=newjava.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)}
![9bcdc927970590d12eb2e14db480ffb4.png](https://img-blog.csdnimg.cn/img_convert/9bcdc927970590d12eb2e14db480ffb4.png)
在初始化了 OgnlValueStack
对象后,会对OgnlValueStack
对象进行依赖注入。
![0305be131831537b4f204ea1e12db710.png](https://img-blog.csdnimg.cn/img_convert/0305be131831537b4f204ea1e12db710.png)
这个时候会调用 com.opensymphony.xwork2.ognl.OgnlValueStack$setOgnlUtil
将黑名单添加进来赋值给OgnlValueStack
的securityMemberAccess
对象
![00a3e7ca5f92d47e4266246a11578f3d.png](https://img-blog.csdnimg.cn/img_convert/00a3e7ca5f92d47e4266246a11578f3d.png)
这里需要注意的是由于 OgnlUtil
默认为singleton
单例模式,因此全局的OgnlUtil
实例都共享着相同的设置。如果利用OgnlUtil
更改了设置项(excludedClasses、excludedPackageNames、excludedPackageNamePatterns
)则同样会更改_memberAccess
中的值。
为什么改了 OgnlUtil
的值以后_memberAccess
的值也会改变呢?
首先上面说过了全局的 OgnlUtil
实例都共享着相同的设置。然后因为SecurityMemberAccess
类的excludedClasses
等属性都是集合类型
![e7c53384f914d550d8dbfd43a4dc8db0.png](https://img-blog.csdnimg.cn/img_convert/e7c53384f914d550d8dbfd43a4dc8db0.png)
![577cad9ab986b80ec701c0083fabee35.png](https://img-blog.csdnimg.cn/img_convert/577cad9ab986b80ec701c0083fabee35.png)
而在java中集合赋值时传递的是一个引用,我们对集合做的任何操作都会影响到它赋值过的变量。所以在解析Ognl表达式时,当 OgnlUtil
的excludedClasses
等安全属性被清空时,_memberAccess
对象的excludedClasses
等安全属性也直接随之被清空了。
![4e36f665614ba6f9fc414a519d9d3d8d.png](https://img-blog.csdnimg.cn/img_convert/4e36f665614ba6f9fc414a519d9d3d8d.png)
漏洞修复
不把报错的信息放到 LocalizedTextUtil.findText
方法中去,从而保证报错的content-type
不会进行OGNL表达式解析。
2.5.10.1版本修改:https://github.com/apache/struts/commit/b06dd50af2a3319dd896bf5c2f4972d2b772cf2b
2.3.32版本修改:https://github.com/apache/struts/commit/352306493971e7d5a756d61780d57a76eb1f519a
![776a7e3fea4c1622dfe9235b93225c09.png](https://img-blog.csdnimg.cn/img_convert/776a7e3fea4c1622dfe9235b93225c09.png)
https://www.easyaq.com