Skip to content

CVE-2026-43825: Verified Reproduction

CVE-2026-43825: Apache OpenNLP SvmDoccatModel unsafe deserialization

CVE-2026-43825 is verified against apache/opennlp · github. Affected versions: 3.0.0-M1 before 3.0.0-M4 (only on 3.x line; introduced in OPENNLP-1808). Fixed in 3.0.0-M4. Vulnerability class: Deserialization. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00279.

REPRO-2026-00279 apache/opennlp · github Deserialization Jul 9, 2026 CVE entry .txt
Severity
HIGH
CVSS
7.3
Confidence
HIGH
Reproduced in
14m 59s
Tool calls
168
Spend
$2.27
01 · Overview

What Is CVE-2026-43825?

CVE-2026-43825 is a high-severity unsafe deserialization vulnerability (CWE-502) in Apache OpenNLP's SvmDoccatModel.deserialize(InputStream), part of the opennlp-ml-libsvm module. Pruva reproduced it (reproduction REPRO-2026-00279).

02 · Severity & CVSS

CVE-2026-43825 Severity & CVSS Score

CVE-2026-43825 is rated high severity, with a CVSS base score of 7.3 out of 10.

HIGH threat level
7.3 / 10 CVSS base
Weakness CWE-502 Deserialization of Untrusted Data — Deserialization of Untrusted Data

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected apache/opennlp Versions

apache/opennlp · github versions 3.0.0-M1 before 3.0.0-M4 (only on 3.x line; introduced in OPENNLP-1808) are affected.

How to Reproduce CVE-2026-43825

$ pruva-verify REPRO-2026-00279
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00279/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-43825

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Crafted Java serialized byte stream containing a MaliciousGadget object (Serializable class with readObject() side-effect) fed to SvmDoccatModel.deserialize(InputStream)

Attack chain
  1. SvmDoccatModel.deserialize()
  2. new ObjectInputStream(in)
  3. readObject()
  4. MaliciousGadget.readObject() [arbitrary code execution before cast to SvmDoccatModel]
How the agent worked 366 events · 168 tool calls · 15 min
15 minDuration
168Tool calls
96Reasoning steps
366Events
1Dead-ends
Agent activity over 15 min
Support
18
Repro
131
Judge
45
Variant
168
0:0014:59

Root Cause and Exploit Chain for CVE-2026-43825

