Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

 

Smi1e@Pentes7eam
漏洞信息:
https://cwiki.Apache.org/confluence/display/WW/S2-045
Struts2默认使用
org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类对上传数据进行解析 。其在处理Content-Type时如果获得非预期的值的话,将会抛出一个异常,在此异常的处理中会对错误信息进行OGNL表达式解析 。
 
漏洞复现
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
 
影响范围Struts 2.3.5 - 2.3.31, Struts 2.5 - 2.5.10
【Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行】 
漏洞分析Struts 2.3.5 - 2.3.31和Struts 2.5 - 2.5.10漏洞入口不同
前者调用栈
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
在过滤器
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterdofilter方法中下断点,跟进this.prepare.wrapRequest(request);,它会封装request对象 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
其中会判断 content_type中是否含有字符串multipart/form-data,如果有则实例化MultiPartRequestWrApper对象,并传入对应参数来对请求进行解析 。其中this.getMultiPartRequest;会去获取用来处理文件上传请求的解析类,默认是
org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest 。我们可以通过配置struts.multipart.parser属性来指定不同的解析类 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
跟进 this.multi.parse(request, saveDir);,这里的this.multi就是默认的JakartaMultiPartRequest对象 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
这里由于对上传请求解析时发现错误所以会进入到下面的 catch操作中,跟进this.buildErrorMessage
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
它会调用
LocalizedTextUtil.findText处理错误信息
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
不断跟进会发现他会把message传入
TextParseUtil.translateVariables进行表达式解析 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
后面就还是以前的流程了 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
而 Struts 2.5 - 2.5.10中
org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper.buildErrorMessage并没有调用
LocalizedTextUtil.findText
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
它是在拦截器 FileUploadInterceptor中调用的
LocalizedTextUtil.findText(error.getClazz, error.getTextKey,
ActionContext.getContext.getLocale, error.getDefaultMessage, error.getArgs);
从而对错误信息进行OGNL表达式解析
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
在2.3.30/2.5.2之后的版本,我们可以用来bypass黑名单过滤的 _memberAccessDefaultMemberAccess都进入到了黑名单中 。
Struts2漏洞系列之「S2-045」上传数据异常导致的OGNL表达式执行

文章插图
不过个人认为沙盒修复的关键是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
 
  1. %{(#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)}


    推荐阅读