PoC β coremltools .mlpackage Manifest weights-item path traversal β file exfiltration
Responsible-disclosure PoC for huntr. This repository is gated. The crafted model targets a
benign canary directory (/tmp/POC_CANARY_SECRET), not any real secret β it demonstrates the
mechanism only.
Summary
coremltools resolves the items of a .mlpackage (root model, weights, β¦) from
attacker-controlled Manifest.json paths by joining them onto the package Data directory with
C++17 std::filesystem::path::operator/. Because operator/ replaces the left-hand side when
the right operand is absolute, a crafted manifest can point the weights item at any absolute host
path, and validate() only checks exists() β never containment. When such a package is loaded and
re-emitted via the documented mlprogram round-trip
(MLModel(spec, weights_dir=loaded.weights_dir).save(...)), _create_mlpackage() β addItem() runs a
raw std::filesystem::copy(<external dir>, <new package>) β copying the external directory's files
verbatim into the package the attacker receives back. Arbitrary host-file content
exfiltration; no format/validation gate on the copy.
Affected
- Package:
coremltools(PyPI) - Vulnerable format: Core ML model package (
.mlpackage) - Affected versions:
>= 5.0(theModelPackagebackend era); reproduces on 9.0 (latest) - Patched: none known
- CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
- CVSS 3.1:
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N= 6.5 (Medium)
Reproduce
pip install -r requirements.txt # coremltools==9.0
python generate_malicious.py # writes ./evil.mlpackage (weights item -> /tmp/POC_CANARY_SECRET)
python load_driver.py # plants a canary, runs the victim round-trip, checks the output
Expected output (Linux/macOS):
[victim] planted canary in /tmp/POC_CANARY_SECRET -> ['credentials']
[victim] loaded package; resolved weights_dir = /tmp/POC_CANARY_SECRET
[victim] re-emitted package saved to /tmp/returned_to_attacker.mlpackage
=== RESULT ===
VULNERABLE: external canary exfiltrated into the returned package:
Data/com.apple.CoreML/weights/credentials
Data/com.apple.CoreML/weights/credentials in the returned package contains the canary bytes that
existed only outside the original package β proving external content was copied out. Point
EXTERNAL_TARGET_DIR in generate_malicious.py at the directory containing a target file to change
what is exfiltrated (the copy is non-recursive: it takes regular files directly in that directory).
Root cause (apple/coremltools @ tag 9.0)
modelpackage/src/ModelPackage.cppβvalidate()(exists-only, no containment),findItem()(m_packageDataDirPath / manifestPath, absolute rhs replaces),addItem()(std::filesystem::copy(path, dstPath)β the exfil sink).coremltools/models/utils.pyβ_try_get_weights_dir_path()βfindItemByNameAuthor("weights", β¦).path();_create_mlpackage()βaddItem(weights_dir, "weights", β¦).coremltools/models/model.pyβMLModel.__init__setsself._weights_dir = _try_get_weights_dir_path(model)on load, and calls_create_mlpackage(model, weights_dir)on rebuild-from-spec.
Suggested fix
Containment-check every manifest item path in the C++ ModelPackage backend (reject absolute paths
and .. escapes, verify the joined path stays inside Data/) in validate(), findItem(), and
addItem() β not only in Python.
Precondition (honest scope)
Exfiltration fires when the victim/service re-emits the loaded package via the documented spec
round-trip and returns/publishes the result (e.g. a Core ML conversion / optimization / metadata-edit
service). A bare MLModel(pkg) load alone does not leak.
Credit: Michael Holmquist (mtholmquist).
- Downloads last month
- 1