Author:LJ(知道创宇404安全实验室)
Date: 2017-03-14
凌晨12点,Struts2 官方发布了 S2-045 的漏洞公告,仔细阅读描述,发现是友商安恒的 Nike Zheng 小伙伴提交的,所以赶紧跟进分析了一把。
首先,我们获取最新的 Struts2
源代码,并编译出存在漏洞环境的 demo
,这里我选择了官方教程推荐的 struts2-showcase
, 具体操作指令如下:
git clone https://github.com/apache/Struts.git
cd Struts
git checkout STRUTS_2_5_10
mvn package
国内的网络,你懂的...,最后在 apps/showcase/target
目录下生成了 struts2-showcase.war
文件,将其拷贝至 tomcat 的webapps 目录下,为了输入方便,这里将文件名重命名为 showcase.war
。启动tomcat,访问 http://localhost:8080/showcase/ 可以看到漏洞环境的界面如下:
下面是使用网上流出 PoC
的关键部分打一下看到的效果:
从上面的效果图可以看出:
发起请求的时候并不需要找到具体的上传点,只要是有效的 URL
就可以
发起请求不需要 POST
方法也可以触发,因为 curl
默认发起请求就是 GET
先进入刚才获取到的 Struts 源代码目录,并查看diff后的代码,具体操作如下:
git diff STRUTS_2_5_10 STRUTS_2_5_10_1 > s2-45.diff
在去除了一些配置和测试文件信息后,下面这部份代码引起了笔者的注意:
从删除的代码上可以看出专门对 validation
做了不为空的校验,进一步跟进 LocalizedTextUtil
的 findText
函数,发现这个函数被重载:
在392行代码中 ActionContext
中获取了 ValueStack
,这里可以简单认为是从请求的上下文环境当中获取了所有的数据,然后调用重载的下一个 findText
,在729行代码中看到 TextParseUtil
类对 valueStack
做了转换处理,做进一步的跟进,具体代码如下:
translateVariables
的多次调用后,最终调用了第152行的 translateVariables
函数,根据此函数的注释,就是在这里做了由值到对象并执行的转换,最终触发漏洞。
但是这里有一个问题,就是具体是在哪里触发的漏洞,这个是由 Struts2 的机制引起的,在 struts2 的框架当中,内置了许多的拦截器,主要用于对框架的功能扩展,此次漏洞的 FileUploadInterceptor
就是对框架文件上传的功能扩展。
在进一步的跟进调试前,我们先梳理下 Struts2 整体的框架结构,以便于理解 request 请求流转的过程,这里借用 struts
官方的设计架构图,并做个整体架构的简述:
我们先不看 Struts2
自带的蓝色核心部分,可以看到 request
请求大部分都是在浅黄色的过滤器和绿色的拦截器中间流转。
在有了具体的业务逻辑需要后台处理时,需要将 request
发送到对应的 Action
来处理,图中画的是 FilterDispatcher
,但在 Struts 2.5
以上已经将这个换成了 StrutsPrepareAndExecuteFilter
。
看到上图,去除 Struts2
自带的核心部分,可以看到大部分的过程都是在过滤器和拦截器中间流转,而且对流转的Action
并没有加以区分。
简单来说,过滤器对所有的请求都起到作用,主要用来对请求添加,修改或者分派转发至Action
处理业务逻辑,图中的FilterDispatcher
就是起到这个作用的。
拦截器能对配置文件中匹配的 request
进行处理,并能获取 request
当中的上下文环境及数据。
我们先来对 FileUploadInterceptor
下断点来调试:
其中第264行是对错误的request
进行验证处理,我们跟进findText
看一下:
这里 request
获取ValueStack
对象,我们可以看到 defaultMessage
的值如下图:
进一步跟进 findText
函数,就发现这个函数被重载了。重载的函数始于448行,然后是循环操作。跳出循环单步跟进到573行,我们发现这里又调用了 GetdefaultMessage
方法:
于是继续跟进 GetDefaultMessage
方法并定位到729行:
接着跟进 translateVariables
方法,expression
就是传入的错误信息:
注意到上图使用了 ognl
的"$"
与 "%"
标签,两者都能告诉执行环境 ${}
或 %{}
中的内容为ognl表达式
。PoC
中使用的是 "%"
,使用 "$"
也能触发漏洞。
再次跟进translateVariables
方法,在这里提取出ognl
表达式并调用evaluate
方法执行。
最后程序在 parser
的evaluate
方法中执行了 ognl
表达式。
综上,漏洞触发后程序的调用栈先后顺序如下:
1.intercept:264,FileUploadInterceptor 文件上传拦截器处理报错信息
2.findText:393,LocalizedTextUtil
3.findText:573,LocalizedTextUtil 提取封装的Action中的值栈
4.getDefaultMessage:729,LocalizedTextUtil 值到对象转换
5.translateVariables:45,TextParseUtil
6.translateVariables:123,TextParseUtil
7.translateVariables:166,TextParseUtil 提取出ognl表达式并执行
8.evaluate:13,OgnlTextParser 最后执行ognl表达式
回顾struts2历史RCE漏洞,形成原因与ognl
表达式执行相关的漏洞层出不穷。
当阅读这些历史漏洞的分析文章时,有些作者以 ”这里竟然执行了ognl表达式“ 这句话对漏洞点进行吐槽。
然而这次的漏洞成因则更为奇怪,解析出错误信息的ognl
表达式并执行。
最后,当动态调试此类漏洞时,前往 ognl
表达式执行的方法处下断点调试,马上就能一目了然的看到漏洞触发的完整调用栈。