SOAP_PERSISTENCE_SESSION allows to persist the SOAP handler object between requests using the session storage. Normally the object is immediately released after SoapServer::handle(), unless this setting is specified. This differentiation happens with the service->soap_class.persistence != SOAP_PERSISTENCE_SESSION check. soap.c fails to account for this flag in two places:
https://github.com/php/php-src/blob/0d9ff00394d9447992bb66ccb1cef3edf70576bd/ext/soap/soap.c#L1595
https://github.com/php/php-src/blob/0d9ff00394d9447992bb66ccb1cef3edf70576bd/ext/soap/soap.c#L1600
Both of them are failure cases when a handler function for a header node either returned false or threw an exception. When the session is written, the freed object will be read and written to the session storage, resulting in a use-after-free. Normally, a SOAP server will only handle one SOAP request per PHP request, so it's unlikely that the attacker will be able to control the freed memory segment, though it is not impossible.
The following example demonstrates the use-after-free.
class SoapSrv {
public function return() {
return new SoapFault('Server', 'denied');
}
public function hello() {
return 'ok';
}
}
session_start();
$srv = new SoapServer(null, ['uri' => 'urn:a']);
$srv->setClass('SoapSrv');
$srv->setPersistence(SOAP_PERSISTENCE_SESSION);
$srv->handle(<< XML);