返回

Struts2's Story

Struts2...

框架流程

一图以蔽之

①请求被封装到了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请求的入口方法,PrepareOperationsExecuteOperations是重点。

  • PrepareOperations 主要做了以下几件事
  1. 设置Encoding和Locale
  2. 创建ActionConetext(ognl的重要知识!!!)
  3. 绑定Dispatcher到当前线程
  4. 对HttpServeltRequest进行封装
  5. 根据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来实现,差别就在与SecurityMemberAccessallowStaticMethodAccess进行赋值了,这个赋值默认是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对象

表达式简单明了

  1. AST解析顺序

由右向左 由内到外

  1. #%$@符号

# 访问非根对象;相当于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的天下,不知道日后还能回归否。

(ง •_•)ง 2021-02-24 17:39:09 星期三

Licensed under CC BY-NC-SA 4.0