CVE-2026-115906: AES Entry HMAC Authentication Bypass in Rust zip Crate
Vulnerability ID: CVE-2026-115906
CVSS Score: 6.5
Published: 2026-10-29
CVE-2026-115906 is a cryptographic authentication bypass vulnerability in the Rust zip library (zip-rs/zip2) affecting versions from 0.6.0 prior to 9.0.0. In the WinZip AE-x AES decryption reader implementation within src/aes.rs, the HMAC-SHA1-80 authentication tag is only validated when the internal byte counter (data_remaining) decrements to exactly zero. Because this counter is initialized from the unauthenticated compressed_size header field, an attacker can craft a ZIP archive where the entry stream hits premature physical end-of-file (EOF) before data_remaining reaches zero, causing the reader to return Ok(0) and skip HMAC verification entirely.
TL;DR
The Rust zip crate prior to version 9.0.0 fails to verify the HMAC-SHA1-80 authentication tag on AES-encrypted ZIP entries if the underlying stream reaches premature EOF or if the payload length is zero. Combined with AES-CTR ciphertext malleability and omitted CRC-32 checks in AE-2 archives, an attacker without knowledge of the password can modify or forge encrypted archive entries to deliver arbitrary plaintext without detection.
⚠️ Exploit Status: POC
Technical Details
- Vulnerability Type: Cryptographic Authentication Bypass (CWE-354 / CWE-1284)
- Affected Component: WinZip AE-x AES Decryption Reader (src/aes.rs in Rust zip crate)
- Attack Vector: Network / Malicious ZIP Archive (AV:N/AC:L/PR:N/UI:R)
- CVSS v3.1 Score: 6.5 Medium
- EPSS Score: 0.151% (4.17th Percentile)
- Security Impact: High Integrity Compromise (Arbitrary Ciphertext Forgery / Truncation)
- Exploit Status: Proof-of-Concept Available in Upstream Tests
- Patched Version: zip 9.0.0
Affected Systems
- Rust applications using the zip crate (zip-rs/zip2) versions >= 0.6.0, < 9.0.0 to decrypt AES-encrypted ZIP entries
- zip: >= 0.6.0, < 9.0.0 (Fixed in:
9.0.0)
Code Analysis
Commit: ec265db
Fix AES entry HMAC authentication bypass on premature EOF and zero-length entries, and validate compressed_size against AES metadata length.
File: src/aes.rs @@ -140,15 +140,18 @@ impl<R: Read> AesReader<R> { - pub const fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> { - let data_length = compressed_size - - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64; - - Self { + pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> ZipResult<AesReader<R>> { + let metadata_len = (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64; + // we accept 0 size data + if compressed_size < metadata_len { + return Err(invalid!("Compressed size of AES data is invalid")); + } + let data_length = compressed_size - metadata_len; + Ok(Self { reader, aes_mode, data_length, - } + }) } @@ -245,6 +250,10 @@ impl<R: Read> Read for AesReaderValid<R> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { if self.data_remaining == 0 { + // handle case were payload is empty + if self.data_length == 0 { + self.verify_hmac()?; + } return Ok(0); } @@ -262,38 +271,48 @@ impl<R: Read> Read for AesReaderValid<R> { if self.data_remaining == 0 { + self.verify_hmac()?; + } else if read == 0 && self.data_remaining > 0 { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "Read 0 bytes of AES data but still contains remaining data", + )); } Exploit Details
- GitHub: Official regression test and Proof of Concept (hmac_is_bypassed_when_the_entry_ends_early and hmac_is_never_checked_for_a_zero_length_entry) included in the remediation commit.
Mitigation Strategies
- Upgrade the Rust zip crate dependency to version 9.0.0 or later to ensure premature EOF and zero-length payload conditions are properly checked during AES decryption.
- Implement pre-extraction structural validation on untrusted ZIP archives to verify that no entry's local header or declared compressed_size extends into the Central Directory or past the End of Central Directory (EOCD) record.
- Replace or supplement password-based ZIP AE-x encryption with detached digital signatures (e.g., Ed25519) or modern authenticated encryption containers (e.g., age) when archive integrity is security-critical.
Remediation Steps:
- Locate all Rust projects using the zip crate by scanning Cargo.lock files with 'cargo audit' or 'cargo tree -i zip'.
- Update Cargo.toml to specify zip = ">= 9.0.0" and run 'cargo update -p zip'.
- Run test suites to accommodate any major version API adjustments introduced between prior releases and v9.0.0.
- Rebuild and redeploy affected binaries and services.
References
- GitHub Advisory GHSA-8hxf-qr8p-62x4
- Official Fix Commit ec265db0b0602f8ffb9479327397bddefb7e2216
- zip v9.0.0 Release Notes
- NVD Vulnerability Detail: CVE-2026-115906
- CVE.org Record: CVE-2026-115906
- CVEProject cvelistV5 Record
- Wiz Vulnerability Database Advisory: CVE-2026-115906
Read the full report for CVE-2026-115906 on our website for more details including interactive diagrams and full exploit analysis.
