简介 {#toc-0}
利用前提 {#toc-1}
该漏洞是由于Tomcat CGI将命令行参数传递给Windows程序的方式存在错误,使得CGIServlet被命令注入影响。
该漏洞只影响Windows平台,要求启用了CGIServlet和enableCmdLineArguments参数。但是CGIServlet和enableCmdLineArguments参数默认情况下都不启用。
时间线 {#toc-2}
- 报告漏洞 2019-3-3
- 漏洞公开 2019-4-10
漏洞影响范围 {#toc-3}
- 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
复现 {#toc-4}
笔者使用的复现环境为9.0.12 + JRE 1.8.0。
首先进行CGI相关的配置,在 conf/web.xml
中启用CGIServlet:
<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>
这里主要的设置是 enableCmdLineArguments
和 executable
两个选项。 enableCmdLineArguments
启用后才会将Url中的参数传递到命令行, executable
指定了执行的二进制文件,默认是 perl
,需要置为空才会执行文件本身。
同样在 conf/web.xml
中启用cgi的servlet-mapping
<servlet-mapping>
<servlet-name>cgi</servlet-name>
<url-pattern>/cgi-bin/*</url-pattern>
</servlet-mapping>
之后修改 conf/context.xml
的 <Context>
添加 privileged="true"
属性,否则会没有权限
<Context privileged="true">
`<span class="c"><!-- Default set of monitored resources. If one of these changes, the --></span>
<span class="c"><!-- web application will be reloaded. --></span>
<span class="nt"><WatchedResource></span>WEB-INF/web.xml<span class="nt"></WatchedResource></span>
<span class="nt"><WatchedResource></span>WEB-INF/tomcat-web.xml<span class="nt"></WatchedResource></span>
<span class="nt"><WatchedResource></span>${catalina.base}/conf/web.xml<span class="nt"></WatchedResource></span>
<span class="c"><!-- Uncomment this to disable session persistence across Tomcat restarts --></span>
<span class="c"><!--</span>
`
<Manager pathname="" />
-->
</Context>
然后在 ROOT\WEB-INF
下创建 cgi-bin
目录, 并在该目录下创建一个内容为 echo Content-type: text/html
的 e.bat
文件。
配置完成后,启动tomcat,访问 http://127.0.0.1:8080/cgi-bin/e.bat?&ver
,可以看到命令执行成功。
原理 {#toc-5}
漏洞相关的代码在 tomcat\java\org\apache\catalina\servlets\CGIServlet.java
中,CGIServlet提供了一个cgi的调用接口,在启用enableCmdLineArguments参数时,会根据RFC 3875来从Url参数中生成命令行参数,并把参数传递至Java的 Runtime
执行。 这个漏洞是因为Windows下参数处理的特性导致的,具体的处理方式可以看微软的这篇文章。
下面以一个简单的case来说明这个问题,在Windows下创建arg.bat
rem arg.bat
echo %*
并执行如下的Java代码
String [] cmd={"arg.bat", "arg", "&", "dir"};
Runtime.getRuntime().exec(cmd);
在Windows下会输出 arg
和 dir
命令运行后的结果。
同样的,用类似的脚本在Linux环境下测试:
# arg.sh
for key in "$@"
do
echo '$@' $key
done
String [] cmd={"arg.sh", "arg", "&", "dir"};
Runtime.getRuntime().exec(cmd);
此时的输出为
$@ arg
$@ &
$@ dir
这个例子可以比较清晰的看出漏洞的成因。
修复方式 {#toc-6}
开发者在 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;
}
不通过时,会将 CGIEnvironment
的 valid
参数设为 false
,在之后的处理函数中会直接跳过执行。
if (cgiEnv.isValid()) {
CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(),
cgiEnv.getEnvironment(),
cgiEnv.getWorkingDirectory(),
cgiEnv.getParameters());
`<span class="k">if</span> <span class="o">(</span><span class="s">"POST"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getMethod</span><span class="o">()))</span> <span class="o">{</span>
<span class="n">cgi</span><span class="o">.</span><span class="na">setInput</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">());</span>
<span class="o">}</span>
<span class="n">cgi</span><span class="o">.</span><span class="na">setResponse</span><span class="o">(</span><span class="n">res</span><span class="o">);</span>
<span class="n">cgi</span><span class="o">.</span><span class="na">run</span><span class="o">();</span>
`
} else {
res.sendError(404);
}
修复建议 {#toc-7}
- 使用更新版本的Apache Tomcat。这里需要注意的是,虽然在9.0.18就修复了这个漏洞,但这个更新是并没有通过候选版本的投票,所以虽然9.0.18没有在被影响的列表中,用户仍需要下载9.0.19的版本来获得没有该漏洞的版本。
- 关闭enableCmdLineArguments参数
参考链接 {#toc-8}
- https://tomcat.apache.org/security-9.html
- https://tomcat.apache.org/tomcat-9.0-doc/cgi-howto.html
- https://github.com/apache/tomcat/commit/4b244d8
- https://github.com/pyn3rd/CVE-2019-0232/
- https://tools.ietf.org/html/rfc3875
- https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
- https://codewhitesec.blogspot.com/2016/02/java-and-command-line-injections-in-windows.html