How I Verified spring4shell Was Real

April 1, 2022

On March 30th, I started my day off the same way I typically do, catching up on infosec news on Twitter. This day though, I noticed a vague post come through about an exploit that impacted the Spring framework that was supposedly as bad as the recent log4j vulnerability. I dropped it into my work chat as kind of a heads up, but didn’t put much effort into further research as the original post had been deleted and there was no public PoC available. About an hour later, that changed when vxunderground shared the original PoC. I downloaded it and started getting to work to try and figure out what was going on.

This was an odd situation in the sense that typically you get a bug report/patch/git commit or something describing a security vulnerability and then you work your way backwards towards an exploit. Sometimes you even get a partially implemented PoC that you can work with, but you typically know what your target is. This scenario was different because the original post was pretty bad and wasn’t really accurate with regards to what was impacted, so what folks ended up with was a working PoC, but no idea what software was actually vulnerable haha.

Having some background with Java, I was sort of familiar with Spring and what it was, so my first step was to go out to their site and find a tutorial on how to build a simple API. The PoC code was using a POST request and sending a malicious payload, so my thinking was to just hack together a bare bones Spring app to test. APIs are typically pretty easy to get up and running, so I looked on the Spring site and found a tutorial on building a RESTful web service which I figured would be just what I needed. I followed the steps in the tutorial and got myself a little web service that could take GET requests, but the PoC was using POST, so I had to play around with my sample app a bit to get it taking POST and GET requests. Despite my sample app being able to take GET and POST requests now, the PoC still wasn’t triggering anything. Looking back, there were probably a couple of reasons why (the biggie being I wasn’t running this via a standalone tomcat instance), but at the time I figured my sample app code was shitty, so I went out looking for another way to test.

The PoC exploit was setting the “Content-Type” header as “application/x-www-form-urlencoded”, so I wanted to find a simple app that was using forms. A bit of searching sent me back to the Spring site where I found this beauty: Handling Form Submission, exactly what I needed! I cloned down the repo, built the executable JAR, started the app and hit it with the PoC. Nothing. Ok? So what is the problem here? I am horrible at taking notes on what I tried, so we’ll skip forward a bit in the story. After not being able to get the PoC to pop, I went back and took a look at the payload it was sending when I noticed something: it was trying to write a file to “webapps/ROOT” on the remote server. Having dealt with tomcat before, I knew that the “webapps” directory is where you put your WAR files when you were deploying something to tomcat, and that the “webapps/ROOT” directory was just that, the main/default app for tomcat.

That was my light bulb moment. I knew Spring was using an embedded tomcat to serve up my sample app, but what if that meant the concept of this “webapps” directory didn’t exist? What if I needed a standalone tomcat instance instead?

I spent 15-20 minutes digging around trying to figure out how to get Spring to spit out a WAR file instead of an executable JAR, and once I had that, I stood up a single tomcat instance to test with. I dropped my WAR file into the webapps directory, waited a minute for it to unpack, and then fired off the PoC again. This time I got a different failure, but it was good! The logs had a line about being unable to write “tomcatwar.jsp” to “webapps/ROOT” due to permissions. If you’ve looked at the PoC, you’ll understand that this is exactly what the PoC attempts to do, so the first part of the exploit was now working!

I went to check permissions on the “webapps/ROOT” directory and noticed it was owned by the “root” user, while everything else was owned by “tomcat” and of course the actual tomcat instance was running as “tomcat”. Knowing that this wouldn’t represent a real-world scenario, I changed the permissions on “webapps/ROOT” to see if I could get the exploit to fire, and sure enough, after running it again I now had a “tomcatwar.jsp” file sitting in “webapps/ROOT” :). The exploit had worked and I now had a webshell sitting there that I could use to run any commands as the “tomcat” user on the remote system.

Changing the permissions on the “webapps/ROOT” directory wasn’t realistic to me though, so I went in and modified the PoC to write the malicious file to “webapps/handling-form-submission” instead, as that was the directory of my unpacked sample app WAR and was owned by the “tomcat” user. With that single update I was able to get the PoC to work on a totally vanilla tomcat9 install on Ubuntu 20.04.

At that point I decided to put together my steps on how to reproduce my results with the PoC and shared it on Twitter for other folks to take a look at. Within a few hours others had picked up my notes and were able to confirm that this PoC was in fact real, and that Spring did have some issues to address. Some folks from CERT confirmed later on that this was a legit vulnerability using my methods, and various other researchers were able to iterate on my process to create easier ways to test.

I write all of this not really to highlight my specific work around “spring4shell”, but instead to highlight the “what if?” mentality that keeps many of us progressing. There were many points during this process that I could have just put my hands up and walked away, leaving the problem for someone else, but I think many of us in the infosec world are not wired that way. We want to understand why something is (or isn’t) doing what it XYZ, and how does that impact what we want it to do? The willingness to keep poking around and tossing shit at the wall to see what sticks is almost a necessity. Some folks are able to narrow their scope on what to toss at the wall, while others like myself have to slowly iterate through everything and lean on luck a bit ;).