← Lablog
Research 31 October 2026 ✎ 251 Staff

Month of Apache Bugs (MoAB) - Operation: Binary Chaos

There is a long tradition of "Month of" bug projects: Month of Browser Bugs, Month of Apple Bugs, Month of Kernel Bugs, Month of PHP Bugs: "A month of bugs is a strategy used by security researchers to draw attention to the lax security procedures of commercial software corporations." - Wikipedia - Month of bugs. I really wanted to see how fast one can discover 0days with the help of AI-SAST tool against Open Source Software (OSS), specifically in projects from Apache Software Foundation (ASF). I wanted to point that same lens at one target and see what fell out. So I ran a Month of Apache Bugs and called it Operation: Binary Chaos. This meant that I had to release a bug every day within one month. The discovery pass took five days. I started on 13 August 2026, and by the 18th all 36 confirmed 0-days in 36 different projects/codebases were triaged and reported. One man, one desire, one foundation, and a question I kept coming back to: how deep does the canary go across the ASF's enormous, uneven, frequently half-abandoned project universe?

Deep enough to fill a release calendar, it turns out. I went through roughly 345 Apache projects and components and opened 40 numbered advisory tracks in that first week. The long tail was disclosure, not discovery: reporting to each PMC, working with the teams that answered, waiting on the ones that never did, and publishing 36 advisories (ZSL-2026-6003 through ZSL-2026-6038) between 11 September and 30 October 2026, the day I closed the operation out. That stretch ran about fifty days, better than one shipped advisory every two. Nine CVEs came out of the pile. Every finding followed the one rule I set on day one: trace it in the real released source, then actually stand it up and trigger it before I let myself call it confirmed or "Green-GO".

This was never a volume contest. Two things that looked like bugs, Derby's LDAP filter and a negative-length write in Avro-C, I withdrew as false positives once I tested the real releases instead of a stale mirror or an AddressSanitizer artifact. They are in this writeup, as retractions, on purpose.

By the numbers

~345
Projects & components examined
36
Advisories published
9
CVEs across the batch
5
Days to find 36 0-days
9
"By-design" declines, still vulnerable
2
False positives withdrawn

Here is the severity distribution of the 36 published advisories, using the rating published with each ZSL advisory. A lot of these are deploy-gated or gadget-gated and carry a wider range in the finding itself, so read the bar as a shape, not a verdict:

3
15
15
3
Critical · 3 High · 15 Medium · 15 Low · 3

The three Criticals are all remote code execution: HugeGraph (sandbox escape), Submarine (auth bypass), and SystemDS (unauthenticated federated-worker deserialization). The long Medium-and-Low tail is mostly memory-safety DoS in the native-C corner of Apache: RocketMQ-C++, ActiveMQ-CPP, Qpid Proton-C, MiNiFi C++, NuttX. That corner is under-audited, and it paid out over and over.

What kind of bugs

Severity is only half the picture, and honestly the less interesting half. Here is the spread by vulnerability class: one foundation handed up ten distinct bug families in five days.

10classes
  • Denial of service8
  • Missing auth / bypass6
  • Java deserialization5
  • Memory corruption3
  • Path traversal / file write3
  • Injection (SQL / LDAP)3
  • Info disclosure / file read3
  • SSRF2
  • XXE2
  • Sandbox escape1

The slice I did not expect to be writing up: that "memory corruption" wedge includes a kernel-space out-of-bounds write in the NuttX RTOS 6LoWPAN decompressor (ZSL-2026-6019). A real kernel 0-day, inside an Apache project, from a desk (sometimes a sofa), in under a week. The other two in that wedge are an out-of-bounds read in the RocketMQ C++ client and a heap overflow in Celix's EDEF parser.

The Zero Science Lab Apache advisory index, ZSL-2026-6038 down to ZSL-2026-6003
MoAB acrostic!

AI in the loop: fast triage, hard verification

If this writeup is about one thing beyond the bugs, it is AI in offensive security research: what the model is genuinely for, where it lies, and who stays accountable for the verdict.

