# Format string vulnerability in zend_throw_or_error()

PHP 7.0.0 passed attacker-controlled non-existent class names as a printf format string in zend_throw_or_error, enabling %n write-what-where and code execution.

- Payout: $1,000
- Severity: critical
- Program: Internet Bug Bounty
- Credited: rewzilla
- Disclosed: 2016-01-10
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/106548
- Library page: https://bugbountylibrary.com/r/h1%3A106548
- Tags: domain:oss impact:rce kind:bounty platform:hackerone sev:critical tech:php technique:source-review vuln:memory-corruption vuln:rce

**Target:** PHP Zend engine class-name error handling (zend_throw_or_error)
**Root cause:** zend_throw_or_error passed the error message (containing the user-supplied class name) directly as the format argument to zend_throw_error instead of using "%s", so %n/%d specifiers in the class name were interpreted by the printf engine.
**Steps:**
1. Trigger a static call on a non-existent class whose name contains format specifiers: `$name="%n%n%n"; $name::doSomething();`
2. Observe segfault in xbuf_format_converter (spprintf.c) confirming format-string interpretation
3. Craft write-what-where: `$name = "%".($rdx-8)."d%d%n".str_repeat("A",($rax-34)); $name::doSomething();`
4. Under gdb, confirm `mov DWORD PTR [rax],edx` with rax=0x43434343 (where) and rdx=0x42424242 (what)
**How found:** Researcher noticed PHP 7.0.0's new class-name error path and traced the call to zend_throw_or_error in Zend/zend_execute_API.c:221, spotting that the message was passed as the format argument rather than a %s argument.
**Exploitation:** Attacker controls the class name string; by embedding %<width>d and %n specifiers they steer the printf engine to write an arbitrary 4-byte value to an arbitrary address, demonstrated in gdb as a controlled write-what-where primitive that can be leveraged for full code execution.
**Escalation:** Write-what-where primitive escalates to arbitrary code execution by overwriting a function pointer/GOT entry.
**Insight:** When a new error/exception path is added, check whether the message string is passed as the printf format argument instead of via "%s" — user-controlled identifiers (class names, function names) reaching vspprintf are a classic format-string sink.
