Exploiting Deserialization Vuln's
Insecure deserialization to refine [echo AAxx= | base64 --decode] my knowledge in deserialization vulnerabilities, specifically in regards to custom exploit development and blackbox testing.
Deserialization vulnerabilities are a class of bugs that have plagued multiple languages and applications over the years. These include Exchange (CVE-2021-42321), Zoho ManageEngine (CVE-2020-10189), Jira (CVE-2020-36239), Telerik (CVE-2019-18935), Jenkins (CVE-2016-9299), and more. Fundamentally, these bugs are a result of applications placing too much trust in data that a user (or attacker) can tamper with.
Attackers have leveraged these vulnerabilities for years to upload files, access unauthorized resources, and execute malicious code on targeted servers. Within the past 2 years, Mandiant has particularly observed APT41 using .NET ViewState and Java deserialization exploits to target companies and government entities within North America.
Given the prevalence and impact of these vulnerabilities, our goal was to create a process to systematically hunt for exploitation attempts. In this blog post, we will share our new rule generation (HeySerial.py) and validation (CheckYoself.py) tools and walk through the research process we used to create them.
While this blog post mainly focuses on deserialization exploits, the tools and processes presented here can help with hunting for the exploitation of other types of zero-days. For example, we can use HeySerial to generate hunting rules for the JNDI code injection zero-day released last week for log4j (CVE-2021-44228). For more details, check the “A Note on CVE-2021-44228” section later in the post.
What is a Deserialization Vulnerability?
“Serialized” data is just an object or data structure that has been encoded in a way that can be transferred easily – for example over the network. Developers do this regularly to pass objects between different parts of an application or between a client and server to maintain state. Once it’s transferred, it can be "deserialized" and used like it never left the original function.
Deserialization vulnerabilities result from applications putting too much trust in data that a user (or attacker) can modify. Deserialization can become dangerous when 3 conditions are met:
The serialized object is provided by or can be modified by a user.
An application attempts to deserialize and use the object without validation.
The object is deserialized by a portion of the application with valuable libraries in the "class path".
Exploiting a deserialization issue involves crafting a payload that replaces what should be a benign object or data structure – such as a session token or a ViewState – with code in the targeted language that executes something malicious for the attacker.
If dangerous classes or libraries are imported and accessible in the application “class path”, an attacker can reference useful functions or object types (also referred to as “gadgets”) to execute their payload. Due to how applications are structured, the dangerous functions may not be directly accessible, so successful exploitation often requires chaining several gadgets together.
Projects such as YSoSerial (Java) and YSoSerial .NET (C#) consolidate public research on successful gadget chains for common libraries and make it easy for anyone to generate a payload with one of these chains. This is then encoded and can be passed to servers with deserialization bugs. When an application with these gadgets imported unsafely deserializes the payload, the chain will automatically be invoked and execute the embedded command on the affected server.
What Does Successful Exploitation Look Like?
Deserialization issues in HTTP servers can appear in many places session/state data, Cookies, HTML form inputs, etc. In one recent example (CVE-2019-18211), the C1 CMS application unsafely deserialized objects passed through certain SOAP requests, leading to remote code execution.
An HTTP SOAP request with a malicious payload is shown in Figure 1, and the server response is shown in Figure 2. In this case, the server returned a simple HTTP 200 OK response after deserializing and executing the provided object.
As mentioned, deserialization vulnerabilities can affect a wide variety of languages and libraries which results in a very large surface area for attackers. Before we can develop a comprehensive hunting strategy, we need a thorough understanding of the problem space.
This class of vulnerabilities is most commonly discussed in the context of web applications, but they’re not the only applications affected.
In 2017, Google Project Zero demonstrated how to use .NET deserialization to target systems over Managed DCOM instead of HTTP. While not specifically deserialization, in 2016, two different Black Hat talks outlined ways to exploit the Java Messaging Service (JMS) and Java Naming and Directory Interface (JNDI) APIs through object/command injection. Just last week, a JNDI command injection 0-day was released for the log4j Java logging package (CVE-2021-44228).
For this blog post, we will limit our scope to attacks that occur over HTTP. This will allow us to focus our initial research while targeting the majority of deserialization exploitation attempts.
Notes for Developers; Languages and Objects
While any language can theoretically be at risk, some of the common languages/object types exploited with this class of vulnerability are serialized Java objects, .NET ViewStates, pickled Python objects, and serialized PHP objects. The following table details the header values used as a prefix for each of these object types.
*PHP serialized objects start with the ASCII O:LENGTH_OF_NAME: where LENGTH_OF_NAME is an integer. 4F 3A is the hex encoding of O:, but further tuning with a regular expression like O:[0-9]+: is necessary to avoid false positives.
Objects may also be serialized using language-specific formatters. For example, .NET applications may use formatters such as the BinaryFormatter, LosFormatter, NetDataContractSerializer, Json.NET, or SoapFormatter depending on the vulnerable gadget. Of note, YSoSerial.NET generates UTF-16LE objects for most of its supported formatters, so many of these payloads start with 0xFFFE.
While serialized objects can be transferred using the raw bytes, more frequently they will be encoded or encrypted in some way to make network transfer simpler. Base64 is very common as it is language agnostic and simple to implement.
Encoding methods can also be layered, for example by GZIP compressing and then Base64 encoding an object. For this blog post, we will focus on simple Base64 encoding due to its prevalence.
Known and Unknown Chains
This problem space includes a lot of unknowns. Deserialization vulnerabilities are regularly disclosed and, as mentioned, can affect any part of an application. Attackers are much more limited in how they can weaponize these vulnerabilities, and as defenders, we have an opportunity to hunt for novel exploitation by looking for the gadget chains and keywords that attackers must use to execute their final payload.
Security researchers (and attackers) are constantly looking for new dangerous gadget chains in common libraries or applications. Several well-known projects that centralize this research are listed in the following table, although this is by no means a comprehensive list. For this blog post, we’ll start with the ysoserial Java project, which was the first major tool and research to be released for this class of bugs.
For this research effort we want to identify a way to generate rules for known gadget chains, but we also want a more generalized approach that can let us proactively identify exploitation attempts for novel vulnerabilities and/or gadget chains. One method here could be looking for the payload vs the chain used to execute the payload. For example, we could hunt for serialized objects with DOS headers, malicious commands, or suspicious binaries.
Defending and find a Solution Surface Area
Now that we understand what we are trying to hunt for, we need to determine how we will hunt for it. The available detection surface area varies depending on your goals and visibility, but hunting opportunities typically fall into three categories:
Network
The most direct method for detecting this method of attack is to observe the exploitation attempt as the requests are made.
Endpoint - Dynamic
This may involve looking for uncommon process execution or behavior from web servers. (For example, IIS servers running cmd.exe /c whoami.) This will limit us to observing successful exploitation attempts only, though, and it will be biased towards exploitation for remote code execution (RCE). We may have limited visibility into exploitation for other objectives like file upload or remote URL inclusion.
Endpoint - Static (Log/File)
Depending on your network traffic visibility, using YARA rules to look at decrypted requests in server logs may provide the same (or better) visibility into exploitation attempts than a network IOC. One limitation here is that logs may not include the full server response, so we will have incomplete evidence.
For this blog post, we will focus on network hunting (through Snort rules) with some static log file hunting (through YARA rules).
I think thats it for now, and happy defending!