A single human cannot read 345 Apache codebases in five days. This includes me :P. What made the pace possible was pairing an AI assistant (Anthropic's Claude) with a verification protocol I refused to bend. The AI did the part it is genuinely good at: fan out across unfamiliar source trees and recognize dangerous shapes. An unfiltered ObjectInputStream. A new File(dir, entry.getName()) with no canonical check. A recursive-descent parser with no depth cap. A servlet route that quietly never reaches the auth filter. It flags them, ranks them, and then a human goes and chases them.

But a static flag is a hypothesis, not a verdict. That is the same lesson our tuktam / LightFTP work turned on, and I did not forget it here. Nothing shipped on a model's say-so. Every finding got traced to the sink in the exact released version, then live-triggered against the real artifact: the pinned Maven jar on a harness, the compiled C binary under a sanitizer, the container running the actual service. That protocol is exactly what caught both false positives. The AI, and a public PR, called Avro-C a crash; the real libavro returned ENOMEM and never faulted. The model makes you fast. It does not get to decide what is true.

And it failed in both directions, which is the part worth sitting with. It quit on real bugs: it decided OzHera's SQL sink "wasn't reachable" when it plainly was, and Doris took a second pass before it would believe its own finding. It was also certain about bugs that were not there, the two retractions above, which it would have sworn on. A model is a superhuman generator of hypotheses and a subhuman judge of them. Infinite confidence, zero calibration, and the whole job was being the calibration.

Where the bugs actually were

The hardened core of Apache is a fortress. Struts, Shiro, the Dubbo and PDFBox cores: everyone attacks them, git blame is wall-to-wall CVE fixes, and I found almost nothing there. The bugs clustered in three softer places, and once I saw the shape I went looking for it on purpose:

Two of the projects I reported against, Submarine and jclouds, were retired to the Apache Attic during the research window. Retirement does not un-deploy software: a freshly-retired project with a live unauthenticated RCE still sitting on Maven is exactly the supply-chain shape this whole thing was about.

Most of these need no framework to fire. The DevLake SSRF, for one, is the entire exploit:

curl -s -X POST "$TARGET/api/plugins/github/test" \ -H 'Content-Type: application/json' \ --data '{"endpoint":"http://169.254.169.254/latest/meta-data/","authMethod":"AccessToken"}'

No token, no session. The server resolves and fetches the cloud-metadata endpoint, then reflects the body back in its own JSON error (ZSL-2026-6037, CWE-918). That is the mundane end of the range. The fun end was a Groovy Finalizer thread strolling out of HugeGraph's SecurityManager sandbox to pop a live shell, and a crafted 802.15.4 frame writing past a static buffer in the NuttX kernel. One curl to one kernel write, same week.

The security-model shield

The most repeated pattern of the whole operation was not a bug class. It was a sentence. Over and over, a genuinely reachable vulnerable path got waved off as "by design", not because the code was safe, but because a threat model had decided the attacker was out of scope. Nine of my findings were declined this way, and in every one of them the vulnerable code still ships in the latest release:

A threat model is a real and useful thing. But "the attacker is assumed trusted" is a scoping choice, not a property of the code, and it does not make readObject on an attacker-controlled stream safe. I published these reframed, honestly, as insecure-default / missing-authentication issues, because the operator who only reads the changelog will never learn that the default leaves them exposed. The ASF is entitled to its triage opinion. The vulnerable byte sequence is still in the jar.

And none of this is hidden; it is written down. Apache Commons, whose libraries gave me two of these, states the rule plainly: "The Commons libraries are low-level libraries typically designed to work with input that is either trusted or validated/sanitized by the application using the library. It is unsafe to provide possibly malicious input to Commons libraries unless otherwise specified." On deserialization it is blunter still: "We do not consider this a vulnerability in Commons." - Apache Commons, Security Model.
A curious symmetry. I leaned on AI to find these bugs quickly. More than once, the reply that declined one read unmistakably as if it had been drafted by AI too. Same kind of assistant, same cadence, triaging on both sides of the disclosure. I will let you decide what that says about where the human security reviewer is headed.

CVEs, silent fixes, and the PR trail

Nine CVE IDs attach to the batch. Eight of them credit ZSL directly. One, Impala, is an independent-analysis duplicate of an earlier report, June 10, by zhaokaifei. o/

Not every outcome was a clean assignment. Qpid Proton-C got silently fixed in 0.41.1 before any advisory existed; the PMC only confirmed the vulnerability after I published against the already-released fix, and the CVE landed later still. PDFBox was quietly capped with no CVE at all. On the better side, BuildStream merged a fix and turned my PoC into a credited regression test, and I offered hardening PRs to a few projects whose maintainers declined the vuln but took the patch (Doris, Avatica's allow-list, a review of Paimon's fix). A couple of those contribution threads are still open as I write this.

The HugeGraph finding earns its own line. It is a live, end-to-end sandbox escape, a residual of CVE-2024-27348 that walks straight past the thread-name guard the original fix added, without ever touching reflection. The mechanism is written up in full in our HugeGraph SecurityManager sandbox-escape paper.

Still open: 0-days, silence, and unfinished threads

A month of bugs ends on a deadline. The vulnerabilities do not. As I publish this:

The full ledger

All 36 published advisories, in publication order. The Shield column marks whether the vendor sheltered vulnerable code behind a security-model or "by design" decision instead of fixing it.

ZSL IDDateProject / VersionVulnerabilityCWE SeverityShieldCVE / Status
600311.09Impala 4.5.1Insufficient authorization, STORED BY JDBC driver.url remote JAR class-load → RCE862/494HighnoCVE-2026-65181 (dup)
600420.09PDFBox 2.0.37 / 3.0.8Flate PNG-predictor disproportionate heap allocation → OOM DoS789Lownofixed, no CVE
600524.09BuildStream 2.8.0Tar symlink traversal → host arbitrary file write59/22MediumnoCVE-2026-82331
600628.09Karaf 4.4.11JAAS LDAP login-module filter injection90MediumnoCVE-2026-90979
600705.10Submarine 0.8.0Spoofable User-Agent auth bypass → unauthenticated RCE290/807Criticalnono CVE (Attic)
600806.10Commons JCS 3.2.1Unauthenticated Java deserialization on the TCP lateral cache → RCE502Highyesby-design, no CVE
600906.10Curator 5.9.0Unsafe Jackson @JsonTypeInfo(Id.CLASS) discovery deser → RCE502Highnono CVE
601007.10Ratis ≤3.3.1Unfiltered Java deser of RPC reply exception bytes → client RCE502Highnono response, 0-day
601107.10SeaTunnel 3.0.0Unauthenticated REST /submit-job + DynamicCompile → RCE306/94Highyesby-design, no CVE
601208.10OzHera 2.2.6Authenticated SQL injection in Doris-backed log search89HighnoCVE-2026-80277
601308.10Avatica ≤1.29.0JDBC connection-property injection → backend file read / deser610/502Mediumyesby-design, no CVE
601409.10HugeGraph 1.7.0SecurityManager sandbox escape via Groovy Finalizer thread → RCE693/94Criticalnoresidual of CVE-2024-27348
601511.10Celix 2.4.0EDEF endpoint-descriptor XML parser heap buffer overflow122/120Highnoaccepted, embargoed, CVE likely
601612.10YuniKorn 1.9.0LDAP group-provider index panic → DoS248LownoCVE-2026-78243
601712.10ResilientDB 1.12.0Pre-auth unbounded malloc in TCP acceptor → OOM DoS789/770Mediumnono CVE
601813.10Airflow 3.3.2Secrets-masker deep-nesting leak → cleartext secrets in logs200/532MediumnoCVE-2026-113504
601913.10NuttX 13.1.06LoWPAN HC06 decompressor kernel out-of-bounds write787MediumnoCVE requested
602014.10IoTDB 2.0.11Authenticated SSRF via CREATE FUNCTION ... USING URI918Mediumyesby-design, no CVE
602115.10Bigtop Manager 1.1.0Tarball Zip-Slip / symlink → arbitrary file write (root) → RCE22/59Highnono CVE
602216.10MINA Vysper 0.7Unbounded XML token buffer → pre-auth OOM DoS400/789HighnoCVE-2026-115721
602316.10Doris 4.1.5JDBC catalog unsafe props → arbitrary file read610/200Mediumyesby-design, no CVE
602418.10SystemDS 3.4.0Unauthenticated federated-worker Java deserialization → RCE502/306Criticalyesprompted their 1st SECURITY.md, then declined
602519.10Qpid Dispatch 1.19.0Unbounded AMQP annotation recursion → stack-overflow DoS674MediumnoCVE-2026-88763
602620.10Paimon 2.1.0MySQL CDC source → arbitrary file read610/200Mediumyesby-design, will harden
602720.10jclouds 2.7.0XXE in provider-response SAX parsing → file read / SSRF611MediumnoAttic, no CVE
602821.10AsterixDB 0.9.9Unauthenticated CC HTTP API: SQL++ exec + /admin/shutdown306/284Highnono response, 0-day
602922.10MiNiFi C++ 1.0.0Uncontrolled allocation from wire length → DoS789Lowyesby-design, no CVE
603022.10CarbonData 2.3.2Index Server Hadoop-IPC Java deserialization → RCE502/306Highnono CVE
603123.10Qpid Proton-C 0.40.0Unbounded AMQP decoder recursion → stack-overflow DoS674MediumnoCVE-2026-105042 (silent fix 0.41.1)
603224.10RocketMQ Client C++ 2.2.0RemotingCommand::Decode unchecked headLen → OOB read125Mediumnono CVE
603324.10Karaf Cellar 4.4.8Insecure Hazelcast default → unauth cluster-join → cross-node RCE1188/306/94HighnoPMC-confirmed, no CVE
603426.10ActiveMQ-CPP 3.9.5OpenWire byte-array unmarshal unbounded resize → DoS789Mediumnono CVE (dormant)
603527.10Dubbo Admin 0.6.0Hardcoded default JWT signing secret → offline-forged admin bypass798/321Highnodocumented default, no CVE
603628.10Commons Digester 3.2XML external entity injection by default611Mediumyesby-design, no CVE
603729.10DevLake (incubating) 1.0.3Unauthenticated server-side request forgery via connection endpoint918/306Highnonot reported, 0-day
603830.10Ambari 3.0.0Management-pack Zip-Slip → arbitrary file write → RCE (admin)22/918HighnoCVE requested

Withdrawn as false positives (not shipped): Derby LDAP filter injection (the released source escapes correctly; I had tested a stale mirror) and Avro-C negative-length write (unreachable; real libavro returns ENOMEM, and the "crash" was AddressSanitizer-only).

Takeaways

Three things the operation made concrete for me. First, AI collapses the discovery timeline: thirty-six confirmed 0-days across hundreds of codebases in five days was not realistic two years ago. But that only holds when it is chained to real-artifact verification, which is also the thing that keeps it honest enough to print retractions. Second, "by design" is carrying an enormous amount of weight in Apache triage, and a threat model that assumes the attacker away is a scoping choice the operator running the default config inherits as risk. Third, the native-C periphery of Apache is under-watched and keeps paying out memory-safety bugs. Whatever the security model says, the code is still vulnerable.

References