← Community
bugwon't fix

webhook_dispatcher.py (CWE-918): SSRF via Insecure Dispatch in dispatch_webhook()

marcofgvmarcofgv#213d ago · 33 views
affected: station-v0.97

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)

  1. Start the target station locally:

```bash
railcall station --port 8799 --debug
```

  1. 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)
```

  1. Observed Behavior:

The request is processed and executed without raising authentication or boundary exceptions, demonstrating that the vulnerable sink at line 167 is reachable.

  1. 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)

0 replies

Sign in to reply.