pragnyanramtha commited on
Commit
d45ba70
·
verified ·
1 Parent(s): 8e4b508

Add files using upload-large-folder tool

Browse files
README.md ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - security
5
+ - model-format
6
+ - caffe
7
+ - opencv
8
+ - modelscan
9
+ ---
10
+
11
+ # Benign Caffe Triggered Output Marker PoC
12
+
13
+ This repository is a benign security research PoC for Caffe-format model loading and scanning behavior. It contains a tiny Caffe deploy prototxt plus empty `.caffemodel` companion file that OpenCV DNN accepts and runs.
14
+
15
+ ## Files
16
+
17
+ - `model/deploy_trigger.prototxt` - Caffe network definition with an input-triggered two-class output.
18
+ - `model/trigger_marker.caffemodel` - empty Caffe protobuf companion accepted by OpenCV for this parameter-free network.
19
+ - `verify_poc.py` - reproduces OpenCV loading/inference and optionally runs ModelScan.
20
+ - `results.json` - captured local runtime and scanner results.
21
+ - `research_candidates/python_layer_candidate.prototxt` - non-packaged legacy pycaffe PythonLayer research note.
22
+
23
+ ## Trigger
24
+
25
+ The model returns class `0` for ordinary scalar input `0.0`, but returns class `1` when the scalar input is `42`.
26
+
27
+ ## Reproduction
28
+
29
+ ```bash
30
+ python -m venv .venv
31
+ .venv/Scripts/python -m pip install opencv-python modelscan numpy
32
+ .venv/Scripts/python verify_poc.py --modelscan
33
+ ```
34
+
35
+ On Linux/macOS, use the equivalent `bin/python` path.
36
+
37
+ ## Expected Output
38
+
39
+ The verifier should show OpenCV loading the Caffe artifact and flipping the predicted class only for the trigger input. ModelScan is expected to skip the Caffe files because `.prototxt` and `.caffemodel` are not in its supported format map.
40
+
41
+ ## Security Impact
42
+
43
+ This demonstrates scanner/runtime mismatch plus trigger-based output manipulation in a Caffe-compatible runtime, not arbitrary code execution. It is intentionally safe: there are no shell commands, callbacks, credential access, persistence, or destructive actions.
44
+
45
+ ## Hashes
46
+
47
+ ```json
48
+ {
49
+ "model\\deploy_trigger.prototxt": {
50
+ "sha256": "f28c3a09015d8f108ede66e4597af3ce8c6ebe3feca68e6e7eac2020c91b6367",
51
+ "size": 765
52
+ },
53
+ "model\\trigger_marker.caffemodel": {
54
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
55
+ "size": 0
56
+ },
57
+ "research_candidates\\python_layer_candidate.prototxt": {
58
+ "sha256": "47effc59390c80ac36a68339dd1b3103ed0b08285dec1b89313801fbfe1831e0",
59
+ "size": 372
60
+ },
61
+ "research_candidates\\benign_marker_layer.py": {
62
+ "sha256": "af23297ba73cb756807bc112122fa91109b5122a09f88a6a8a2f0fca6b00f31b",
63
+ "size": 693
64
+ },
65
+ "verify_poc.py": {
66
+ "sha256": "4eaeebf0c86b1af7b915af611e97379f1589d193676dcdf13af1550d2382c716",
67
+ "size": 2462
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Local Validation Summary
73
+
74
+ ```json
75
+ {
76
+ "opencv_version": "4.13.0",
77
+ "trigger_value": 42.0,
78
+ "checks": [
79
+ {
80
+ "case": "normal",
81
+ "input_value": 0.0,
82
+ "scores": [
83
+ 10.0,
84
+ -764.0
85
+ ],
86
+ "predicted_class": 0
87
+ },
88
+ {
89
+ "case": "trigger",
90
+ "input_value": 42.0,
91
+ "scores": [
92
+ 10.0,
93
+ 1000.0
94
+ ],
95
+ "predicted_class": 1
96
+ }
97
+ ],
98
+ "triggered_output_manipulation": true
99
+ }
100
+ ```
101
+
102
+ ## Mitigations
103
+
104
+ - Treat Caffe `.prototxt` and `.caffemodel` files as executable model logic, not inert data.
105
+ - Extend scanners to identify Caffe artifacts and flag unsupported model types explicitly in policy gates.
106
+ - Run behavioral tests or provenance checks before trusting third-party Caffe model outputs.
model/deploy_trigger.prototxt ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "caffe_triggered_output_marker"
2
+ input: "data"
3
+ input_shape {
4
+ dim: 1
5
+ dim: 1
6
+ dim: 1
7
+ dim: 1
8
+ }
9
+ layer {
10
+ name: "normal_class_score"
11
+ type: "Power"
12
+ bottom: "data"
13
+ top: "normal_score"
14
+ power_param {
15
+ power: 1
16
+ scale: 0
17
+ shift: 10
18
+ }
19
+ }
20
+ layer {
21
+ name: "trigger_distance"
22
+ type: "Power"
23
+ bottom: "data"
24
+ top: "distance"
25
+ power_param {
26
+ power: 2
27
+ scale: 1
28
+ shift: -42
29
+ }
30
+ }
31
+ layer {
32
+ name: "trigger_class_score"
33
+ type: "Power"
34
+ bottom: "distance"
35
+ top: "trigger_score"
36
+ power_param {
37
+ power: 1
38
+ scale: -1
39
+ shift: 1000
40
+ }
41
+ }
42
+ layer {
43
+ name: "scores"
44
+ type: "Concat"
45
+ bottom: "normal_score"
46
+ bottom: "trigger_score"
47
+ top: "scores"
48
+ concat_param {
49
+ axis: 1
50
+ }
51
+ }
model/trigger_marker.caffemodel ADDED
File without changes
modelscan_results.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ No settings file detected at C:\Users\Pragnyan\dev\huntr-exp1\caffe\modelscan-settings.toml. Using defaults.
2
+
3
+ {"summary": {"total_issues_by_severity": {"LOW": 0, "MEDIUM": 0, "HIGH": 0,
4
+ "CRITICAL": 0}, "total_issues": 0, "input_path":
5
+ "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\model",
6
+ "absolute_path":
7
+ "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\model",
8
+ "modelscan_version": "0.8.8", "timestamp": "2026-05-12T12:46:58.972661",
9
+ "scanned": {"total_scanned": 0}, "skipped": {"total_skipped": 2,
10
+ "skipped_files": [{"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan
11
+ did not scan file", "source": "deploy_trigger.prototxt"}, {"category":
12
+ "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source":
13
+ "trigger_marker.caffemodel"}]}}, "issues": [], "errors": []}
research_candidates/benign_marker_layer.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benign Caffe Python-layer marker module.
2
+ # This is not used by the OpenCV verifier. It documents the legacy pycaffe
3
+ # PythonLayer candidate, which requires a Caffe build compiled with
4
+ # WITH_PYTHON_LAYER enabled.
5
+ from pathlib import Path
6
+
7
+ MARKER = "CAFFE_PYTHON_LAYER_IMPORT_MARKER"
8
+
9
+
10
+ class BenignMarkerLayer: # pragma: no cover - requires pycaffe runtime
11
+ def setup(self, bottom, top):
12
+ Path("caffe_python_layer_marker.txt").write_text(MARKER, encoding="utf-8")
13
+
14
+ def reshape(self, bottom, top):
15
+ pass
16
+
17
+ def forward(self, bottom, top):
18
+ top[0].data[...] = bottom[0].data
19
+
20
+ def backward(self, top, propagate_down, bottom):
21
+ pass
research_candidates/python_layer_candidate.prototxt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "caffe_python_layer_marker_candidate"
2
+ input: "data"
3
+ input_shape {
4
+ dim: 1
5
+ dim: 1
6
+ dim: 1
7
+ dim: 1
8
+ }
9
+ layer {
10
+ name: "python_marker_layer"
11
+ type: "Python"
12
+ bottom: "data"
13
+ top: "marker"
14
+ python_param {
15
+ module: "benign_marker_layer"
16
+ layer: "BenignMarkerLayer"
17
+ param_str: "{\"marker\":\"CAFFE_PYTHON_LAYER_IMPORT_MARKER\"}"
18
+ }
19
+ }
results.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "environment": {
3
+ "python": "3.12.12 (main, Oct 28 2025, 14:15:42) [MSC v.1944 64 bit (AMD64)]",
4
+ "platform": "Windows-11-10.0.26220-SP0"
5
+ },
6
+ "paths": {
7
+ "deploy_prototxt": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\model\\deploy_trigger.prototxt",
8
+ "caffemodel": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\model\\trigger_marker.caffemodel",
9
+ "python_layer_candidate": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\research_candidates\\python_layer_candidate.prototxt",
10
+ "python_layer_module": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\research_candidates\\benign_marker_layer.py"
11
+ },
12
+ "opencv": {
13
+ "opencv_version": "4.13.0",
14
+ "trigger_value": 42.0,
15
+ "checks": [
16
+ {
17
+ "case": "normal",
18
+ "input_value": 0.0,
19
+ "scores": [
20
+ 10.0,
21
+ -764.0
22
+ ],
23
+ "predicted_class": 0
24
+ },
25
+ {
26
+ "case": "trigger",
27
+ "input_value": 42.0,
28
+ "scores": [
29
+ 10.0,
30
+ 1000.0
31
+ ],
32
+ "predicted_class": 1
33
+ }
34
+ ],
35
+ "triggered_output_manipulation": true
36
+ },
37
+ "modelscan": {
38
+ "command": [
39
+ "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\.venv\\Scripts\\modelscan.exe",
40
+ "-p",
41
+ "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\hf_caffe_poc\\model",
42
+ "-r",
43
+ "json",
44
+ "--show-skipped"
45
+ ],
46
+ "returncode": 3,
47
+ "stdout": "No settings file detected at C:\\Users\\Pragnyan\\dev\\huntr-exp1\\caffe\\modelscan-settings.toml. Using defaults. \n\n{\"summary\": {\"total_issues_by_severity\": {\"LOW\": 0, \"MEDIUM\": 0, \"HIGH\": 0, \n\"CRITICAL\": 0}, \"total_issues\": 0, \"input_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\caffe\\\\hf_caffe_poc\\\\model\", \n\"absolute_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\caffe\\\\hf_caffe_poc\\\\model\", \n\"modelscan_version\": \"0.8.8\", \"timestamp\": \"2026-05-12T12:46:58.972661\", \n\"scanned\": {\"total_scanned\": 0}, \"skipped\": {\"total_skipped\": 2, \n\"skipped_files\": [{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan\ndid not scan file\", \"source\": \"deploy_trigger.prototxt\"}, {\"category\": \n\"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan did not scan file\", \"source\": \n\"trigger_marker.caffemodel\"}]}}, \"issues\": [], \"errors\": []}\n",
48
+ "stderr": ""
49
+ }
50
+ }
verify_poc.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import cv2
9
+ import numpy as np
10
+
11
+
12
+ TRIGGER_VALUE = 42.0
13
+
14
+
15
+ def run_model(root: Path) -> dict:
16
+ deploy_path = root / "model" / "deploy_trigger.prototxt"
17
+ weights_path = root / "model" / "trigger_marker.caffemodel"
18
+ net = cv2.dnn.readNetFromCaffe(str(deploy_path), str(weights_path))
19
+
20
+ checks = []
21
+ for label, value in [("normal", 0.0), ("trigger", TRIGGER_VALUE)]:
22
+ blob = np.array([[[[value]]]], dtype=np.float32)
23
+ net.setInput(blob)
24
+ scores = net.forward("scores").reshape(-1).astype(float).tolist()
25
+ checks.append(
26
+ {
27
+ "case": label,
28
+ "input_value": value,
29
+ "scores": scores,
30
+ "predicted_class": int(np.argmax(scores)),
31
+ }
32
+ )
33
+
34
+ return {
35
+ "opencv_version": cv2.__version__,
36
+ "trigger_value": TRIGGER_VALUE,
37
+ "checks": checks,
38
+ "triggered_output_manipulation": checks[0]["predicted_class"] != checks[1]["predicted_class"],
39
+ }
40
+
41
+
42
+ def run_modelscan(root: Path) -> dict:
43
+ executable = Path(sys.executable)
44
+ if os.name == "nt":
45
+ cli = executable.with_name("modelscan.exe")
46
+ else:
47
+ cli = executable.with_name("modelscan")
48
+ command = [str(cli)] if cli.exists() else [sys.executable, "-m", "modelscan.cli"]
49
+ proc = subprocess.run(
50
+ command
51
+ + [
52
+ "-p",
53
+ str(root / "model"),
54
+ "-r",
55
+ "json",
56
+ "--show-skipped",
57
+ ],
58
+ text=True,
59
+ stdout=subprocess.PIPE,
60
+ stderr=subprocess.PIPE,
61
+ check=False,
62
+ )
63
+ return {
64
+ "command": proc.args,
65
+ "returncode": proc.returncode,
66
+ "stdout": proc.stdout,
67
+ "stderr": proc.stderr,
68
+ }
69
+
70
+
71
+ def main() -> int:
72
+ parser = argparse.ArgumentParser()
73
+ parser.add_argument("--root", default=Path(__file__).resolve().parent)
74
+ parser.add_argument("--modelscan", action="store_true")
75
+ args = parser.parse_args()
76
+ root = Path(args.root).resolve()
77
+
78
+ result = {"opencv": run_model(root)}
79
+ if args.modelscan:
80
+ result["modelscan"] = run_modelscan(root)
81
+
82
+ print(json.dumps(result, indent=2))
83
+ return 0 if result["opencv"]["triggered_output_manipulation"] else 1
84
+
85
+
86
+ if __name__ == "__main__":
87
+ raise SystemExit(main())
versions.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "opencv-python": "4.13.0.92",
3
+ "modelscan": "0.8.8",
4
+ "huggingface_hub": "1.14.0",
5
+ "protobuf": "7.34.1",
6
+ "numpy": "2.4.4"
7
+ }