PoC Archive PoC Archive
Critical CVE-2025-30065 patched

Apache Parquet-Avro Schema Deserialization RCE/SSRF — Incomplete-Fix Bypass (CVE-2025-30065)

by micrictor · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-30065
Category
misc
Affected product
Apache Parquet Java (parquet-avro module)
Affected versions
parquet-avro < 1.15.1 fully vulnerable (Vector A, java-class STRING fields); parquet-avro 1.15.1 and later remain vulnerable to a lower-severity bypass (Vector B, record name/namespace resolution) that the official fix does not cover
Disclosed
2026-07-06
Patch status
patched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / Researchermicrictor
CVE / AdvisoryCVE-2025-30065
Categorymisc
SeverityCritical
CVSS Score9.8 (per NVD)
StatusPoC
Tagsrce, ssrf, unsafe-deserialization, parquet-avro, avro-schema, java-class, jvm, cwe-502, cwe-20, incomplete-fix, java
RelatedN/A

Affected Target

FieldValue
Software / SystemApache Parquet Java (parquet-avro module)
Versions Affectedparquet-avro < 1.15.1 fully vulnerable (Vector A, java-class STRING fields); parquet-avro 1.15.1 and later remain vulnerable to a lower-severity bypass (Vector B, record name/namespace resolution) that the official fix does not cover
Language / PlatformJava 17, Maven, JVM-based data pipelines (Spark/Flink/Hadoop-style consumers using AvroParquetReader)
Authentication RequiredNo
Network Access RequiredYes (attacker must get a crafted Parquet file into a victim ingestion path; SSRF variant requires attacker-reachable callback listener)

Summary

CVE-2025-30065 is an unsafe class-instantiation vulnerability in Apache Parquet Java’s parquet-avro module: crafted Avro schema metadata embedded in a Parquet file can force the reading JVM to load and instantiate attacker-named classes. The official 1.15.1 patch introduced ReflectClassValidator to block the most dangerous vector (java-class on STRING fields, which allows single-argument String constructors such as javax.swing.JEditorPane(String url) to be called with attacker-controlled input, yielding confirmed SSRF). This PoC repository shows that fix is incomplete: a second vector — setting the Avro record’s name/namespace to the target class’s fully-qualified name — still causes SpecificData.getClass(schema) to call Class.forName() and invoke a no-arg constructor on every version including patched 1.15.1, because ReflectClassValidator is never consulted on that code path. The repository includes working demos for both vectors plus an honest write-up concluding the bypass is real but, absent a known no-arg-constructor gadget with dangerous side effects, its practical impact is more limited (DoS/file-write/environment-dependent JNDI) than the original CVSS 10.0 rating for the pre-fix vulnerability would suggest.


Vulnerability Details

Root Cause

Two independent unsafe reflection paths exist in parquet-avro’s Avro-schema-driven record materialization:

  • Vector A — java-class on STRING fields (fixed in 1.15.1): AvroRecordConverter.getStringableClass() reads a java-class property from a STRING field’s schema and loads it via ClassUtils.forName(). FieldStringableConverter then looks up Constructor(String.class) and calls ctor.newInstance(fieldValue) for every row, where both the class name and the string argument are attacker-controlled. 1.15.1 added ReflectClassValidator.PackageValidator, which throws SecurityException: Forbidden class ... before the class loads.
  • Vector B — record name/namespace (NOT fixed in 1.15.1): When constructing an AvroIndexedRecordConverter/AvroRecordConverter, getDatumClass() calls SpecificData.getClass(schema), which resolves schema.getFullName() (namespace + name) via Class.forName() directly — ReflectClassValidator is never invoked on this path. This works identically on compat/non-compat mode and GenericData/SpecificData models, on all versions including 1.15.1.

Vector A payload schema (from src/main/java/com/example/WriteJEditorPanePayload.java):

1
2
3
4
5
6
7
String schemaJson = "{"
    + "\"type\": \"record\",\"name\": \"Data\",\"namespace\": \"com.example\","
    + "\"fields\": [{\"name\": \"url\",\"type\": {"
    + "  \"type\": \"string\","
    + "  \"java-class\": \"javax.swing.JEditorPane\""
    + "}}]}";
// record.put("url", "http://127.0.0.1:8888/ssrf-callback");

Vector B payload schema (from src/main/java/com/example/WriteSsrfPayload.java), which bypasses the 1.15.1 validator entirely by encoding the target class in the schema’s own name/namespace instead of a java-class property:

1
2
3
4
5
6
String schemaJson = "{"
    + "\"type\": \"record\","
    + "\"name\": \"SsrfCanary\","
    + "\"namespace\": \"com.example\","
    + "\"fields\": [{\"name\": \"data\", \"type\": \"string\"}]"
    + "}";

Attack Vector

  1. Attacker crafts a Parquet file whose embedded Avro schema either (a) sets a STRING field’s java-class property to a dangerous class with a useful String-argument constructor (Vector A, pre-1.15.1 only), or (b) sets the schema’s name/namespace to the fully-qualified name of a class already present on the victim’s classpath (Vector B, all versions).
  2. Attacker delivers the file into any workflow that parses it with AvroParquetReader (batch upload, object-store ingestion, ETL/pipeline import, ad-hoc analytics query).
  3. On read, parquet-avro resolves the schema and calls Class.forName() on the attacker-supplied class name, then invokes either a String constructor (Vector A, attacker-controlled argument) or a no-arg constructor (Vector B).
  4. Depending on the target class, this triggers a static initializer and/or constructor side effect: SSRF (JEditorPane(String url)), DoS (org.apache.derby.jdbc.EmbeddedDriver booting an embedded DB engine), arbitrary file creation (java.util.logging.FileHandler), or environment-dependent JNDI lookups (javax.naming.InitialContext).

