bpy_server: bind to loopback instead of 0.0.0.0 (unauthenticated RCE) - #7
adrianmelian wants to merge 1 commit into
Conversation
The Blender helper is local IPC — demo.py connects to http://localhost:59876 (BPY_SERVER in spec.py). Its POST handlers deserialize request bodies with bytes_to_object() == torch.load(weights_only=False) == pickle, which executes arbitrary code on load, before any validation. Binding 0.0.0.0 makes that deserializer reachable from the local network with no authentication. Binding 127.0.0.1 removes the network path at zero functional cost, since the only client already uses localhost.
|
@adrianmelian I have a headless server with firewall rules to only accept requests inside my network. If I don't use 0.0.0.0 other PCs can't connect to it. Is this really an issue? |
|
Yes, the server deserializes request bodies with torch.load(weights_only=False), which is pickle, so anyone who can reach the port can execute arbitrary code. That's by design of pickle, no bug needed. A perimeter firewall doesn't cover that case: binding 0.0.0.0 trusts every device inside your network, so a single compromised machine on the LAN gets code execution on the server. Your setup is a real use case though. The clean fix is a --host flag (or env var) defaulting to 127.0.0.1, the bundled demo.py already connects to localhost, so the default stays safe, and your headless server passes --host 0.0.0.0 explicitly. Happy to update the PR to do that if you'd take it. |
|
@adrianmelian that's a good point, if any machine is compromised it will be a problem. I just raised my concern because for this project it sounds a little overkill but makes sense. I will let the owner of the repo decide. I am fine with your solution. |
Summary
The Blender helper (
src/server/bpy_server.py) binds0.0.0.0and deserializes untrusted HTTP request bodies withtorch.load(weights_only=False)(pickle), which executes arbitrary code during unpickling. Together these make the helper an unauthenticated remote code execution reachable from the local network while a rig job is running.This PR is the minimal fix: bind
127.0.0.1. The only client (demo.py) already connects tohttp://localhost:59876(BPY_SERVERinspec.py), so loopback removes the network exposure at zero functional cost.Detail
The
/exportand/transferroutes read the raw request body and pass it to the worker, which callsbytes_to_object(d[1])→torch.load(io.BytesIO(b), weights_only=False). Pickle runs the payload's reducer on load, before any validation. The listener isbottle.run(app, host='0.0.0.0', port=59876)with no authentication, so any host that can reach port 59876 during the job window can execute code as the user.I confirmed this end to end with a benign payload (it wrote a single marker file and did nothing else), serialized with the project's own
object_to_bytesand POSTed to/exportwith no credentials. Happy to share the harmless PoC privately if useful; I've kept the exploit out of this description.Fix
weights_only=Truefor tensors, or a plain length-prefixed format for the small control payloads) would remove the code-execution primitive entirely. Out of scope for this one-liner, but worth a look.Note
There is no
SECURITY.mdand private vulnerability reporting is disabled on this repo, so there was no private channel to use — this PR discloses and fixes in the same change so a patch is available the moment the report is public. If you'd prefer to coordinate disclosure differently in future, enabling GitHub's private vulnerability reporting gives researchers a way to reach you first.Found and fixed while integrating SkinTokens into FabricatorStudio (https://fabricator.studio). Thanks for the great work on this project.