Summary
Calibre's eReader PDB readers (both 132-byte and 202-byte header variants) contain path traversal vulnerabilities that allow arbitrary file writes with arbitrary extension and arbitrary content anywhere the user has write permissions. Files are written in 'wb' mode, silently overwriting existing files. This can lead to potential code execution and denial of service through file corruption.
Details
Confirmed on: Calibre 9.2.1, Windows 11 (x64)
Trigger: Converting a malicious PDB file via GUI ("Convert books")
Affected Components:
src/calibre/ebooks/pdb/ereader/reader132.py
src/calibre/ebooks/pdb/ereader/reader202.py
Vulnerable Code — reader132.py (Line 89):
name = data[4:4 + 32].strip(b'\x00').decode(self.encoding or 'cp1252') # ATTACKER CONTROLLED
img = data[62:] # ATTACKER CONTROLLED
No os.path.basename(), no extension check, no content validation.
Vulnerable Code — reader132.py (Lines 159-165):
with CurrentDir(os.path.join(output_dir, 'images/')):
for i in range(self.header_record.num_image_pages):
name, img = self.get_image(self.header_record.image_data_offset + i)
with open(name, 'wb') as imgf: # PATH TRAVERSAL
imgf.write(img) # ARBITRARY FILE WRITE
Vulnerable Code — reader202.py (Lines 67-68):
if data.startswith(b'PNG'): # FORMAT MARKER ONLY
name = data[4:4 + 32].strip(b'\x00') # ATTACKER CONTROLLED
img = data[62:] # ATTACKER CONTROLLED
The data.startswith(b'PNG') check is a record-type marker distinguishing image sections from text sections — it does not constrain the filename extension or file content.
Attack Flow:
- Malicious PDB file contains image records with traversal paths (e.g.,
../../../../../../pdb_rce.bat) in the 32-byte name field and batch script content in the data field
get_image() reads the 32-byte name from binary data — no sanitization, no extension check
extract_content() enters images/ subdirectory via CurrentDir
open(name, 'wb') resolves ../ traversal, writes outside images/ directory
- Attacker-controlled content is written to the traversed path, overwriting any existing file
PoC:
> python3 create_poc.py
[+] poc_132.pdb
[+] poc_202.pdb
# Convert via GUI "Convert books"
# Both write pdb_rce.bat to %APPDATA% and %USERPROFILE%
Writing image ../../../../../pdb_rce.bat to images/ ← wrote to %APPDATA%
Writing image ../../../../../../pdb_rce.bat to images/ ← wrote to %USERPROFILE%
Confirmed:
.bat files written (not .png) — no extension restriction
- Batch script content written (not image data) — no content validation
- Existing files silently overwritten on repeat conversion
Proof Files:
create_poc.py — Standalone PoC generator (creates both poc_132.pdb and poc_202.pdb)
create_poc.py
Summary
Calibre's eReader PDB readers (both 132-byte and 202-byte header variants) contain path traversal vulnerabilities that allow arbitrary file writes with arbitrary extension and arbitrary content anywhere the user has write permissions. Files are written in
'wb'mode, silently overwriting existing files. This can lead to potential code execution and denial of service through file corruption.Details
Confirmed on: Calibre 9.2.1, Windows 11 (x64)
Trigger: Converting a malicious PDB file via GUI ("Convert books")
Affected Components:
src/calibre/ebooks/pdb/ereader/reader132.pysrc/calibre/ebooks/pdb/ereader/reader202.pyVulnerable Code — reader132.py (Line 89):
No
os.path.basename(), no extension check, no content validation.Vulnerable Code — reader132.py (Lines 159-165):
Vulnerable Code — reader202.py (Lines 67-68):
The
data.startswith(b'PNG')check is a record-type marker distinguishing image sections from text sections — it does not constrain the filename extension or file content.Attack Flow:
../../../../../../pdb_rce.bat) in the 32-byte name field and batch script content in the data fieldget_image()reads the 32-byte name from binary data — no sanitization, no extension checkextract_content()entersimages/subdirectory viaCurrentDiropen(name, 'wb')resolves../traversal, writes outsideimages/directoryPoC:
Confirmed:
.batfiles written (not.png) — no extension restrictionProof Files:
create_poc.py— Standalone PoC generator (creates bothpoc_132.pdbandpoc_202.pdb)create_poc.py