作者:Lyle
來源:https://xz.aliyun.com/t/4875
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org
簡述
利用前提
該漏洞是由于Tomcat CGI將命令行參數傳遞給Windows程序的方式存在錯誤,使得CGIServlet被命令注入影響。
該漏洞只影響Windows平臺,要求啟用了CGIServlet和enableCmdLineArguments參數。但是CGIServlet和enableCmdLineArguments參數默認情況下都不啟用。
時間線
- 報告漏洞 2019-3-3
- 漏洞公開 2019-4-10
漏洞影響范圍
- 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
復現
筆者使用的復現環境為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">
<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<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 ,可以看到命令執行成功。
原理
漏洞相關的代碼在 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下會輸出 arg 和 dir 命令運行后的結果。同樣的,用類似的腳本在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('arg.sh arg & dir'); ,得到的結果為
execve("/bin/sh", ["sh", "-c", "arg.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文件時,需要做合適的參數檢查才能避免漏洞出現。
修復方式
開發者在 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());
if ("POST".equals(req.getMethod())) {
cgi.setInput(req.getInputStream());
}
cgi.setResponse(res);
cgi.run();
} else {
res.sendError(404);
}
修復建議
-
使用更新版本的Apache Tomcat。這里需要注意的是,雖然在9.0.18就修復了這個漏洞,但這個更新是并沒有通過候選版本的投票,所以雖然9.0.18沒有在被影響的列表中,用戶仍需要下載9.0.19的版本來獲得沒有該漏洞的版本。
-
關閉enableCmdLineArguments參數
參考鏈接
- 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
- https://blog.trendmicro.com/trendlabs-security-intelligence/uncovering-cve-2019-0232-a-remote-code-execution-vulnerability-in-apache-tomcat
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/958/
暫無評論