Java Web安全入门——S2-001漏洞分析
Java-Web入门第一篇,S2-001漏洞复现&原理分析
1. 漏洞简介
官方公告:https://cwiki.apache.org/confluence/display/WW/S2-001
漏洞影响范围:WebWork 2.2.0-WebWork 2.2.5,Struts 2.0.0-Struts 2.0.8
2. Struts2 架构&请求处理流程
在该图中,一共给出了四种颜色的标识,其对应的意义如下。
- Servlet Filters(橙色):过滤器,所有的请求都要经过过滤器的处理。
- Struts Core(浅蓝色):Struts2的核心部分。
- Interceptors(浅绿色):Struts2的拦截器。
- User created(浅黄色):需要开发人员创建的部分。
图中的一些组件的作用如下:
- FilterDispatcher:是整个Struts2的调度中心,也就是整个MVC架构中的C,它根据ActionMapper的结果来决定是否处理请求。
- ActionMapper:用来判断传入的请求是否被Struts2处理,如果需要处理的话,ActionMapper就会返回一个对象来描述请求对应的ActionInvocation的信息。
- ActionProxy:用来创建一个ActionInvocation代理实例,它位于Action和xwork之间。
- ConfigurationManager:是xwork配置的管理中心,可以把它当做已经读取到内存中的
struts.xml
配置文件。 - struts.xml:是Stuts2的应用配置文件,负责诸如URL与Action之间映射的配置、以及执行后页面跳转的Result配置等。
- ActionInvocation:用来真正的调用并执行Action、拦截器和对应的Result,作用类似于一个调度器。
- Interceptor:拦截器,可以自动拦截Action,主要在Action运行之前或者Result运行之后来进行执行,开发者可以自定义。
- Action:是Struts2中的动作执行单元。用来处理用户请求,并封装业务所需要的数据。
- Result:是不同视图类型的抽象封装模型,不同的视图类型会对应不同的Result实现,Struts2中支持多种视图类型,比如Jsp,FreeMarker等。
- Templates:各种视图类型的页面模板,比如JSP就是一种模板页面技术。
- Tag Subsystem:Struts2的标签库,它抽象了三种不同的视图技术JSP、velocity、freemarker,可以在不同的视图技术中,几乎没有差别的使用这些标签。
接下来我们可以结合上图,来了解下Struts2框架是如何处理一个HTTP请求的。
当HTTP请求发送个Web服务器之后,Web服务器根据用户的请求以及web.xml
中的配置文件,将请求转发给Struts2
框架进行处理。
- HTTP请求经过一系列的过滤器,最后到达
FilterDispatcher
过滤器。 FilterDispatcher
将请求转发给ActionMapper
,判断该请求是否需要处理。- 如果该请求需要处理,
FilterDispatcher
会创建一个ActionProxy
来进行后续的处理。 ActionProxy
拿着HTTP请求,询问struts.xml
该调用哪一个Action
进行处理。- 当知道目标
Action
之后,实例化一个ActionInvocation
来进行调用。 - 然后运行在
Action
之前的拦截器,图中就是拦截器1、2、3。 - 运行
Action
,生成一个Result
。 Result
根据页面模板和标签库,生成要响应的内容。- 根据响应逆序调用拦截器,然后生成最终的响应并返回给Web服务器。
3. 漏洞复现
常用Payload
获取tomcat路径
1 |
|
获取Web路径
1 |
|
执行命令
1 |
|
4. 漏洞分析
我们就以%{1+1}
作为Payload,来分析一下漏洞产生的原因。
首先给我们自定义的Action上下一个断点,然后发送一个请求。
从调用栈中,我们可以知道,在DefaultActionInvocation
类中反射调用了我们自定义的类LoginAction
。
路径:xwork-2.0.3-sources.jar!/com/opensymphony/xwork2/DefaultActionInvocation.java
此时到达自定义类LoginAction
的username
的值为%{1+1}
。
从官方公布的漏洞详情中,我们可以知道,漏洞是出现在Struts2重新渲染jsp时,对ognl表达式进行了递归解析,导致了恶意的表达式被执行。
让我们继续往下跟,直到进入了TextParseUtil
中。
路径:xwork-2.0.3-sources.jar!/com/opensymphony/xwork2/util/TextParseUtil.java
1 |
|
在这里下了断点之后,程序进入了该方法好几次,而且每次的expression
的值也不一样。
在手册中,我们也可以查到该方法的作用是将变量转换为对象。
https://struts.apache.org/maven/struts2-core/apidocs/index.html
当expression
为username
时,从调用栈中我们可以看到整个调用过程。
读取index.jsp
的标签
通过UIBean
将标签解析出来
然后将其传入到了translateVariables
方法中。
经过两次调用之后,
1 |
|
传入的expression
的值变为了%{username}
。
在后面的findValue
方法中获得了我们传入的Payload%{1+1}
,然后将其存入到了o
中。
继续往下走,%{1+1}
还是满足ognl表达式的规则,于是又进行了一次调用。
此时的expression
的值为%{1+1}
,在后面使用findValue
对表达式进行了解析,返回的值2
。
在这之后,2
这个值是不满足表达式的规则,于是直接将值进行了返回。
由于没有进行验证,导致我们的输入%{1+1}
被程序错误的当做了表达式进行了解析,返回了值2
,最终显示在响应的表单中。
5. 补丁分析
通过对比xwork2.0.3和2.0.4版本的源码,我们很容易可以发现区别。
添加了一个maxLoopCount
属性,限制了递归解析的最大数目。
1 |
|
从而在解析到%{1+1}
时,不会继续向下递归了,这样就修复了该漏洞。
6. Reference
- https://www.kingkk.com/2018/08/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%AD%A6%E4%B9%A0struts2-S2-001/
- https://chybeta.github.io/2018/02/06/%E3%80%90struts2-%E5%91%BD%E4%BB%A4-%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E7%B3%BB%E5%88%97%E3%80%91S2-001/
- https://seaii-blog.com/index.php/2019/09/20/90.html
- https://struts.apache.org/core-developers/big-picture
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!