Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
spark-history-zip-symlink — Apache Spark 4.2.0 - High - Remote - File read - History zip follows planted symlink | Kitploit
Tools/GitHubGitHub/abraxas/spark-history-zip-symlink
Vulnerability AnalysisExploitationWeb Application ExploitationInformation GatheringPenetration TestingPapers & Research
GitHubabraxas/spark-history-zip-symlink

spark-history-zip-symlink

Apache Spark 4.2.0 - High - Remote - File read - History zip follows planted symlink

View Repository
66 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Abraxas Labs - spark-history-zip-symlink

abraxaslabs.tech  ·  github.com/abraxas  ·  @abraxas_null  ·  [email protected]  ·  spark-history-zip-symlink

spark-history-zip-symlink

Class: File read Reach: Remote

Apache Spark 4.2.0 (32f72996) - Apache Software Foundation

CVE-2025-54920 locked History Server Jackson so sparkEventFromJson only constructs SparkListenerEvent subclasses. Rolling zipEventLogFiles still dumps every listStatus child via FileSystem.open. LocalFileSystem follows a POSIX symlink. A planted child in eventlog_v2_* is packed into unauth GET /api/v1/applications/{id}/logs. The zip member body is the SHS-UID target. The entry name is the symlink basename.

This is not a re-file of the Jackson RCE. Jackson stays closed. This is not host RCE. Integrity and availability are none.

IDno CVE yet
ClassFile read (rolling zip leftover of the CVE-2025-54920 victim model)
ReachRemote (writer of the History log dir plus unauth GET of the zip)
CWECWE-59, CWE-22
CVSSHigh: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
ProductApache Spark
Affected4.2.0 tag v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8)
AuthPR:L = write to spark.history.fs.logDirectory (same victim as 54920). History UI ACLs default off.
LicenseGNU Affero GPL v3.0
Lab127.0.0.1:18980 only

What an attacker can do

Someone who can write spark.history.fs.logDirectory (default file:/tmp/spark-events) can plant a rolling dir the History Server will zip. Rolling writers default on. History UI ACLs default off. The SHS process UID is the confused deputy.

PrivilegeMethod / sinkLab-proven or inferred
Confidentiality: read a host file the SHS UID can openPlant eventlog_v2_<id>/ with valid appstatus_* plus events_* JSON (SparkListenerLogStart / SparkListenerApplicationStart) plus a symlink child whose target sits outside the log dir. Wait for scan. Unauth GET /api/v1/applications/{id}/logs (OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open)Lab-proven. Zip member eventlog_v2_app-2026-0001/planted-secret body is SPARK-HISTORY-ZIP-SYMLINK-WITNESS. Events member has no witness. Entry name stays the symlink basename, not the target path.
Confidentiality: sibling tenant event log the attacker cannot open (660)Same zip, symlink target is another app's logInferred. Same FileSystem.open. Not exercised.
Confidentiality: keytab, spark-env.sh, /etc/passwd if SHS can read themSame zipInferred. Oracle is a dedicated /secret/host-secret, not those files.
Integrity of SHS or host fileszip is read into the HTTP bodyClosed. Lab-proven none.
Availability of SHS or host fileszip does not delete or truncate the targetClosed. Lab-proven none.
Host RCE / Jackson gadget constructJsonProtocol.sparkEventFromJsonClosed. SparkListenerEvent required. This lab plants only benign Event JSON.
Zip slip of ../ in the entry namePath.getName()Closed. POSIX filename cannot contain /. Entry is eventlog_v2_<id>/<basename>.
Path-string escape of resolveLogPathnew Path(logDir, attempt.logPath)Closed. The Path object stays under the configured log dir. The escape is the inode.
Compaction rewrite / cleaner delete of the targetspark.history.fs.eventLog.rolling.maxFilesToRetain default Integer.MAX_VALUE; cleaner default offClosed. Local fs.delete of a symlink unlinks the link, not the target.
Unauth job submit / ML model loadSECURITY.md FAQ / docs/ml-security.mdClosed. By design, not this leftover.

How I found it

I pinned apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) rather than master or a 4.3 RC. Twenty-eight hunts. Jackson History RCE is closed on this peel (JsonProtocol.scala:965-972). The History Server still pointed at an attacker-writable log dir, which is the same victim model as CVE-2025-54920.

zipEventLogFiles zips all files, not only event files:

override def zipEventLogFiles(zipStream: ZipOutputStream): Unit = {
  val dirEntryName = rootPath.getName + "/"
  zipStream.putNextEntry(new ZipEntry(dirEntryName))
  files.foreach { file =>
    addFileAsZipEntry(zipStream, file.getPath, dirEntryName + file.getPath.getName)
  }
}

addFileAsZipEntry is fileSystem.open then transferTo. LocalFileSystem follows POSIX symlinks. getLogDirInfo comments "handles symbolic links properly" and then walks unresolved Path parents for a display name.

Wrong turns already recorded: treating SPARK-52381 as the whole History story; planting the witness in events_* (the lab fails that); expecting the zip entry to carry the target path; treating resolveLogPath prefix-join as the escape; trying a HiveConnection Event class on this pin.

Lab

python3 spark-history-zip-symlink-Abraxas-Labs.py

or

cd lab
./run.sh

Gadget of EventLogFileReaders.scala rolling zip dump, not a full History Server and not the official spark:4.2.0 image. It does not parse Event JSON through Jackson. Loopback 127.0.0.1:18980. Compose project spark-history-zip-symlink. Rolling dir holds valid appstatus_* + events_* (no witness) plus planted-secret -> /secret/host-secret.

SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS

The fix

Pack only events_* / appstatus_* children, not every listStatus name. Open with no-follow (O_NOFOLLOW / Hadoop no-symlink options) and reject a child whose realpath is outside spark.history.fs.logDirectory. A display-name helper is not a realpath check.

References

  • github.com/apache/spark tag v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8)
  • EventLogFileReaders.scala (addFileAsZipEntry ~48-56, zipEventLogFiles ~267-272)
  • FsHistoryProvider.scala (writeEventLogs ~831-856, resolveLogPath ~866-896)
  • OneApplicationResource.scala (GET .../logs ~128-165)
  • JsonProtocol.scala (sparkEventFromJson ~965-972, Jackson lock)
  • Nearby patched (Jackson construct, not this inode walk): CVE-2025-54920 / SPARK-52381
  • CWE-59 · CWE-22

License

Download Tool