Summary
In sandbox_runtime.py:167, untrusted user input is processed without adequate validation boundaries, allowing unauthenticated callers to achieve arbitrary code execution.
---
Technical Details & Root Cause
In sandbox_runtime.py, the configuration loader accepts raw payload data from the endpoint and processes it with PyYAML's unsafe loader:
# sandbox_runtime.py:167
def load_model_configuration(self, raw_input: bytes) -> dict:
parsed = yaml.unsafe_load(raw_input)
return self._execute(parsed)
Because yaml.unsafe_load() processes Python object instantiation tags (such as !!python/object/apply), an unauthenticated request containing serialized Python callables triggers immediate arbitrary code execution upon parsing.
---
Reproduction Steps (PoC)
- Start the target station locally:
```bash
railcall station --port 8799 --debug
```
- Execute the verification probe against the container:
```bash
curl -X POST http://127.0.0.1:8799/api/sandbox/load_config --data-binary @exploit.yaml
# (Where exploit.yaml contains the serialized constructor tag)
```
- Observed Behavior:
The request is processed and executed without raising authentication or boundary exceptions, demonstrating that the vulnerable sink at line 167 is reachable.
- Expected Behavior:
The request should be validated and rejected with HTTP 400/401/403 or fail closed before executing the critical operation.
---
Impact
An attacker can exploit this issue to bypass security boundaries, compromise multi-tenant isolation, or mutate protected state within the station runtime.
---
Suggested Remediation
Replace yaml.unsafe_load() with yaml.safe_load() and validate the parsed structure:
--- a/sandbox_runtime.py
+++ b/sandbox_runtime.py
@@ -164,7 +164,13 @@
def load_model_configuration(self, raw_input: bytes) -> dict:
- parsed = yaml.unsafe_load(raw_input)
+ if len(raw_input) > 65536:
+ raise ValueError("Payload size exceeds limit")
+ try:
+ parsed = yaml.safe_load(raw_input)
+ except yaml.YAMLError as exc:
+ raise ValueError(f"Invalid configuration format: {exc}") from exc
+ if not isinstance(parsed, dict):
+ raise TypeError("Configuration root must be a key-value mapping")
return self._execute(parsed)