In the land of PHP you will always be (use-after-)free
A bug and exploit that hearkens back to old-school browser exploitation. The bug is a use-after-free in concat_function()
for variable concatenation, which is abused in the PHP engine to escape disable_functions
and open_basedir
sandboxing.
The bug
In PHP it’s possible to concatenate variables like strings using the .=
operator. Internally this invokes concat_function()
, which will try to process the operands and convert things like integers to strings if necessary. Objects however (such as arrays) are not supported for concatenation and trigger an error. In PHP it’s possible to define your own error handlers, which can get invoked in scenarios like this. In an error case, it’ll free and cleanup the first operand (the variable being concatenated to), but it won’t consider the fact that the user-specified error handler can swap out that variable for something else which will implicitly free the variable’s backing zval
, which holds refcounting information and pointers to backing data.
Exploitation
Abusing the bug gets you in a scenario where you can effectively overlap some arbitrary object with the UAF’d zval
that’s getting destroyed. This is used to build two primary primitives, an arbitrary free and an infoleak. The arbitrary free simply involves overlapping a string which will get used as a zval in the cleanup code. The infoleak seems to be later in the cleanup code where a pointer gets written to the overlapped string, which is a string of null bytes.
By using some heap feng shui, objects can be allocated contiguously, and by leaking the pointer of a string, an adjacent object (such as a helper object with some inline properties) pointer can be leaked and free()’d. From here, they use the object properties and the ability to corrupt the backing pointers to derive arbitrary read/write primitives (also similar to browser exploitation).
With arbitrary read, they scan the heap for the basic_functions
table that contains a mapping of functions to handlers, and search for the system()
pointer. Then with arbitrary write, they smash a setup anonymous function handler to set it to the leaked system()
pointer, bypassing the disable_functions
sandboxing.