Automated Threat Modeling

Apache MINA CVE-2026-41409: Brief Summary of a Critical Deserialization Bypass via Static Initializer Timing Flaw

A brief summary of CVE-2026-41409, a critical (CVSS 9.8) deserialization bypass in Apache MINA that makes the earlier CVE-2024-52046 fix ineffective, with patch details and affected versions.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-04-27

Apache MINA CVE-2026-41409: Brief Summary of a Critical Deserialization Bypass via Static Initializer Timing Flaw
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

An incomplete security fix in Apache MINA has reopened a critical remote code execution path through Java deserialization, with a CVSS score of 9.8. The original vulnerability, CVE-2024-52046, was patched with a classname allowlist. CVE-2026-41409 shows that the allowlist was enforced too late in the deserialization pipeline, so a malicious static initializer could execute before the check ever ran.

Apache MINA is a network application framework maintained by the Apache Software Foundation for building high-performance, highly scalable network applications in Java. It is widely used in enterprise environments and is the foundation for several Apache subprojects. Because it sits in server-side Java stacks, a deserialization vulnerability in MINA is particularly consequential for organizations running networked services.

Technical Information

Root Cause: A Timing Problem in Allowlist Enforcement

CVE-2026-41409 is classified under CWE-502 (Deserialization of Untrusted Data). The vulnerability is in AbstractIoBuffer.getObject(), which uses Java native deserialization to process incoming serialized data. When Apache addressed CVE-2024-52046, they added a classname allowlist to the custom ObjectInputStream used internally. The intent was to reject any class not explicitly approved before it could be instantiated.

But the allowlist was consulted too late, and two methods inside the custom ObjectInputStream each contained a separate bypass path.

Bypass Path 1: readClassDescriptor()

