Fix path traversal in Topaz extraction (arbitrary file write)

The Topaz header record "tag" is read verbatim from the untrusted book
file by bookReadString() and then used unsanitized to build the output
filename in extractFiles(). A crafted tag such as "../../foo" let a
malicious .azw/Topaz file write attacker-controlled bytes outside the
extraction directory. The payload content requires no book key, since an
unencrypted record with compressedLength == 0 is returned raw.

Strip the record name to its basename before joining it to destdir so
traversal sequences (../, /, \) can no longer escape, and add an
abspath-based containment check as defense in depth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 18:21:42 -05:00
co-authored by Claude Opus 4.8
parent 7379b45319
commit 768d49094c
+9
View File
@@ -358,7 +358,16 @@ class TopazBook:
destdir = os.path.join(outdir,"page")
if name == b'glyphs':
destdir = os.path.join(outdir,"glyphs")
# The record name is read verbatim from the (untrusted) book
# file, so strip any directory components to stop a crafted
# name (e.g. "../../foo") from writing outside of destdir.
fname = os.path.basename(fname)
outputFile = os.path.join(destdir,fname)
# Defense in depth: never let the resolved path escape destdir.
real_out = os.path.abspath(outputFile)
real_dir = os.path.abspath(destdir)
if not (real_out == real_dir or real_out.startswith(real_dir + os.sep)):
raise DrmException("Invalid book record name: {0}".format(repr(name)))
print(".", end=' ')
record = self.getBookPayloadRecord(name,index)
if isinstance(record, str):