框架流程
一图以蔽之
①请求被封装到了HTTPServletRequest
②经过Filters,其中包括ActionContextCleanUp、Other filters(SiteMesh, etc)、FilterDispatcher or StrutsPrepareAndExecuteFilter(2.1.3版本)
③第3层filter询问ActionMapper是否调用Action,ActionMapper返回Action信息给filter
④第3层filter把请求转交给ActionProxy
⑤ActionProxy通过Configuration Manager询问配置文件Struts.xml,找到调用的Action类
⑥ActionProxy创建一个ActionInvocation实例
⑦ActionInvocation调用Action类,并且之前经过一系列的拦截器(默认Struts拦截器)
⑧Action执行完之后,ActionInvocation根据Struts.xml找到对应的Result(一般是Jsp、FreeMarker等,也存在Action链)
⑨Template通过Tag Subsystem处理标签(**未在图中体现**)
⑨反过来再执行一遍拦截器,最后返回到HTTPServletResponse
- FilterDisPathcer 和 StrutsPrepareAndExecuteFilter都是在Fileter的第3层,FilterDispathcer在
<2.1.3
中使用,StrutsPrepareAndExecute在>=2.1.3
版本中使用
Servlet filters(过滤器): ctionContextCleanUp、Other filters(SiteMesh, etc)、FilterDispatcher or StrutsPrepareAndExecuteFilter
struts core(核心): ActionMapper、ActionProxy、Configuration Manager、ActionInvocation、Result、Tag Subsystem
Interceptors: Struts-default
User(用户): Struts.xml、Action、Template
这里粗略的讲下Struts2的初始化以及HTTP请求处理的流程,为了后续分析做下铺垫(这里以2.2.3进行分析)
Struts2初始化
初始化只在系统启动时运行一次,建立Struts2的运行环境
看到StrutsPrepareAndExecuteFilter
,这里是Struts2的入口点,init
方法
涉及到Dispathcer(核心分发器)、PrepareOperations(Http预处理类)、ExecuteOperations(Http处理执行类),这里主要分析下Dispatcher,后两者会在HTTP请求处理流程中说明。
看到initDispatcher
虽然这里的的步骤很简单,实际上加载了大量的配置元素,例如default.properties,struts-default.xml,struts-plugin.xml,struts.xml等,这个过程还涉及到Provid
(配置元素加载器)、Builder
(配置元素构造器)。Provid
包括ContainerProvider
(容器加载器)、PackageProvider
(事件映射加载器),Builder
包括ContainerBuilder
(容器构造器)、PackageConfigBuilder
(事件映射构造器),这里就不展开讲,有兴趣的可以去看《Strut2 技术内幕-深入解析Struts2架构设计与实现原理》。
Dispatcher初始化过程
初始化的过程中,Dispatcher主要是负责进行配置的加载,它在整个Struts2运行过程中都起了作用。
包括后续的接受并预处理http请求
、Struts2和Xwork的交互
、垃圾清理
。
HTTP请求处理
一图以蔽之
总体上有两个阶段,HTTP请求预处理阶段
、XWork事件处理阶段
。
第一阶段是把HTTP请求变成了Java对象,而第二阶段Xwork对请求进行处理。
回到StrutsPrepareAndExecuteFilter
doFilter
就是处理http请求的入口方法,PrepareOperations
和ExecuteOperations
是重点。
- PrepareOperations 主要做了以下几件事
- 设置Encoding和Locale
- 创建ActionConetext(ognl的重要知识!!!)
- 绑定Dispatcher到当前线程
- 对HttpServeltRequest进行封装
- 根据http请求查找ActionMapping
这里重点讲下第2个过程,创建ActionConetext
看到createActionContext
首先在当前线程中获取ActionContext,如果没有则通过创建ValueStack来创建。
Stack使用ValueStackFactory工厂类来创建
这里使用了OgnlValueStack来进行创建,创建完成之后,会注入到当前线程容器中
这里会设置root、securityMemberAccess、context,跟到createDefaultContext
返回一个OgnlContext实例,并且设置了classResolver、converter、memberAccess
这里看下攻击经常用到的memberAccess
,进入setMemberAccess
可以看到_memberAccess
的值在构造函数中就是DEFAULT_MEMBER_ACCESS
,而http请求时的_memberAccess
被OgnlValueStack的securityMemberAccess
赋值了,这里看下这两者的对比。
this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess); //ognlvaluestack
public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess(false); //ognlcontext
-
SecurityMemberAccess - DEFAULT_MEMBER_ACCESS 可以看出他们都调用
DefaultMemberAccess
来实现,差别就在与SecurityMemberAccess
对allowStaticMethodAccess
进行赋值了,这个赋值默认是false,因此早期漏洞的poc基本都要修改这个,后来就直接把_memberAccess
替换为DEFAULT_MEMBER_ACCESS
,也是可行的。 -
ExecuteOperations 这个主要是把http移交到Xwork进行处理的流程 创建MVC运行环境,创建ActionProxy
Xwork阶段不做说明,感兴趣可以自己去看
OGNL&漏洞历史
- OGNL 其实Sturts2不只支持OGNL表达式,还支持其它的类型表达式
(1)OGNL:可以方便地操作对象属性的开源表达式语言;
(2)JSTL:(JSP Standard Tag Library):JSP2.0集成的标准表达式语言;
(3)Groovy:基于Java平台的动态语言,它具有时下比较流行的动态语言的一些特性;
(4)Velocity:一种基于Java的模板匹配引擎。
OGNL有三个重要的参数,表达式、上下文环境、Root对象
表达式简单明了
- AST解析顺序
由右向左 由内到外
#%$@
符号
#
访问非根对象;相当于ActionContext.getContext()
,例如:#session.msg=ActionContext.getContext.getSession().getAttribute("msg")
; 过滤投影集合; 构造Map;%
当s2标签被理解为字符串类型时,告诉执行环境%{}
以OGNL表达式来执行,类似js里面的eval$
引用OGNL表达式,一般再配置文件种使用@
静态方法和变量访问,例如:@java.lang.Runtime@getRuntime().exec()
上下文环境(context)、root比较重要 context是OGNL执行的上下文环境,是由Xwork把ActionContext转变而来,包括application、session、value stack、action、request、parameters、attr 可以从图中看出VALUE_STACK中保存着valuestack这个对像
root是由Xwork把valuestack转变而来 可以看到valuestack也可以请求到context
actioncontext和valuestack类似相互包含,但从HTTP处理流程来看,其实是Actiontext包含了valuestack,而valuestack中有actioncontext的引用。
- 沙箱绕过 沙箱绕过主要对于以下几点进行
1、com.opensymphony.xwork2.ognl.SecurityMemberAccess
2、struts-default.xml黑名单
大致流程如下
- 漏洞一览
编号 | 版本 | 修复方式 | 触发点 | 漏洞原因 |
---|---|---|---|---|
s2-001 | 2.0.0-2.0.8 | 设置最大循环解析次数 | translateVariables | JSP标签 |
s2-003 | 2.0.0-2.0.11.2 | 参数名过滤正则改进+AllowStaticMethodAccess | OGNL表达式求值绕过 | |
s2-005 | 2.0.0-2.1.8.1 | 参数名正则改进 | s2003补丁绕过 | |
s2-009 | 2.1.0-2.3.1 | 正则改进,防止"(a)(b)" | s2005补丁绕过 | |
s2-012 | 2.1.0-2.3.14.2 | 禁止eval表达式 | translateVariables | 重定向参数可控 |
s2-013&4 | 2.0.0-2.3.14.1 | 函数修复;沙箱变化,删除setAllowStaticMethodAccess | translateVariables | JSP标签 |
s2-015 | 2.0.0-2.3.14.2 | 白名单过滤;修改pos初始化 | translateVariables | action通配符*、OGNL二次执行 |
s2-008-debug | 2.0.0-2.3.17 | 无 | findvalue | 配置问题 |
s2-016 | 2.0.0-2.3.15 | 删除关键字;增加过滤 | translateVariables | action:、redirect:、redirectAction:短路由导航 |
s2-029 | 2.0.0 - 2.3.24.1 | 增加黑名单 | translateVariables | JSP标签 |
s2-032 | 2.3.20-2.3.28 | 增加过滤 | translateVariables | name:短路由导航 |
s2-033 | 2.3.20-2.3.28 | 增加过滤 | REST插件 | |
s2-037 | 2.0.28-2.3.28.1 | 增加过滤 | S2033绕过 | |
s2-045 | 2.3.5 - 2.3.31,2.5 - 2.5.10 | 代码修复 | Content-type | Jakarta文件上传 |
s2-046 | 2.3.5 - 2.3.31,2.5 - 2.5.10 | 代码修复 | Content-Disposition、Content-Length | Jakarta文件上传 |
s2-052 | 2.1.6 - 2.3.33,2.5 - 2.5.12 | 白名单 | REST-XStream插件,反序列化 | |
s2-055 | 2.1.6 - 2.3.33,2.5 - 2.5.12 | 白名单 | REST-Jackson插件,反序列化 | |
s2-053 | 2.0.0-2.3.33&2.5-2.5.10.1 | 属性修改、方法删除 | translateVariables | freemarker标签 |
s2-057 | 2.0.4-2.3.34,2.5.0-2.5.16 | 黑名单、赋值方式修改 | namespace配置 |
One’storm
OGNL作为表达式引擎之一,由于Struts2不断出现漏洞而出名,这也是Struts2的占有率不断走低的原因之一,不是OGNL不够好,只是在早年那些修补漏洞的方式可真够拉跨的,其它的表达式引擎也出现过问题,例如Spring的SPEL,Jenkins的Groovy等,现在也慢慢变为了Spring的天下,不知道日后还能回归否。
- 参考链接 浅析OGNL的攻防史 struts2(六)之ognl表达式与ActionContext、ValueStack ognl-apache-struts-exploit-CVE-2018-11776 struts2请求过程源码分析
(ง •_•)ง 2021-02-24 17:39:09 星期三