今天简单介绍一下php反序列化漏洞,其实这个漏洞我们在实战中并不常见,经常在某些CTF比赛,或者是面试的时候会问到,所以这个我们还是需要了解的。本文主要讲解的是PHP的反序列化。
要了解反序列化漏洞,首先要了解关于序列化的一些概念。
序列化 (serialize)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。简单来说就是将对象转换为字符串
反序列化和序列化刚好相反,就是将这个状态信息拿出来再进行使用。就是将字符串再转换成对象。
其实这个过程就好像是我们玩单机游戏,有一个存档和读档的功能,存档就相当于是序列化,将当前的一个人物或者关卡信息进行一个存档,再我们下次玩的时候,就会将这些东西再拿出来,相当于一个反序列化。再或者我们网购一张大的床,商家在发给我们的时候会直接是一张床吗,那肯定是将床拆开,这样的话也便于运输存储,这一过程和我们的序列化是一样的,当我们收到货的时候,再将床进行一个组装使用,这就相当于反序列化。
行动或思考时作为目标的事物,我们在学习C的时候应该听说万物皆对象这句话,也就是说一切的事物都可以叫做对象,搞对象,有的有两个对象,有的有三个,有的一个没有等等,这都叫做对象。
实现某种功能的集合,这个概念就比较抽象了,不过我们经常可以听到物以类聚,人以群分,比如我们是人类,这个都属于是类。
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象
php反序列化,我们从两个方面进行讲解,分别是无类和有类。
我们来用一个CTF的题目进行讲解(放在了本地)
<?php
error_reporting(0);
include "flag.php";
$KEY = "nihao";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);
?>
我们想要得到flag就得明白这段代码是什么意思?
error_reporting() 是一个PHP函数,用于设置脚本的错误报告级别。它控制PHP引擎将哪些类型的错误或警告显示给用户。我们的代码中设置的级别是0,也就是说禁用所有错误报告,即不显示任何错误、警告或提示信息。
接下来是包含了一个flag.php文件,以我们的一个经验来说,在这个文件中肯定有我们想要的一个flag,再下来是将nihao赋值给变量KEY,使用GET传参,传的是str,将传进来的参数赋值给变量str,下面是一个判断语句:如果我们传进的参数的反序列化===$KEY,则输出flag。
show_source(__ FILE__ )表示的是将该文件的源代码输出到浏览器上面。show_source()表示的高亮显示文件中的内容,比如我们想要显示C盘下的1.php文件,则表示为show_source('c://1.php');__FILE__ 是一个魔术常量,表示当前文件的完整路径和文件名。
在代码中=表示的是赋值
==表示的是等于,转换成相同的类型,进行比较,比如:'123'==123返回的是ture。
===表示的是恒等于,它会将内容和类型都进行比较,比如:'123'===123返回的是false。
读懂以上代码的话,这个题就非常简单了,我们传进去的str的反序列化等于nihao,就可以实现输出flag,反着推一下,就是将nihao进行序列化一下,传进去,代码会将他反序列化正好又回到了nihao,这样就可以实现输出flag。将nihao序列化一下
传进去,看结果
可以看到确实是显示出来了flag.
<?php $flag='you are very cool'; ?>
这是一个字符型的,我们将他改成整型的看一下效果
<?php
error_reporting(0);
include "flag.php";
$KEY = 123;
$str = $_GET['str'];
if (unserialize($str) === $KEY)
{
echo "$flag";
}
show_source(__FILE__);
?>
依据字符型的一个传参,我们可以猜到应该传:i:3:"123",但是并不是,我们应该用php在线运行工具看一下123的序列化是什么?
将其上传
得到他的Flag。
判断它有没有类,只需要看他有没有class,我们用以下代码进行一个讲解
<?php
show_source(__FILE__);
class nihao{
var $test='123';
}
$A = new nihao;
$B = serialize($A);
var_dump($B);
?>
解释一下这段代码,代码中定义了一个名为 nihao 的类(类名的命名规则通常建议遵循驼峰命名法,首字母小写),类中有一个名为 test 的属性,其值为 '123'。
接下来,通过创建一个 nihao 类的实例并赋值给变量变量A。然后,使用 serialize() 函数将该实例序列化为一个字符串,并将结果赋值给变量变量B.
最后,使用 var_dump() 打印出变量B 的值,以便查看该序列化字符串的结果。
看一下输出结果
结果为:string(37) "O:5:"nihao":1:{s:4:"test";s:3:"123";}" 当前的是个对象,调用什么nihao类 只有1个值,来看一下两个值的
<?php
show_source(__FILE__);
class nihao{
var $test='123';
var $C="ccc";
}
$A = new nihao;
$B = serialize($A);
var_dump($B);
?>
这是一个简单的类的讲解,可能看到这里你还是会很迷惑,这个序列化也好,反序列化也好,不就是个正常功能吗,这哪里会有漏洞产生呢?接下来说一下漏洞原理。
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,sql注入,目录遍历等不可控后果,在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
在原理中提到了一个名词:魔术方法。魔术方法是在 PHP 类中定义的特殊方法,以双下划线开头和结束,例如 __construct(),__toString(),__get() 等。这些方法会在特定的情况下自动调用,而无需手动调用。魔术方法使得我们可以对类的行为进行自定义,并实现一些特殊的功能,如对象的初始化、对象的字符串表示、属性的访问控制等。通过定义魔术方法,我们可以在特定的时候干预类的行为和处理。
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
搞三个简单的说下:__construct()、__destruct()、__wakeup()
<?php
class nihao{
var $test=123;
function __construct()
{
echo '我是构造函数';
}
}
$a =new nihao;
?>
这个函数在new的时候会自行调用
<?php
class nihao {
var $test = 123;
function __destruct() {
echo '我是析构函数';
}
}
$a = new nihao;
?>
这个函数在代码执行完毕的时候会自行调用
<?php
class nihao{
var $test;
function __wakeup(){
echo '我是苏醒函数';
}
}
上面的代码看图片里输入的,__wakeup()函数是在使用反序列化的时候自行调用。
网鼎杯2020青龙大赛真题
地址:https://www.ctfhub.com/#/index
由题目命名和函数unserialize可以判断此题目考察的是反序列化知识点。主要是获取flag--存储flag.php 先进行代码分析,看看代码说了什么,看这段代码
这段代码从下往上看,GET接收一个str参数,然后用is_valid进行检测,检测通过就将str进行反序列化,中间一段代码是is_valid的检测内容。最后一段代码执行完成以后,也就是销毁了,在销毁的时候触发了析构函数_destruct.然后析构函数就开始执行。如果op===2,则会强制让他转为1.
isset()函数用于检测变量是否已设置并且非 NULL,符合返回ture,不符合返回false
is_valid()函数检查对象变量是否已经实例化,即实例变量的值是否是个有效的对象。如果指定对象已经创建了对此案实例,那么IsValid()函数返回True,否则返回FALSE。如果参数obejctname的值为NULL,IsValid()函数返回NULL。
接着看下面的代码
传入的op=1就执行write写入,如果为2,就执行读取,因为我们是要flag,所以肯定是要他执行读取。
__destruct函数对this->op进行判断。所以我们要绕过_destruct函数的判断,进入到process函数中的读取。这里使用字符串' 2'(空格2)进行绕过。这样就可以执行读取。
因为最后一步是将str进行反序列化,我们传入的时候就应该是序列化。
传入到str,右键查看源代码。
以上大概就是php反序列化的全部内容,这应该是所有漏洞中最需要代码审计的漏洞,希望能对你有所帮助。如有不足之处,还望指正。
星光安全面向基础!
项目下载