#知识点:
1、反序列化魔术方法全解
2、反序列化变量属性全解
3、反序列化魔术方法原生类
4、反序列化语言特性漏洞绕过
-其他魔术方法
-共有&私有&保护
-语言模式方法漏洞
-原生类获取利用配合
#反序列化利用大概分类三类
-魔术方法的调用逻辑-如触发条件
-语言原生类的调用逻辑-如 SoapClient
-语言自身的安全缺陷-如 CVE-2016-7124
#反序列化课程点:
-PHP&Java&Python
序列化:对象转换为数组或字符串等格式
反序列化:将数组或字符串等格式转换成对象
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象
#PHP 反序列化漏洞
原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而
导致代码执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些
魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
#魔术方法利用点分析:
触发:
unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:
__construct(): //构造函数,当对象 new 的时候会自动调用
__destruct()://析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatci(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用 isset
Ø 方法&属性-调用详解&变量数据详解
Ø CTF-语言漏洞-__wakeup()方法绕过
Ø CTF-方法原生类-获取&利用&配合其他
#方法&属性-调用详解&变量数据详解
对象变量属性:
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
序列化数据显示:
private 属性序列化的时候格式是%00 类名%00 成员名
protect 属性序列化的时候格式是%00*%00 成员名
__destruct方法:
具体代码:
//__construct __destruct 魔术方法 创建调用__construct 2种销毁调用__destruct
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";$this->name = $name;$this->age = $age;$this->string = $string;}// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。// * 当对象销毁时会调用此方法// * 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁// *//*function __destruct(){echo "__destruct 类执行完毕"."<br>";}
}
// 主动销毁
$test = new Test("Spaceman",566, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';
// 程序结束自动销毁
$test = new test("Spaceman",566, 'Test String');
echo '第二种执行完毕'.'<br>';
分析
__destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制
一是用户主动销毁对象(unset),二是当程序结束时由引擎自动销毁
__toString方法
具体代码:
class Test
{public $variable = 'This is a string';public function good(){echo $this->variable . '<br />';}// 在对象当做字符串的时候会被调用public function __toString()
{return '__toString <br>';}
}$a = new Test();
$a->good();
//输出调用
echo $a;
在对象当做字符串的时候会被调用,即输出变量a——则表示以字符串的形式输出
__CALL方法:
具体代码:
class Test{public function good($number,$string){echo '存在good方法'.'<br>';echo $number.'---------'.$string.'<br>';}// 当调用类中不存在的方法时,就会调用__call();public function __call($method,$args){echo '不存在'.$method.'方法'.'<br>';var_dump($args);}
}$a = new Test();
$a->good(566,'nice');
$b = new Test();
$b->spaceman(899,'no');
调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
__get()
具体代码:
class Test {public $n=123;// __get():访问不存在的成员变量时调用public function __get($name){echo '__get 不存在成员变量'.$name.'<br>';}
}$a = new Test();
// 存在成员变量n,所以不调用__get
echo $a->n;
echo '<br>';
// 不存在成员变量spaceman,所以调用__get
echo $a->spaceman;
读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数与__call方法类似,只不过是其对象属性
__set()方法
具体代码:
class Test{public $data = 100;protected $noway=0;// __set():设置对象不存在的属性或无法访问(私有)的属性时调用//__set($name, $value)// * 用来为私有成员属性设置的值// * 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。public function __set($name,$value){echo '__set 不存在成员变量 '.$name.'<br>';echo '即将设置的值 '.$value."<br>";$this->noway=$value;}public function Get(){echo $this->noway;}
}$a = new Test();
// 读取 noway 的值,初始为0
$a->Get();
echo '<br>';
// 无法访问(私有)noway属性时调用,并设置值为899
$a->noway = 899;
// 经过__set方法的设置noway的值为899
$a->Get();
echo '<br>';
// 设置对象不存在的属性spaceman
$a->spaceman = 566;
// 经过__set方法的设置noway的值为566
$a->Get();
设置一个对象的属性时,若属性存在,则直接赋值;若不存在,则会调用__set函数。
__sleep()方法:
具体代码:
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";$this->name = $name;$this->age = $age;$this->string = $string;}// __sleep() :serialize之前被调用,可以指定要序列化的对象属性public function __sleep(){echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";// 例如指定只需要 name 和 age 进行序列化,必须返回一个数值return array('name', 'age');}
}$a = new Test("Spaceman",566, 'Test String');
echo serialize($a);
serialize之前被调用,可以指定要序列化的对象属性。
__wakeup方法:
具体代码:
class Test{public $sex;public $name;public $age;public function __construct($name, $age, $sex){$this->name = $name;$this->age = $age;$this->sex = $sex;}public function __wakeup(){echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";$this->age = 566;}
}$person = new Test('spaceman',21,'男');
$a = serialize($person);
echo $a."<br>";
var_dump (unserialize($a));
Unserialize反序列化恢复对象之前调用该方法
__isset()方法
具体代码:
class Person{public $sex;private $name;private $age;public function __construct($name, $age, $sex){$this->name = $name;$this->age = $age;$this->sex = $sex;}// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。public function __isset($content){echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";return isset($this->$content);}
}$person = new Person("spaceman", 25,'男');
// public 成员
echo ($person->sex),"<br>";
// private 成员
echo isset($person->name);
检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
__unset()方法
具体代码:
class Person{public $sex;private $name;private $age;public function __construct($name, $age, $sex){$this->name = $name;$this->age = $age;$this->sex = $sex;}// __unset():销毁对象的某个属性时执行此函数public function __unset($content) {echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";echo isset($this->$content)."<br>";}
}$person = new Person("spaceman", 21,"男"); // 初始赋值
echo "666666<br>";
unset($person->name);//调用 属性私有
unset($person->age);//调用 属性私有
unset($person->sex);//不调用 属性共有
在不可访问的属性(private)上使用unset()时触发
__INVOKE()
具体代码:
class Test{// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用public function __invoke($param1, $param2, $param3)
{echo "这是一个对象<br>";var_dump($param1,$param2,$param3);}
}$a = new Test();
$a('spaceman',21,'男');
将对象当做函数来使用时执行此方法,通常不推荐这样做。
#私有和公有属性——案例
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
序列化数据显示:
private 属性序列化的时候格式是%00 类名%00 成员名
protect 属性序列化的时候格式是%00*%00 成员名
具体代码:
class test{public $name="xiaodi";private $age="29";protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
将age改为public
所以通常对于序列化的数据,要进行一次编码,要考虑到%00的因素
#CTF-语言漏洞-__wakeup()方法绕过——案例
https://buuoj.cn/challenges
通过路径扫描,扫到一个www.zip的地址,访问便可进行下载去文件
此页面以GET方式select的变量接收,根据代码逻辑,得知__destruct方法在程序结束后被调用且password需等于100,__construct方法用new进行构造接收name和password 的值,__wakeup方法,在使用unserializ后自动调用,此时这里就出现了问题
我们要的是admin,而自动调用的是wakeup方法里的guest,那么就需想想wakeup方法该怎么绕过,通过历史漏洞得知——语言特性漏洞
靶场PHP版本符合
即将构造的序列化,变量个数改为3个,符合条件即可
(修改原理)
成功
#CTF-方法原生类-获取&利用&配合其他
参考:浅析PHP原生类-安全客 - 安全资讯平台
版本会与原生类有直接的关系
-PHP有那些原生类-见脚本使用
Get.php
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {$methods = get_class_methods($class);foreach ($methods as $method) {if (in_array($method, array('__destruct','__toString','__wakeup','__call','__callStatic','__get','__set','__isset','__unset','__invoke','__set_state'))) {print $class . '::' . $method . "\n";}}
}
会将魔术方法里自带的类给打印出来
-常见使用的原生类-见参加案例
1.ctf-xss
从此代码可以猜测要用的方法可能是To_string,由于它的本身代码就只有这一点,那么无法通过其他对象进行触发To_string,故需要用原生类的To_string,通过get.php,可以得到相关的To_string对象
Exception对象
对其进行构造,并加入xss攻击
成功
2.Ctfshow259
由于此题的getflag();方法,不知道它的具体是什么才可触发,所以先把它姑且为不可访问的类型,也就是可被__call()、__get()所触发,
查看自身的原生类
对其进行搜索
找到构造payload(题目中要求,ip为127.0.0.1,token为ctfshow)
将flag.txt发送到本地
-原生类该怎么使用-见官方说明
0、生成原生类
补:phar、字符串逃逸
反序列化:自身不是漏洞,只是一种方法,在方法中会引发一些危险函数。