一、使用Dongtai-IAST检测S2-001漏洞

1. 启动在线靶场

登陆在线靶场,启动S2-001环境,等待环境启动之后,点击访问靶场按钮即可前往靶场环境。该环境来源于vulhubvulapps

image-20210416121306566

2. 登陆洞态IAST网站,查看漏洞检测结果

image-20210416121638264

3. 进入搜索功能,分析完整的污点调用图

3.1 Source点

首先,在org.apache.struts2.dispatcher.mapper.DefaultActionMapper#handleSpecialParameters()方法中,调用Servlet接口的getParameterMap方法获取外部参数,形成初始污点

image-20210416121817595

3.2 Propagator方法

污点经过一系列的处理,最终在com.opensymphony.xwork2.util.OgnlUtil#compile方法中,调用ognl.Ognl#parseExpression方法将污点数据传播为Ognl表达式对象


image-20210416121842241

3.3 Sink方法

最后,在ognl.Ognl#getValue方法中,使用Ognl表达式对象的getValue方法获取Ognl表达式的值

image-20210416121904431

二、S2-001漏洞手工分析

首先,使用struts2标签来定义表单

<%@ 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 rel="nofollow" href="https://struts.apache.org/docs/s2-001.html">https://struts.apache.org/docs/s2-001.html</a></p>

<s:form action="login">
	<s:textfield name="username" label="username" />
	<s:textfield name="password" label="password" />
	<s:submit></s:submit>
</s:form>
</body>
</html>

当上述表单提交后,struts2会解析表单,对表单中usernamepassword的值进行解析然后展示。其中,包括构造ognl表达式%{username}获取用户输入,后端代码如下:

Class valueClazz = this.getValueClassType();
if (valueClazz != null) {
    if (this.value != null) {
        this.addParameter("nameValue", this.findValue(this.value, valueClazz));
    } else if (name != null) {
        String expr = name;
        if (this.altSyntax()) {
            expr = "%{" + name + "}";
        }

        this.addParameter("nameValue", this.findValue(expr, valueClazz));
    }
} else if (this.value != null) {
    this.addParameter("nameValue", this.findValue(this.value));
} else if (name != null) {
    this.addParameter("nameValue", this.findValue(name));
}

org.apache.struts2.components.UIBean#evaluateParams方法中,使用Ognl语法拼接参数名称,然后调用findValue方法,查找ognl表达式的值。findValue方法最终调用com.opensymphony.xwork2.util.TextParseUtil#translateVariables方法,代码如下:

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;
        }
    }
}

translateVariables方法中,检查当前expression是否存在Ognl表达式,如果存在,循环调用stack.findValue(var, asType)方法搜索Ognl表达式的结果,并对结果重新构造Ognl表达式,然后循环上述步骤,直到findValue的结果不存在ognl表达式,返回数据。上述方法的关键是findValue方法,因此,跟进该方法,代码如下:

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);
    } catch (OgnlException var9) {
        var4 = this.findInContext(expr);
        return var4;
    } catch (Exception var10) {
        this.logLookupFailure(expr, var10);
        var4 = this.findInContext(expr);
        return var4;
    } finally {
        OgnlContextState.clear(this.context);
    }

    return var4;
}

上述方法中,先检查是否需要重写ognl表达式,如果需要,替换ognl表达式;然后使用OgnlUtil.getValue方法获取Ognl表达式expr的值,代码如下:

public static Object getValue(String name, Map context, Object root, Class resultType) throws OgnlException {
    return Ognl.getValue(compile(name), context, root, resultType);
}

上述方法中,首先调用compile方法解析Ognl表达式的值获取Ognl对象,代码如下:

public static Object compile(String expression) throws OgnlException {
    synchronized(expressions) {
        Object o = expressions.get(expression);
        if (o == null) {
            o = Ognl.parseExpression(expression);
            expressions.put(expression, o);
        }

        return o;
    }
}

然后调用Ognl.getValue方法使用Ognl对象和上下文获取表达式的值。至此,触发Ognl表达式解析,且ognl表达式为用户可控。

账号申请

  • SaaS版本地址:http://iast.huoxian.cn:8000/

  • SaaS版本账号申请

    image-20210416191241000

  • 洞态IAST合作伙伴计划之整体开源联合开发,申请方式请扫描下方二维码

    image-20210416191319643

  • 如需加入技术讨论群,扫描二维码添加微信并备注"洞态IAST-加群",工作人员将拉您进群

image-20210416160854960