Impact

Vector A (pre-1.15.1) gives confirmed SSRF using only JDK classes, and potential RCE if a classpath gadget exposes a dangerous single-String-argument constructor. Vector B survives the official patch on all versions including 1.15.1 and, while limited to a no-arg-constructor primitive with no confirmed public RCE gadget, still enables arbitrary class loading/static-initializer execution, DoS, and unauthorized file writes on any JVM that parses attacker-influenced Parquet data — a materially different (lower but non-trivial) risk than the pre-fix CVSS 10.0 rating implies.


Environment / Lab Setup

OS:              Linux/macOS
JDK:              Java 17
Build:            Maven (mvn -q clean package / shaded jar via maven-shade-plugin)
Dependencies:     org.apache.parquet:parquet-avro:1.15.1 (pom.xml pins the patched version to demonstrate the bypass)
                  org.apache.parquet:parquet-hadoop:1.15.1
                  org.apache.avro:avro:1.12.0
                  org.apache.hadoop:hadoop-common:3.3.6 / hadoop-mapreduce-client-core:3.3.6
                  org.slf4j:slf4j-simple:2.0.9
Vulnerable dep for Vector A demo: swap parquet.version to a pre-1.15.1 release (e.g. 1.15.0) in pom.xml

Proof of Concept

PoC Script

See the Java sources in src/main/java/com/example/ in this folder, and the detailed writeup in parquet_avro_rce_report.md:

  • WritePoC.java / WritePoC2.java — write malicious Parquet payloads for Vector B (record name) and the java-class STRING field vector
  • WriteSsrfPayload.java / SsrfDemo.java — Vector B end-to-end SSRF demo (bypasses 1.15.1 fix) via SsrfCanary’s static initializer
  • WriteJEditorPanePayload.java / JEditorPaneSsrfDemo.java — Vector A SSRF demo via javax.swing.JEditorPane (blocked on >= 1.15.1)
  • CanaryClass.java / SsrfCanary.java — canary gadget classes proving arbitrary class loading/instantiation
  • ReadPoC.java — reads a malicious Parquet file under four reader configurations to show which trigger the vulnerable code path
1
2
3
4
5
6
7
mvn -q clean package

java -cp target/parquet-avro-poc-1.0-SNAPSHOT.jar com.example.WriteSsrfPayload ssrf-payload.parquet
java -cp target/parquet-avro-poc-1.0-SNAPSHOT.jar com.example.SsrfDemo ssrf-payload.parquet
java -cp target/parquet-avro-poc-1.0-SNAPSHOT.jar com.example.WriteJEditorPanePayload jeditorpane-ssrf.parquet 8888
java -cp target/parquet-avro-poc-1.0-SNAPSHOT.jar com.example.JEditorPaneSsrfDemo jeditorpane-ssrf.parquet
java -cp target/parquet-avro-poc-1.0-SNAPSHOT.jar com.example.ReadPoC malicious.parquet

Detection & Indicators of Compromise

Signs of compromise:

  • Static-initializer or constructor side effects (network calls, file writes) observed during Parquet/Avro schema resolution, not during actual row-data processing
  • Outbound requests from data-pipeline nodes to unfamiliar hosts shortly after ingesting a new Parquet file
  • SecurityException: Forbidden class entries in parquet-avro reader logs
  • Unexplained derby.log files or new java.util.logging.FileHandler output on hosts that parse untrusted Parquet data

Remediation

ActionDetail
Primary fixUpgrade to a parquet-avro release addressing CVE-2025-30065 (1.15.1) for Vector A; note that Vector B (record name/namespace resolution via SpecificData.getClass()) is not covered by the 1.15.1 ReflectClassValidator fix as of this repo’s research — track upstream Apache Parquet advisories for a follow-up patch
Interim mitigationNever parse Parquet/Avro files from untrusted sources with default AvroParquetReader settings; validate/sanitize schema metadata (record name, namespace, and any java-class properties) before read; restrict SERIALIZABLE_PACKAGES/classpath exposure of dangerous gadget classes; run ingestion JVMs with restricted egress and minimal classpath to limit exploitable no-arg-constructor gadgets

References


Notes

Mirrored from https://github.com/micrictor/parquet-avro-rce on 2026-07-06. This repo does not merely reproduce CVE-2025-30065 — it demonstrates that the official 1.15.1 fix (ReflectClassValidator) only blocks one of two exploitable class-instantiation vectors (java-class on STRING fields). The second vector, driven by the Avro schema’s record name/namespace, bypasses the validator entirely on all tested versions including 1.15.1, though the author’s own analysis (parquet_avro_rce_report.md) honestly notes no confirmed RCE/SSRF gadget has been found for that no-arg-constructor-only primitive on standard classpaths, so real-world severity of the bypass is lower than the original CVE’s CVSS 10.0 rating.