目录
一. 关于序列化
二. PHP中的序列化和反序列化
1. php代码实现序列化与反序列
1. 为何会出现反序列化漏洞
2. PHP中的魔术方法
3. 总结
4. 扩展
序列化即 将对象的状态信息转换为可以存储或传输的形式的过程(序列化是一种用来处理对象流的机制。所谓对象流也就是将对象的内容进行流化,流(就是I/O)。我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)
在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象
简单来说,序列化就是把一个对象变成可以传输的字符串,可以以特定的格式在进程之间跨平台、安全的进行通信 。
反序列分为PHP反序列和java反序列化,下面就以PHP反序列化漏洞为切入点,讲述反序列化漏洞底层原理,其他Java、Python等语言的反序列化漏洞,其原理基本相通。
PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而代码执行、getshell等一系列不可控的后果。
PHP 中的序列化与反序列化,基本都是围绕serialize() 和unserialize() 两个函数展开的。
web服务器上建立文件夹stu.php,内容如下。代码的意思是我们在其中建立了一个名为Stu的类,并实例化了一个对象$stu1,分别给对象的四个属性进行赋值。将对象打印出来,然后将对象序列化为字符串输出,再反序列还原成原来的对象。
name = 'xzc';
$stu1->sex = true;
$stu1->age=24;
$stu1->score = 90;
var_dump($stu1); //1
echo "
";
$stu1_str = serialize($stu1); //序列化,格式化为字符串,遵循统一的格式
print $stu1_str; //2
echo "
";
$stu2 = unserialize($stu1_str); //反序列化,将字符串还原为对象
var_dump($stu2); //3
?>
刷新浏览器输出结果如下所示,看见反序列化后还是回到了最初的样子(对象)。
为了能理解为什么出现反序列化漏洞。我们再建立一个文件。内容如下,代码的意思为:定义一个名为Test的类,类中有一个属性和一个函数。接着实例化了一个对象,将对象输出,再将对象序列化为字符串。此时再反序列化,只不过此时的反序列化接收的字符串写成了动态的。如果此时还是输入一开始的序列化后的字符串就会还原为原来的对象
str);
}
}
$test = new Test();
var_dump($test);
echo "
";
//序列化
$test = serialize($test);
echo $test;
echo "
";
//反序列化
$test = $_GET['test'];
$test = unserialize($test);
var_dump($test)
?>
?test=O:4:"Test":1:{s:3:"str";s:4:"xzc;";}
如果我们还是传一开始对象序列化后的字符串,那么反序列后将还原为一开始的对象,所以下面的1和3相同。
输出结果如下
这时,我们将序列化后的字符串进行篡改,如下所示:
?test=O:4:"Test":1:{s:3:"str";s:10:"phpinfo();";}
刷新页面查看,phpinfo()被解析了,既然phpinfo()可以解析,那这里我们就可以换成别的命令语句,从而进行远程命令执行。反序列化漏洞产生了
那漏洞在何处了?漏洞就出在这个函数上 __destruct(),如下所示
function __destruct(){ //在销毁对象时自动调用
echo "This is function __construct()";
//@eval($this->str);
}
我们先把 @eval()给注释掉,然后换成 echo "This is function __construct()";
这里我们没有调用函数,语句“This is function __construct()”写在了函数内,通常情况下如果我们没有调用函数,函数内的语句是不会去执行的。如果“This is function __construct()” 被输出了,也就说明函数被调用了。如下
刷新浏览器发现,函数中的语句进行了输出,说明函数自动进行了调用
而且函数中的语句被输出了两次,也就是说函数被自动调用了两次。可是我们并没有手动去调用这个方法,它为什么会自动调用了?原来PHP中以 “__” 开头的方法,是PHP 中的魔术方法,其在特定情况下会被自动调用。所以函数中的eval函数也被调用了,从而触发了命令执行。
那这个方法为什么会被调用两次了?原来__destruct()在销毁对象时会自动调用,因为我们创建了一个对象 $test = new Test() ,后面又反序列化了一个对象,当脚本执行结束,这两个对象都要进行销毁,所以是两次。
那为什么两次调用的顺序不一样了?一次调用在前一次调用在后。因为对象进行序列化的时候,其实就是将内存中的对象存储为字符串,此时对象会自动进行释放,所以 $test = serialize($test) 后就会马上释放对象从而调用函数。后面反序列化的时候(对象也是放在内存中),脚本执行结束,内存要释放,此时才进行对象的销毁,所以函数是在最后面进行调用。这样就输出了两次。
接着把 __destruct() 函数换成 __construct , 这个函数在创建对象时会自动进行调用
刷新浏览器,发现这方法也自动进行了调用,但是它只调用了一次,因为我们只创建了一个对象 $test
上面所了解的那两个方法都以 “__” 开头,其为PHP 中的魔术方法。类中的魔术方法,在特定情况下会被自动调用。
主要魔术方法如下。
__construct 在创建对象时自动调用
__destruct 在销毁对象时自动调用
__call() 在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时调用
__get() 读取不可访问属性的值时,__get() 会被调用
__set() 在给不可访问属性赋值时,__set() 会被调用
__isset() 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset() 当对不可访问属性调用 unset() 时,__unset() 会被调用。
__sleep() serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。
__wakeup() unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
__toString() __toString() 方法用于一个类被当成字符串时应怎样回应。
__invoke() 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
__set_state() 自PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。
__clone() 当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
反序列化漏洞的核心的地方就是在序列化和反序列的过程中会自动的调用某些函数,而这些函数的设计往往会有一些问题,且其又对需要反序列的字符串没有进行严格的过滤,比如上面写的:
function __destruct(){ @eval($this->str); //php中eval()函数中的eval是evaluate的简称,这个函数的作用就是把一段字符串当作PHP语句来执行,一般情况下不建议使用因为容易被黑客利用 |
在渗透测试中,反序列漏洞是非常好用的一个漏洞,利用简单且危害高。下面介绍几个在实战中利用率比较高的反序列漏洞。
——心,若没有栖息的地方,到哪都是流浪