- Status Closed
-
Assigned To
cbay - Private
Opened by cyberzod - 24.06.2026
Last edited by cbay - 24.06.2026
FS#345 - Server-Side Request Forgery (SSRF) via Reverse Proxy Configuration
Title: Server-Side Request Forgery (SSRF) via Reverse Proxy Configuration
Severity: High — CVSS 7.7
CWE: 918
Overview
The alwaysdata reverse proxy feature accepts arbitrary URLs, including loopback addresses and external destinations, without validating the target IP or domain. This allows authenticated users to cause the server to make HTTP requests to arbitrary destinations.
Vulnerability Details
When configuring a Reverse Proxy site, the url field accepts any URL passing Django's URLValidator. This validator only checks format and does not resolve the hostname, check for internal/loopback destinations, or validate against an allowlist.
As a result, the following are accepted and saved without error:
http://127.0.0.1/
http://localhost/
http://169.254.169.254/
Steps to Reproduce
1. Log in to
https://admin.alwaysdata.com
2. Navigate to Web → Sites and select any site
3. Change Type to Reverse proxy
4. Set Remote URL to
http://127.0.0.1:80/
5. Click Save — receives
302 Found
(no validation error)
6. Visit the site's public URL
7. The server proxies the request to the loopback address
Out-of-Band Verification:
Set the Remote URL to a webhook.site URL, save, and visit the site. The webhook receives:
GET /your-webhook-id HTTP/1.1 x-forwarded-server: cyberzod.alwaysdata.net via: 1.1 alproxy, 1.1 cyberzod.alwaysdata.net user-agent: python-requests/2.25.1
Evidence
Claim 1 — Loopback URL accepted:
Request:
POST /site/{site_id}/ HTTP/2
Host: admin.alwaysdata.com
type=reverse_proxy&url=http://127.0.0.1:80/
Response:
HTTP/2 302 Found Location: /site/
Claim 2 — Server makes outbound requests to user-controlled URLs:
Webhook.site received:
Source IP: 2a00:b6e0:1:20:20::1 (Paris, France — alwaysdata infrastructure) x-forwarded-server: cyberzod.alwaysdata.net via: 1.1 alproxy, 1.1 cyberzod.alwaysdata.net
Impact
- Internal service discovery — attacker can probe internal ports and services
- Cloud metadata access —
169.254.169.254
reachable (AWS/GCP instance metadata)
- Information disclosure — internal responses can be exfiltrated via outbound requests
Note: Internal service access is indicated but not definitively confirmed. The above represent potential impact based on confirmed URL acceptance.
Remediation
Add a custom validator that resolves the destination hostname and rejects private IP ranges:
import socket, ipaddress
from urllib.parse import urlparse
def validate_proxy_url(value):
parsed = urlparse(value)
try:
ip = socket.gethostbyname(parsed.hostname)
except socket.gaierror:
raise ValidationError("Invalid hostname")
blocked = [
'127.0.0.0/8', '10.0.0.0/8',
'172.16.0.0/12', '192.168.0.0/16',
'169.254.0.0/16', '::1/128'
]
ip_obj = ipaddress.ip_address(ip)
for net in blocked:
if ip_obj in ipaddress.ip_network(net):
raise ValidationError("Internal addresses not allowed")
return value
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N — Score: 7.7 (High)
References
- CWE-918: Server-Side Request Forgery
- OWASP SSRF Prevention Cheat Sheet
Loading...
Available keyboard shortcuts
- Alt + ⇧ Shift + l Login Dialog / Logout
- Alt + ⇧ Shift + a Add new task
- Alt + ⇧ Shift + m My searches
- Alt + ⇧ Shift + t focus taskid search
Tasklist
- o open selected task
- j move cursor down
- k move cursor up
Task Details
- n Next task
- p Previous task
- Alt + ⇧ Shift + e ↵ Enter Edit this task
- Alt + ⇧ Shift + w watch task
- Alt + ⇧ Shift + y Close Task
Task Editing
- Alt + ⇧ Shift + s save task