## https://sploitus.com/exploit?id=78BE315A-C781-588D-BD38-065DB981EF80
# CVE-2026-17544 β PHP bcmath OOB write β universal memory-only RCE
A weaponized proof-of-concept for **CVE-2026-17544**, an out-of-bounds write in PHP's
rewritten `bcmath` extension (`bc_str2num`). This turns the publicly known **DoS-only**
crash into a **full, universal, memory-only remote code execution primitive** that
bypasses both `disable_functions` **and** `open_basedir` at the same time.
> **Authorized research / educational use only.** Run this only against systems you own
> or are explicitly authorized to test.
---
## TL;DR
- **Bug:** `bc_str2num` truncates a fractional part on a manual `scale`, re-trims trailing
zeros, but does not update `fractional_end`. The buffer is sized for the re-trimmed
length while the copy uses the pre-trim length β controlled **out-of-bounds zero-write**
into the Zend MM heap.
- **Exploit:** the OOB zero-write zeroes the refcount of a neighbouring `zend_string`
(length preserved) β **refcount-desync use-after-free** β giant-string relative R/W β
fault-safe absolute arbitrary read β runtime symbol resolution β a fully **data-only**
native function call.
- **Payload:** builds a fake `zend_class_entry` + inline `function_table` + fake
`zend_function{ handler = zif_shell_exec }`, then calls it as a method. The command
output is returned as a string and delivered into the response/stdout.
- **No hardcoded offsets.** Base address, `.data`, and the target symbol are all resolved
at runtime by scanning memory β so a single file works across builds and distributions.
---
## Threat model β read this first
This is a **sandbox-escape / post-exploitation** primitive, **not** a remote-input-only RCE.
| Scenario | Result |
|---|---|
| Attacker can already execute PHP (webshell, deserialization, `eval` sink) but is boxed by `disable_functions` + `open_basedir` | β
**Native RCE** (this PoC) |
| Application merely calls a bcmath function on raw user input, e.g. `bccomp($_POST['v'])` | β Only DoS / crash |
A single bcmath call on attacker input gives one **blind** OOB zero-write with no feedback.
Full RCE requires arbitrary PHP execution to groom the heap, build the R/W primitives, and
run the resolver β none of which is expressible as a function argument. So the realistic
ceiling for input-only exposure is a **remote denial of service**; code execution requires
an existing PHP-execution context that this PoC then frees from the sandbox.
---
## Affected versions
The bug lives only in the **rewritten** bcmath (PHP 8.4 / 8.5 line):
| PHP branch | Vulnerable |
|---|---|
| 7.x, 8.0 β 8.3 | No (old `libbcmath`, different `bc_str2num`) |
| **8.4.0 β 8.4.23** | **Yes** |
| **8.5.0 β 8.5.8** | **Yes** |
| β₯ 8.4.24 / β₯ 8.5.9 | Patched |
Verified end-to-end (`uid=0`, output captured) on **PHP 8.4.23** and **PHP 8.5.8**, amd64,
with the same unmodified file β the Zend struct layout is identical across 8.4 / 8.5.
---
## Requirements / assumptions
- Linux **amd64**.
- A PHP 8.4/8.5 binary in the vulnerable range with `bcmath` enabled.
- **Contiguous** binary mapping (every standard distribution / production build). The runtime
base scan walks down page-by-page from `.text` to the ELF header; this is safe on any
contiguously-mapped binary. Only artificial `-z separate-code` builds with unmapped 2 MB
gaps cannot be auto-resolved by a blind scan (not seen in production).
- For web delivery, request isolation is required so a per-request `exit()` does not kill the
server: **php-fpm**, Apache mod_php, or the built-in server with `PHP_CLI_SERVER_WORKERS>0`.
---
## Usage
### CLI
```bash
php exploit.php "id; uname -a"
```
### Web (post-exploitation, inside a sandboxed PHP context)
Deploy as e.g. `shell.php`, then:
```
GET /shell.php?cmd=id
POST cmd=id
```
The command output is captured via `zif_shell_exec` and written into the response body.
---
## How it works (detailed)
1. **Trigger + groom.** Spray fixed-size strings, punch a hole, and run the vulnerable
`bccomp()` so the OOB zero-write lands on a neighbouring `zend_string`'s refcount only
(length preserved).
2. **Refcount-desync UAF.** The victim string is freed prematurely while a reference still
dangles.
3. **Giant string.** The freed slot is reclaimed by an object; the dangling string's length
field becomes a huge value β relative read/write over the heap.
4. **Absolute read.** A fake `zend_reference` (via indirection) yields a fault-safe absolute
arbitrary read that never touches the refcounted header of the target.
5. **Resolve.** From a leaked `.text` pointer: page-step down to the ELF magic (base), parse
program headers to find `.data`, then scan `.data` for the `shell_exec`
`zend_function_entry` whose handler points into `.text`.
6. **Data-only call.** Construct a fake `zend_class_entry`, an inline `function_table` with a
single bucket (correct `zend_string` hash, terminated collision chain), and a fake
`zend_internal_function` whose handler is `zif_shell_exec`; corrupt the probe object's
class pointer and invoke the method. Output is returned and echoed.
---
## Files
- `exploit.php` β the universal memory-only exploit (CLI + web).
---
## Credits
- Original public DoS proof-of-concept: [boreas37/cve-2026-17544-poc](https://github.com/boreas37/cve-2026-17544-poc).
- Memory-disclosure / data-only-call techniques inspired by prior PHP exploitation research.
---
## Disclaimer
Provided for security research and education. The author is not responsible for misuse.
Do not deploy against systems you are not authorized to test.