Versions: Apache OpenNLP 3.x before 3.0.0-M4 (the libsvm document categorization module was introduced via OPENNLP-1808 and exists only on the 3.x line)Fixed: in: 3.0.0-M4 (commit 3cf42d4a0145cefd9dd06138873a91312052c767, PR #1029, OPENNLP-1823)

Apache OpenNLP 3.x before 3.0.0-M4 contains an unsafe Java deserialization vulnerability in SvmDoccatModel.deserialize(InputStream) within the opennlp-ml-libsvm module. The method creates a java.io.ObjectInputStream and calls readObject() on an attacker-supplied byte stream without installing an ObjectInputFilter. Because ObjectInputStream.readObject() materialises every class referenced in the stream before the result is cast to SvmDoccatModel, any gadget class available on the consumer's classpath can execute arbitrary code during deserialization — before the caller ever inspects the returned object.

  • Package/component affected: org.apache.opennlp:opennlp-ml-libsvm — class opennlp.tools.ml.libsvm.doccat.SvmDoccatModel
  • Affected versions: Apache OpenNLP 3.x before 3.0.0-M4 (the libsvm document categorization module was introduced via OPENNLP-1808 and exists only on the 3.x line)
  • Fixed in: 3.0.0-M4 (commit 3cf42d4a0145cefd9dd06138873a91312052c767, PR #1029, OPENNLP-1823)
  • Risk level: High (CVSSv3 7.3 per Red Hat advisory)
  • Consequences: Remote code execution in any JVM process that loads SvmDoccatModel instances from untrusted or semi-trusted sources. The method is public static, so any caller can pass an untrusted stream directly. Apache OpenNLP itself does not ship a known gadget chain, so the realistic risk is to downstream applications that embed the libsvm module alongside vulnerable transitive dependencies (e.g., CommonsCollections, Groovy, Spring).

Impact Parity

  • Disclosed/claimed maximum impact: Code execution — arbitrary code execution via a crafted serialized stream when a gadget chain is present on the classpath.
  • Reproduced impact from this run: Code execution — a custom MaliciousGadget class (implementing Serializable with a readObject() side-effect) was placed on the classpath and its readObject() method executed during SvmDoccatModel.deserialize(), writing a marker file as proof of arbitrary code execution. The cast to SvmDoccatModel threw ClassCastException only AFTER the gadget code had already run.
  • Parity: full — the proof demonstrates the exact mechanism described in the advisory: ObjectInputStream.readObject() materialises foreign objects before the cast, allowing arbitrary code execution during deserialization.
  • Not demonstrated: A real-world gadget chain (e.g., CommonsCollections) was not used; instead, a purpose-built gadget class simulated the same mechanism. This is appropriate because the CVE explicitly states "Apache OpenNLP itself does not ship a known gadget chain."

Root Cause

The vulnerable deserialize method in SvmDoccatModel.java (before 3.0.0-M4) is:

public static SvmDoccatModel deserialize(InputStream in) throws IOException, ClassNotFoundException {
    try (ObjectInputStream ois = new ObjectInputStream(in)) {
      return (SvmDoccatModel) ois.readObject();
    }
}

No ObjectInputFilter is installed. Java's ObjectInputStream.readObject() resolves and instantiates every class descriptor in the serialized stream, invoking each class's readObject(), readResolve(), or other deserialization hooks as part of the process. The cast to SvmDoccatModel occurs only AFTER the entire object graph has been deserialised. This means:

  1. An attacker crafts a serialized byte stream containing a gadget class (any Serializable class with a dangerous readObject() method).
  2. The stream is passed to SvmDoccatModel.deserialize().
  3. ObjectInputStream.readObject() encounters the gadget class, loads it from the classpath, and invokes its readObject() method — arbitrary code executes.
  4. Only then does the cast (SvmDoccatModel) fail with ClassCastException — but the damage is already done.

Fix (commit 3cf42d4a): The fix adds an ObjectInputFilter via ois.setObjectInputFilter(buildFilter(limits)) before readObject(). The filter:

  • Allow-lists only the specific classes that can legitimately appear in a SvmDoccatModel serialization graph (OpenNLP types, zlibsvm domain types, libsvm structures, and a minimal set of JDK types).
  • Bounds graph depth (64), references (5,000,000), and array length (10,000,000).
  • Rejects any class not on the allow-list with ObjectInputFilter.Status.REJECTED, causing readObject() to throw InvalidClassException BEFORE the foreign class is materialised.

Fix commit: 3cf42d4a0145cefd9dd06138873a91312052c767 Fix PR: https://github.com/apache/opennlp/pull/1029 (OPENNLP-1823)

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh
  2. What the script does:
    • Installs JDK 21 and Maven (required by OpenNLP 3.x).
    • Clones the Apache OpenNLP repository (or reuses the project cache).
    • Checks out and builds the vulnerable commit (3cb7232e) — the parent of the fix — which has no ObjectInputFilter.
    • Compiles a MaliciousGadget class (a Serializable class whose readObject() writes a marker file) and a Poc harness that serializes the gadget and feeds the bytes to SvmDoccatModel.deserialize().
    • Runs the PoC against the vulnerable build: the gadget's readObject() executes, a marker file is created, and ClassCastException is thrown after execution.
    • Checks out and builds the fixed commit (3cf42d4a) which adds ObjectInputFilter.
    • Runs the same PoC against the fixed build: InvalidClassException is thrown by the filter, no code executes, no marker file is created.
    • Writes runtime_manifest.json with proof artifacts and exits 0 if the vulnerability is confirmed.
  3. Expected evidence:
    • bundle/logs/poc_vulnerable.log — shows [GADGET EXECUTED] and CODE EXECUTION CONFIRMED with exit code 10.
    • bundle/logs/gadget_executed_vulnerable.txt — the marker file written by the gadget's readObject().
    • bundle/logs/poc_fixed.log — shows ObjectInputFilter rejected foreign class with InvalidClassException and exit code 20.

Evidence

Vulnerable version PoC output (bundle/logs/poc_vulnerable.log):
[*] Calling SvmDoccatModel.deserialize() with malicious stream...
[GADGET EXECUTED] Arbitrary code ran during deserialization: id;whoami;uname -a
[GADGET EXECUTED] Marker file written to: /tmp/opennlp_poc_marker_vuln.txt
[!] VULNERABLE version: readObject() completed, cast failed AFTER
[!] Exception: java.lang.ClassCastException: class MaliciousGadget cannot be cast to class opennlp.tools.ml.libsvm.doccat.SvmDoccatModel
[!!!] CODE EXECUTION CONFIRMED: gadget readObject() ran!
Gadget marker file (bundle/logs/gadget_executed_vulnerable.txt):
DESERIALIZATION_GADGET_EXECUTED: id;whoami;uname -a
Thread: main
Time: 2026-07-09T18:16:03.331869217Z
Class: MaliciousGadget
Fixed version PoC output (bundle/logs/poc_fixed.log):
[*] Calling SvmDoccatModel.deserialize() with malicious stream...
[+] FIXED version: ObjectInputFilter rejected foreign class
[+] Exception: java.io.InvalidClassException: filter status: REJECTED
[*] No marker file found — gadget code did NOT execute
VERDICT: FIXED — ObjectInputFilter blocked the foreign class
Source verification:
  • Vulnerable SvmDoccatModel.java: 0 references to ObjectInputFilter (confirmed via grep -c).
  • Fixed SvmDoccatModel.java: 11 references to ObjectInputFilter (confirmed via grep -c).
Environment:
  • JDK: OpenJDK 21.0.11
  • Maven: 3.9.12
  • OS: Ubuntu 26.04 (Linux)
  • Vulnerable commit: 3cb7232ecaccc788e32bf76b7c031fb166840da5
  • Fixed commit: 3cf42d4a0145cefd9dd06138873a91312052c767

Recommendations / Next Steps

  • Upgrade: Users on OpenNLP 3.x before 3.0.0-M4 should upgrade to 3.0.0-M4 immediately.
  • Defense in depth: Even with the ObjectInputFilter, callers should treat serialized SvmDoccatModel streams as untrusted input and verify their provenance before deserialization. The filter is defense-in-depth, not a license to deserialize from untrusted sources.
  • Input validation: Applications that accept model files from end users or third-party sources should add integrity checks (e.g., signatures, checksums) before calling deserialize().
  • Classpath hygiene: Remove unnecessary gadget-chain-capable libraries from the classpath of any process that deserializes OpenNLP models.
  • Testing: The fix includes test cases (SvmDoccatModelTest.java) that verify foreign classes are rejected. Downstream applications should add integration tests that feed crafted streams to deserialize() and verify rejection.

Additional Notes

  • Idempotency: The script was run twice consecutively; both runs produced identical results (exit code 0, vulnerable exit 10, fixed exit 20).
  • Gadget class: The MaliciousGadget class is a purpose-built simulation of a real deserialization gadget. In a real attack, a class from a vulnerable transitive dependency (e.g., org.apache.commons.collections.functors.InvokerTransformer) would serve the same role. The proof demonstrates the deserialization mechanism, not a specific real-world gadget chain.
  • Scope: The vulnerability is in the opennlp-ml-libsvm module only. Other OpenNLP modules use different serialization mechanisms (e.g., the main DoccatModel uses a custom binary format, not Java object serialization).
  • Limitation: The MaliciousGadget class must be on the classpath for the exploit to work, which mirrors the real-world constraint that a gadget-chain-capable library must be present. The CVE description explicitly acknowledges this.

CVE-2026-43825 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:44
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-43825 · REPRO-20
0:02
0:04
web search
0:06
web search
0:09
0:10
0:12
web search
0:15
0:16
web search
0:18
web search
0:26
0:26
extract_facts
no facts extracted
0:27
0:27
0:27
supportrepro
0:29
0:29
0:29
0:30
0:30
0:30
0:30
0:33
0:33
0:34
web search
0:36
0:37
0:38
web search
0:43
08 · How to Fix

How to Fix CVE-2026-43825

Upgrade apache/opennlp · github to 3.0.0-M4 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-43825 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-43825

How does the CVE-2026-43825 deserialization attack work?

An attacker crafts a serialized Java object stream containing a gadget chain and passes it to the public static SvmDoccatModel.deserialize() method. Because the method has no filtering, ObjectInputStream instantiates the gadget classes and triggers their side effects during deserialization, achieving code execution before the SvmDoccatModel cast even occurs. Apache OpenNLP itself does not ship a known gadget chain, so risk depends on gadget classes available via the consumer's transitive dependencies.

Which OpenNLP versions are affected by CVE-2026-43825, and where is it fixed?

Apache OpenNLP 3.0.0-M1 before 3.0.0-M4 is affected (only on the 3.x line, introduced with OPENNLP-1808). It is fixed in 3.0.0-M4, which adds an ObjectInputFilter and resource limits.

How severe is CVE-2026-43825?

It is rated high severity: remote code execution in any JVM process that loads SvmDoccatModel instances from untrusted or semi-trusted sources, since the deserialize method is public static and callable directly by any caller.

How can I reproduce CVE-2026-43825?

Download the verified script from this page and run it in an isolated environment against Apache OpenNLP 3.0.0-M1 through 3.0.0-M3 with the opennlp-ml-libsvm module. It feeds a crafted serialized object stream to SvmDoccatModel.deserialize() and shows the gadget chain executing on the vulnerable build, then confirms 3.0.0-M4 rejects it via ObjectInputFilter.
11 · References

References for CVE-2026-43825

Authoritative sources for CVE-2026-43825 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.