作者: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>

這里主要的設置是 enableCmdLineArgumentsexecutable 兩個選項。 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/htmle.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下會輸出 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('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;
}

不通過時,會將 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);
}


修復建議

  1. 使用更新版本的Apache Tomcat。這里需要注意的是,雖然在9.0.18就修復了這個漏洞,但這個更新是并沒有通過候選版本的投票,所以雖然9.0.18沒有在被影響的列表中,用戶仍需要下載9.0.19的版本來獲得沒有該漏洞的版本。

  2. 關閉enableCmdLineArguments參數


參考鏈接


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/958/