Java vulnerabilities of the week: 17 to 23 September 2026
Most security feeds hand you a CVE id, a CVSS number, and one sentence saying an attacker may do something bad. As a developer that tells me almost nothing I can use. The part worth knowing is the mechanism: the exact buffer with no ceiling, the exact class filter that checks one path and forgets another. See the shape once and you recognise it in your own code, which is worth far more then the score. That is the whole reason for this series, and I wrote about the mindset in the CVE stigma post.
After last week this one is calm. Last week was coordinated mega-batches: Netty fifteen advisories, Storm fifteen CVEs, Syncope twenty-two. This week is five items, and every single one comes from the Apache Software Foundation, and every one is a library or framework that sits underneath other software rather then something you wrote. MyFaces under your JSF pages, MINA under your directory and messaging services, Karaf as the container your bundles run in, Neethi under any SOAP stack still speaking WS-Policy.
Two of the five are the same project, Apache MINA, with two unrelated bugs. The one to patch today is MyFaces, a critical that landed right as last week’s window closed. The one worth studying is a MINA advisory that exists only because an earlier fix was announced for three release branches and reached one of them.
The week in five
-
Apache MyFaces,
CVE-2026-68536, critical (CVSS 9.8), disclosed 16 September 2026, fixed in 2.3.12, 2.3-next-M9, 3.0.4, 4.0.4 and 4.1.4. Server-side request forgery and local file inclusion in MyFaces Core (CWE-918). The advisory describes it as SSRF and LFI in MyFaces Core: a request reaches a component that fetches a URL or opens a file whose location the request influences, so an attacker on the network can make your server open connections it should not or read files it should never touch. The precise request path is not public yet, so treat every version in range as affected, from the old 2.2.x line through 4.1.3. It landed on 16 September, the day my last roundup went out, so it missed that post (the GHSA record went up on the 16th, NVD published a few days later). Its the only critical here and the fixes are all on Maven Central, so patch this one first. -
Apache MINA,
CVE-2026-94301, disclosed 21 September 2026, fixed in 2.0.31 and 2.1.15. A deserialization allowlist bypass. The fix for the earlierCVE-2026-47065, anObjectInputStreamfilter that could be walked past with ajava.lang.reflect.Proxy, shipped on 2 June 2026 for 2.2.8, 2.1.13 and 2.0.29. The change reached the 2.2.x branch, and the 2.0.x and 2.1.x branches did not receive it at that time, so 2.0.29, 2.0.30, 2.1.13 and 2.1.14 remain open to the original bypass. This CVE covers that gap, with the override now backported to 2.0.31 and 2.1.15. Reported by tonghuaroot. Deep dive below. -
Apache MINA,
CVE-2026-47321, disclosed 21 September 2026, fixed in 2.0.29, 2.1.13 and 2.2.8. A decompression bomb in theCompressionFilter(CWE-409). When the filter inflates incoming data it allocates a buffer for the result no matter how large that result is, so a small payload with a compression ratio in the thousands expands until the heap is gone. The fix addsmaxDecompressedSize,maxDecompressRatioand adecompressRatioMinSizegrace limit, all configurable. Reported by Venkatraman Kumar of SecurIn. Note the two MINA fixes do not line up: on the 2.1 line the decompression fix is in 2.1.13 but the deserialization fix only in 2.1.15. -
Apache Karaf,
CVE-2026-92230, moderate, disclosed 17 September 2026, fixed in 4.4.11. A classloader leak. Karaf’sXmlUtilscached XML parser and transformer factories in staticThreadLocalfields on long-lived container threads. AThreadLocalvalue outlives the bundle that created it, so every bundle or feature install, update or refresh leaves another bundleClassLoaderpinned and unreachable for garbage collection, and Metaspace grows until the instance falls over. Reported by Baoquan Cui and Yucheng Qiu. Deep dive below. -
Apache Neethi,
CVE-2026-91863throughCVE-2026-91867, moderate, disclosed 18 September 2026, fixed in 3.2.4. Five denial-of-service bugs in the WS-Policy library that sits under CXF and Axis2. Deeply nested policy elements exhaust the thread stack (CVE-2026-91863), a single assertion packs unlimited content past the size limits and exhausts the heap (CVE-2026-91864), crafted references expand exponentially during normalization (CVE-2026-91865), policy intersection does unbounded work (CVE-2026-91866), and a remote policy fetch has no total timeout so a slow server hangs the request (CVE-2026-91867). All five were found with Claude agents studying the project, the same programme behind several of last week’s batches.
Apache Karaf: a ThreadLocal on a pooled thread pins the ClassLoader
Karaf is an OSGi runtime, and in OSGi bundles come and go while the process keeps running, each bundle with its own ClassLoader. Hot redeploy is the whole point of the model: install a feature, update a bundle, refresh it, all without a restart. So anything that keeps an old ClassLoader alive after its bundle is gone is a leak, and it shows up in Metaspace, the region that holds class metadata, not in the ordinary heap. That is part of why these leaks hide: a heap dump under load can look healthy while Metaspace climbs on every redeploy.
The mechanism here starts from a reasonable performance choice. DocumentBuilderFactory and TransformerFactory are not cheap to create, so XmlUtils cached them and reused them per thread through a static ThreadLocal. That part is sensible. The trouble is lifetime. A ThreadLocal entry lives as long as the Thread it is set on, and Karaf’s worker threads are pooled and long-lived, so in practice they never end. The cached factory was loaded by, and holds a reference back to, the ClassLoader of the bundle that first reached XmlUtils. When that bundle is updated, OSGi builds a fresh ClassLoader for the new revision and tries to drop the old one, and it cannot, because a pooled thread still holds a ThreadLocal whose value chains back to it. Do that on every redeploy and you leak one ClassLoader per cycle, and every class that loader ever defined is pinned with it. The fix in 4.4.11 stops caching those factories in a static ThreadLocal.
Why I find it interesting
This is the archetypal Java memory leak and the pattern is worth carrying in your head. A ThreadLocal set on a thread you did not create and will not end, a pool thread or a container thread, lives until that thread dies, which is never. If its value references a class, directly or down a chain of fields, it pins that class’s ClassLoader and everything the loader ever defined. In any environment with hot redeploy this is a ClassLoader per cycle: OSGi here, but a servlet container reloading a webapp or an application server hot-deploying an EAR is the same story, and Metaspace is where it lands. The bug belongs to the unbounded-lifetime family, the same reason ThreadLocal.remove() exists and the same reason a library that stashes something in a ThreadLocal on a borrowed thread has to clear it in a finally. The tell is a static ThreadLocal whose value is anything richer than a primitive or a plain JDK type. If you see one living on a pooled thread, assume a leak until you have proven otherwise.
Apache MINA: a fix is a claim until you can see it in the artifact you run
MINA is a network application framework, the transport layer under Apache Directory Server, Qpid and a handful of other services, and one of the things it can do is read a serialized Java object straight off the wire. Java deserialization is the most reliable remote code execution primitive the platform has, because readObject runs logic chosen by the incoming byte stream, so any framework that deserializes untrusted input guards it with a filter: an allowlist of the classes it is willing to reconstruct.
The earlier bug, CVE-2026-47065, was a hole in that guard. MINA’s filter overrode resolveClass, the ObjectInputStream hook called for ordinary classes, and checked each name against its allowlist. But ObjectInputStream has a second hook, resolveProxyClass, called when the stream rebuilds a dynamic java.lang.reflect.Proxy. If you only guard resolveClass, an attacker serializes a proxy that implements an allowed interface but is backed by a malicious InvocationHandler, and the proxy path never consults your allowlist at all. So a filter that looked complete had a door in the side, and the fix was to override resolveProxyClass too and apply the same check. This is a known class of bypass, and it is exactly why hardening deserialization means covering both hooks rather than one.
The new bug, CVE-2026-94301, is that this resolveProxyClass override reached the 2.2.x branch and not the 2.0.x and 2.1.x branches, which kept shipping without it. A user who upgraded to 2.1.13 on the strength of the earlier advisory was therefore still exposed to the same bypass. tonghuaroot found the gap, MINA issued a new CVE and backported the override, and the fix for the older branches is in 2.0.31 and 2.1.15.
Why I find it interesting
There are two lessons stacked here, one small and one large. The small one is the resolveProxyClass gap: an allowlist that checks class names has to cover the proxy path as well, or dynamic proxies walk straight through it, and that is a concrete thing to grep for in any custom ObjectInputStream you own. The large one is that a fixed-version number in an advisory is a claim you can verify, not a guarantee to assume. A backport can miss a maintenance branch, and a release can be tagged before it reaches Maven Central. The way to know a fix is in the artifact you run is to look at it: the commit on the branch you build from, or the class in the jar you actually resolved. That is the same instinct behind this whole series, a number tells you a bug exists, it does not tell you the mechanism, and it does not by itself tell you the code in front of you carries the fix. If you deserialize anything off a network in a MINA-based service, check that resolveProxyClass is overridden in exact version on your classpath, and move to 2.0.31, 2.1.15 or 2.2.8 and up.
What I take away from this week
Where the fix reaches you splits the list in two. MyFaces and Karaf are things you run and patch directly: bump myfaces-impl in your JSF application to 4.1.4 or the matching fix on your line, and move Karaf to 4.4.11. MINA and Neethi usually arrive transitively, MINA under directory and messaging services, Neethi under a CXF or Axis2 SOAP stack, so a framework or BOM bump can carry them, but the resolved version is what counts and pins lag, so check org.apache.mina:mina-core and org.apache.neethi:neethi in your dependency tree instead of assuming. And the MINA pair has a trap worth repeating: on the 2.1 line the decompression fix is in 2.1.13 while the deserialization fix is only in 2.1.15, so aim for 2.0.31, 2.1.15 or 2.2.8 and later to be clear of both.
The MINA backport is the story I will keep, because it is a plain reminder that a version number in an advisory is worth verifying. The earlier advisory listed three fixed versions, and at that point the override had reached one branch. That is not special to MINA: backports get sequenced across branches, and releases sometimes get tagged before they land on Maven Central. I lean on the workflow behind this post partly because I have been caught before by a patched version that was not downloadable yet. The habit that protects you is to confirm the fix in the artifact, the commit on your branch or the code in your jar, and treat the version string in the notes as a pointer to check rather than proof.
Third, the unbounded-work shape is everywhere again. Neethi’s five are all variations on “some input makes us allocate or recurse with no cap”, the decompression bomb in MINA’s CompressionFilter is the same idea with a compression ratio as the lever, and the identical decompression shape hit async-http-client back in August (CVE-2026-85721, unbounded response decompression). Any buffer, queue or recursion sized by data the other side controls needs an explicit ceiling and a sane failure when it is hit, because “the input will be reasonable” is a hope, not a bound, and a hostile peer’s entire job is to break that hope.
References
- oss-security daily index for 17 September 2026, Apache Karaf - Openwall oss-security
- oss-security: CVE-2026-92230, Apache Karaf ClassLoader leak via static ThreadLocal caching - Openwall oss-security
- oss-security daily index for 18 September 2026, Apache Neethi batch - Openwall oss-security
- oss-security: CVE-2026-91863, Apache Neethi uncontrolled recursion in WS-Policy parsing - Openwall oss-security
- oss-security: CVE-2026-91864, Apache Neethi element and attribute limit bypass - Openwall oss-security
- oss-security daily index for 21 September 2026, Apache MINA - Openwall oss-security
- oss-security: CVE-2026-47321, Apache MINA decompression bomb in CompressionFilter - Openwall oss-security
- oss-security: CVE-2026-94301, Apache MINA resolveProxyClass fix missing from 2.0.x and 2.1.x - Openwall oss-security
- GHSA-5fv6-hxpr-6p55, CVE-2026-68536 Apache MyFaces SSRF and LFI - GitHub Advisory Database
- GHSA-7grg-jcf7-rpmx, CVE-2026-85721 async-http-client unbounded response decompression - GitHub Advisory Database
- Apache MINA mina-core on Maven Central, 2.0.31, 2.1.15 and 2.2.8 confirmed - Maven Central
- Apache MyFaces myfaces-impl on Maven Central, 3.0.4, 4.0.4 and 4.1.4 confirmed - Maven Central
- Apache Karaf on Maven Central, 4.4.11 confirmed - Maven Central
- Apache Neethi on Maven Central, 3.2.4 confirmed - Maven Central
- Java vulnerabilities of the week: 11 to 16 September 2026 - oscerd.github.io
- The CVE stigma, we need a new security culture - oscerd.github.io