-
Notifications
You must be signed in to change notification settings - Fork 148
Description
Security Advisory: Multiple Cryptographic Vulnerabilities
Reporter: Conner Webber (conner.webber000@gmail.com)
Severity: HIGH
Affected versions: <= 1.6.1
Note: I attempted to file this via GitHub's Private Vulnerability Reporting (PVRA), but it is not enabled on this repository. There is also no SECURITY.md. Maintainer: please consider enabling PVRA for future security reports. I'm also emailing this to github@ricmoo.com.
Finding 1: CBC Padding Oracle (HIGH, CWE-209)
File: pyaes/util.py:51-60
strip_PKCS7_padding() performs incomplete padding validation — it only checks that the last byte is ≤16 but does NOT verify that all padding bytes match. Additionally, pad=0 is accepted (returning empty data). Different exception types for length vs. padding-value errors create distinguishable error paths, enabling a classic Vaudenay padding oracle attack for full plaintext recovery.
PoC sketch: An application that returns different errors for "invalid padding" vs "decryption failed" allows an attacker to recover the full plaintext byte-by-byte without knowing the key.
Finding 2: Default All-Zero IV (HIGH, CWE-329)
File: pyaes/aes.py:378-379, 497-498, 425-426
CBC, OFB, and CFB modes default to iv=None which silently uses an all-zero IV. This makes encryption deterministic (identical plaintexts → identical ciphertexts), violating IND-CPA security. No warning is emitted.
Note: This overlaps with #56 and #57. I'm including it here for completeness of the security audit.
Finding 3: CTR Default Counter=1 with No Nonce (HIGH, CWE-323)
File: pyaes/aes.py:556-562, 278
CTR mode defaults to counter=None → Counter(initial_value=1) with no random nonce. Every encryption with the same key starts from the same counter, producing identical keystreams. If two messages are encrypted with the same key and default counter, XORing the ciphertexts yields the XOR of the plaintexts (two-time pad), which is trivially breakable.
Finding 4: T-table Timing Side-Channel (HIGH, CWE-208)
File: pyaes/aes.py:219-232, 253-265
AES implementation uses T-table lookups indexed by secret-dependent byte values. In pure Python, list indexing is variable-time, creating cache-timing side channels exploitable in shared-host environments.
Finding 5: No Authenticated Encryption (HIGH, CWE-353)
Architectural issue — no GCM, CCM, EAX, or HMAC wrapper is provided. All modes produce malleable ciphertext. An attacker can flip ciphertext bits to predictably alter the plaintext without detection.
Impact
- Finding 1 (padding oracle): Full plaintext recovery without the key in applications that expose error types
- Findings 2-3 (zero IV/counter): Silent production of insecure, deterministic encryption
- Finding 4 (timing): Key recovery in shared environments
- Finding 5 (no AEAD): Ciphertext manipulation without detection
Recommended Remediation
- Validate all padding bytes in
strip_PKCS7_padding(), rejectpad=0 - Require explicit IV/nonce — no silent defaults (aligns with Insecure (because of) default IV provided #56/Raise on default IV #57)
- Add GCM mode or at minimum document that HMAC-then-encrypt is required
- Use constant-time S-box lookups (single byte-indexed array, no T-tables)
References
- Vaudenay, S. "Security Flaws Induced by CBC Padding" (EUROCRYPT 2002)
- Trail of Bits blog on pyaes defaults (referenced in Insecure (because of) default IV provided #56)
- CWE-209, CWE-329, CWE-323, CWE-208, CWE-353