Skip to content

bpy_server: bind to loopback instead of 0.0.0.0 (unauthenticated RCE) - #7

Open
adrianmelian wants to merge 1 commit into
VAST-AI-Research:mainfrom
adrianmelian:fix/bpy-server-loopback-bind
Open

adrianmelian wants to merge 1 commit into
VAST-AI-Research:mainfrom
adrianmelian:fix/bpy-server-loopback-bind

Conversation

@adrianmelian

Copy link
Copy Markdown

Summary

The Blender helper (src/server/bpy_server.py) binds 0.0.0.0 and deserializes untrusted HTTP request bodies with torch.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 to http://localhost:59876 (BPY_SERVER in spec.py), so loopback removes the network exposure at zero functional cost.

Detail

The /export and /transfer routes read the raw request body and pass it to the worker, which calls bytes_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 is bottle.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_bytes and POSTed to /export with no credentials. Happy to share the harmless PoC privately if useful; I've kept the exploit out of this description.

Fix

  • This PR (immediate): bind loopback. Safe and sufficient to close the network-reachable path.
  • Suggested follow-up (defense in depth): the pickle sink is still a risk to anyone who feeds the helper an untrusted file locally. Since the IPC is local-only, a non-executing serialization (e.g. weights_only=True for 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.md and 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.

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.
@yuripourre

Copy link
Copy Markdown

@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?

@adrianmelian

Copy link
Copy Markdown
Author

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.

@yuripourre

Copy link
Copy Markdown

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants