一、序列化/反序列化技术
序列化:将对象转为字节流,目的是方便对象在内存、文件、数据库或者网络之间的传递
反序列化:序列化的逆过程,即将字节流转为对象的过程。
序列化就像把桌子拆除许多零件,反序列化就是将零件组装起来
二、反序列化漏洞
原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
三、PHP反序列化
PHP反序列化漏洞:也叫PHP对象注入,程序在执行unserialize()函数时,自动执行了某些魔术方法(magic method),而魔术方法的参数被用户所控制,这就会产生安全问题。
漏洞利用条件:
- unserialize()函数的参数可控。
- 存在可利用的魔术方法。
四、面向对象
封装:
将一个类进行封装,类中有属性,以及行为(成员函数)
继承:
子类会继承父类的属性以及行为
实例化:
类实例化就成为了一个对象,就是类生成了一个具体的实例
五、序列化
<?php
//定义类
class Girl{
//声明属性
public $name;
public $age;
//声明方法
//__construct()对象创建(new)时会自动调用
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
public function hello(){
echo "Hello, my boy! \n";
echo "My name is $this->name, my age is $this->age !";
}
}
//类实例化成为对象
$ryan = new Girl('小美','18');
$str = serialize($ryan);
echo $str;
?>
输出内容:
O:4:"Girl":2:{s:4:"name";s:6:"小美";s:3:"age";s:2:"18";}
格式为:类型:长度:内容,如:O:4:"Girl"
常见的表示类型的字符:
O:类
a:数组(array)
b:布尔(boolean)
i整型
S:字符串
N:Null
d:double,浮点型
六、反序列化
O:4:"Girl":2:{s:4:"name";s:6:"小美";s:3:"age";s:2:"18";}
<?php
//定义类
class Girl{
//声明属性
public $name;
public $age;
//声明方法
//__construct()对象创建(new)时会自动调用
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
public function hello(){
echo "Hello, my boy! \n";
echo "My name is $this->name, my age is $this->age !";
}
}
//类实例化成为对象
//$ryan = new Girl('小美','18');
$str = 'O:4:"Girl":2:{s:4:"name";s:6:"小美";s:3:"age";s:2:"18";}';
$obj = unserialize($str);
$obj->hello();
?>
七、案例
<?php
header("content-type:text/html;charset=utf-8");
show_source(__FILE__);
error_reporting(0);
class Girl{
public $name = '小红';
public $age = 18;
public function __construct($name,$age){
$this->name = $name;
$this->age = $age;
}
public function hello(){
echo "Hello,my boy!\n";
echo "My name is $this->name, my age is $this->age !";
}
}
if(isset($_GET['str' ])){
$str = $_GET['str'];
$object = unserialize($str);
$object->hello();
}
?>
利用:xss
O:4:"Girl":2:{s:4:"name";s:29:"<script>alert('xss')</script>";s:3:"age";s:2:"18";}

八、访问控制符 public\protected\private
<?php
class Girl{
public $name = '小红';
protected $age = 18;
private $money = 100.5;
public function __construct($name, $age,$money){
$this->name = $name;
$this->age = $age;
$this->money = $money;
}
public function hello(){
echo "Hello, my boy! \n";
echo "My name is $this->name, my age is $this->age !";
echo "I have $this->money RMB !";
}
}
$ryan = new Girl("ryan",20,108.5);
echo serialize($ryan);
?>
序列化后:
O:4:"Girl":3:{s:4:"name";s:4:"ryan";s:6:"*age";i:20;s:11:"Girlmoney";d:108.5;}
对象字段名的序列化规则:
-
var和public:var和public声明的字段都是公共字段,因此们的字段名的序列化格式是相同的,序列化后的字段名中不包括声明时的变量前缀符号
-
protected:声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。在序列化时,字段名前面会加上\0*\0(\0为不可见字符)的前缀,因此该字段的长度会比可见字符长度大3。
-
private:声明的字段为私有字段,只有在所声明的类中可见。名在序列化时,字段名前面会加上
\0<声明该私有字段的类的类名>\0前缀
九、访问控制符的反序列化
<?php
class Girl{
public $name = '小红';
protected $age = 18;
private $money = 100.5;
public function __construct($name, $age,$money){
$this->name = $name;
$this->age = $age;
$this->money = $money;
}
public function hello(){
echo "Hello, my boy! \n";
echo "My name is $this->name, my age is $this->age !";
echo "I have $this->money RMB !";
}
}
$str = 'O:4:"Girl":3:{s:4:"name";s:4:"ryan";S:6:"\00*\00age";i:20;S:11:"\00Girl\00money";d:108.5;}';
$o = unserialize($str);
$o->hello();
?>
O:4:"Girl":3:{s:4:"name";s:4:"ryan";S:6:"\00*\00age";i:20;S:11:"\00Girl\00money";d:108.5;}
注意: 要将小写的s改为大写的S,\00替代空字符

十、魔术方法
魔术方法是一种特殊的方法,在某些情况下会自动调用。魔术方法的命名是以两个下划线开头的,常见的魔术方法有:
__construct() 对象创建(new)时会自动调用
__destruct() 对象被销毁时触发
__tostring() 把对象当作字符串使用时触发
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get($key) 用于从不可访问的属性读取数据,$key就是不存在的属性
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__clone() 当克隆对象是调用
__invoke() 当脚本尝试将对象调用为函数时触发
__autoload() 在代码中当调用不存在的类时会自动调用该方法
案例1.:
<?php
header(header:"content-type:text/html;charset=utf-8") ;
class Girl{
public $name = "小花";
public function __construct($name){
$this->name = $name;
echo "__construct: 创建对象时,create object:$this->name \n";
}
public function __destruct(){
echo "__destruct:对象销毁时,$this->name is died !\n";
}
public function __call($name, $arguments){
echo"__caLL:调用不存在的方法时\n";
}
public function __toString(){
return "__toString:把对象当做字符串输出时 \n";
}
public function __clone(){
echo "__clone:克隆对象时 \n";
}
public function __get($name){
echo "__get:读取一个不存在的属性时 \n";
}
public function _set($name,$value){
echo "__set:设置不存在的属性 \n";
}
public function __isset($name){
echo "__isset:对不可访问属性调用isset() \n";
}
public function _unset($name){
echo "__unset:在不可访问的属性上使用unset()\n";
}
public function hello(){
echo "My name is $this->name !\n";
}
}
$ryan = new Girl("Ryan");
$ryan->hello();
$ryan->a();
$ryan->b;
$r2 = clone $ryan;
echo $ryan;
$r2->name = "zs";
$r2->abc ="zs";
isset($r2->abc);
unset($r2->a);
?>

案例2.:
<?php
header("content-type:text/html;charset=utf-8");
class Girl{
public $name = "小花";
public function __construct($name){
$this->name = $name;
echo "__construct: 创建对象时, create object:$this->name \n";
}
public function __sleep(){
echo "__sleep:序列化时调用\n";
return array('name');
}
public function __wakeup(){
echo "__wakeup:反序列化时调用\n";
}
}
$ryan = new Girl("Ryan");
$r = serialize($ryan);
echo $r;
$str ='O:4:"Girl":1:{s:4:"name";s:4:"Ryan";}';
echo "\n";
unserialize($str);
?>

案例3.:
<?php
header("content-type:text/html;charset=utf-8");
highlight_file(__FILE__);
class Girl{
public $name = "小明";
public $file = "a.txt";
function __wakeup(){
$file = fopen($this->file,'w');
fwrite($file, $this->name);
fclose($file);
}
}
echo "<h1>反序列化漏洞案例</h1>";
if(isset($_GET["str"])){
$str = $_GET['str'];
$o = unserialize($str);
echo "name:".$o->name;
}
?>
poc写入webshell:
O:4:"Girl":2:{s:4:"name";s:18:"<?php phpinfo();?>";s:4:"file";s:5:"a.php";}
执行poc:

访问a.php文件:

成功写入