In the vulnerable code, after reading the class name via readUTF(), the code immediately called Class.forName(className, true, classLoader). The true parameter is the problem: it tells the JVM to initialize the class, which means executing any static {} initializer block. The allowlist check happened only after this call returned. An attacker could craft a serialized payload referencing a class with a malicious static initializer. The JVM would load and initialize the class (executing the attacker's code), and only then would the allowlist reject it.

Bypass Path 2: resolveClass()

This method had a flaw in its branching logic. When desc.forClass() returned null, the code fell through to a Class.forName() call with no allowlist check at all. When forClass() returned a non-null value, the allowlist was checked, but only after the class was already loaded. Either branch let an attacker bypass the intended security control.

Attack Flow

  1. An attacker identifies a target application using Apache MINA that calls IoBuffer.getObject(). This method is commonly invoked when a ProtocolCodecFilter is configured with ObjectSerializationCodecFactory in the filter chain.
  2. The attacker crafts a serialized Java object containing a reference to a class with a malicious static {} initializer block. This initializer could execute arbitrary system commands, open a reverse shell, or take other actions.
  3. The attacker sends this payload over the network to the MINA service.
  4. MINA's AbstractIoBuffer.getObject() begins deserializing the payload. During deserialization, the custom ObjectInputStream calls either readClassDescriptor() or resolveClass().
  5. In both methods, Class.forName() is invoked before the allowlist is consulted. The JVM loads the attacker's class and executes its static initializer.
  6. The malicious code runs with the privileges of the MINA application process. The allowlist check runs afterward and throws an exception, but by then the attacker's code has already executed.

The attack requires no authentication and is exploitable over the network, which accounts for the 9.8 CVSS score.

Vulnerable Codepaths

An application is only affected if it calls IoBuffer.getObject(). This most commonly happens when ObjectSerializationCodecFactory is used with a ProtocolCodecFilter in the MINA filter chain. Applications that do not use Java object serialization through MINA are not exposed.

Patch Information

Emmanuel Lécharny committed the fix on April 23, 2026 (commit 8b1dadb55b1d3f6b17f4ded10179e6b253ff1cc9) with the message: "Added patch regarding whitelist bypassing backported from mina-2.2.X." The commit modifies mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java and restructures both methods inside the custom ObjectInputStream.

Change 1: readClassDescriptor()

The fix moves the allowlist check ahead of the Class.forName() call. If the class name does not match any entry in the allowlist, a ClassNotFoundException is thrown immediately and the class is never loaded:

String className = readUTF(); // Only accept classes that are listed as acceptable // Apply class filter BEFORE calling Class.forName if (!acceptMatchers.stream().anyMatch(m -> m.matches(className))) { throw new ClassNotFoundException("Class not in accept list " + className); } // Use initialize=false to prevent static block execution during class loading Class clazz = Class.forName(className, true, classLoader);

Change 2: resolveClass()

This method changed more. The patched code applies the allowlist filter unconditionally at the top of the method, before touching forClass() or Class.forName():

String className = desc.getName(); // apply acceptMatchers filter before any Class.forName() call, // regardless of whether forClass() is null or not if (!acceptMatchers.stream().anyMatch(m -> m.matches(className))) { throw new ClassNotFoundException("Class not in accept list " + className); } Class clazz = desc.forClass(); if (clazz != null) { return clazz; } try { return Class.forName(className, false, classLoader); } catch (ClassNotFoundException ex) { return super.resolveClass(desc); }

This closes both bypass paths: the forClass() == null path that previously skipped filtering entirely, and the forClass() != null path that checked only after loading.

The commit also adds 79 lines of new test cases in IoBufferTest.java that verify rejection behavior. For instance, testObjectSerializationReject() confirms that deserializing an object containing an unlisted class now throws ClassNotFoundException, and testNonserializableClassReject() / testNonserializableInterfaceReject() check that non-allowlisted classes and interfaces are blocked.

Configuration Hardening

Upgrading the library is necessary but not sufficient. By default, the decoder rejects all classes present in incoming data, so developers must explicitly configure which classes it will accept on the ObjectSerializationDecoder instance. It provides these methods:

/** * Accept class names where the supplied ClassNameMatcher matches for * deserialization, unless they are otherwise rejected. * * @param classNameMatcher the matcher to use */ public void accept(ClassNameMatcher classNameMatcher) /** * Accept class names that match the supplied pattern for * deserialization, unless they are otherwise rejected. * * @param pattern standard Java regexp */ public void accept(Pattern pattern) /** * Accept the wildcard specified classes for deserialization, * unless they are otherwise rejected. * * @param patterns Wildcard file name patterns as defined by * org.apache.commons.io.FilenameUtils(String, String) */ public void accept(String... patterns)

Organizations should use these methods to restrict deserialization to the minimum set of trusted classes their application requires.

The fix ships in Apache MINA versions 2.0.28, 2.1.11, and 2.2.6.

Affected Systems and Versions

MINA BranchAffected VersionsFixed Version
2.0.x2.0.0 through 2.0.272.0.28
2.1.x2.1.0 through 2.1.102.1.11
2.2.x2.2.0 through 2.2.52.2.6

Only applications that call IoBuffer.getObject() are affected, most commonly through ObjectSerializationCodecFactory used with a ProtocolCodecFilter in the MINA filter chain.

Apache has confirmed that subprojects including FtpServer, SSHd, and Vysper are not affected by this vulnerability.

Vendor Security History

CVE-2026-41409 is a direct bypass of the fix for CVE-2024-52046, which was itself a critical deserialization vulnerability in the same ObjectSerializationDecoder component. CVE-2024-52046 was widely tracked across vendor security databases, with advisories from NetApp, F5, IBM, and SentinelOne among them. This level of attention indicates that threat actors actively monitor Apache MINA for deserialization flaws.

The Apache Software Foundation responded to both vulnerabilities with clear advisories and patches. An incomplete first fix followed by a bypass is not uncommon in deserialization remediation. The interaction between Java class loading, initialization, and security checks creates subtle timing windows that are easy to miss on the first pass.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization