
Exploit for CVE-2026-17544: PHP bcmath OOB write converted into memory-only RCE, bypassing disable_functions and open_basedir with a runtime offset-free resolver.
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.
![]()
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.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.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..data, and the target symbol are all resolved
at runtime by scanning memory — so a single file works across builds and distributions.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. |
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.
The bug lives only in the rewritten bcmath (PHP 8.4 / 8.5 line):
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.
bcmath enabled..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).exit() does not kill the
server: php-fpm, Apache mod_php, or the built-in server with PHP_CLI_SERVER_WORKERS>0.php exploit.php "id; uname -a"
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.
bccomp() so the OOB zero-write lands on a neighbouring zend_string's refcount only
(length preserved).zend_reference (via indirection) yields a fault-safe absolute
arbitrary read that never touches the refcounted header of the target..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.zend_class_entry, an inline function_table with a
single bucket (correct hash, terminated collision chain), and a fake
whose handler is ; corrupt the probe object's
class pointer and invoke the method. Output is returned and echoed.exploit.php — the universal memory-only exploit (CLI + web).Provided for security research and education. The author is not responsible for misuse. Do not deploy against systems you are not authorized to test.
bccomp($_POST['v'])| ❌ Only DoS / crash |
| 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 |
zend_stringzend_internal_functionzif_shell_exec