【愚公系列】2024年08月 《CTF实战:从入门到提升》 011-Web安全入门(PHP反序列化漏洞)

举报
愚公搬代码 发表于 2024/08/31 23:33:52 2024/08/31
【摘要】 🏆 作者简介,愚公搬代码🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主...

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

PHP是一种广泛使用的开源服务器端脚本语言,它支持面向对象的编程范式。在PHP中,类和对象是用于封装数据与功能的核心组件,它们使得代码更加模块化、易于管理和扩展。在处理对象持久化——即在不同会话或不同请求之间保存对象状态的过程中,序列化和反序列化是两个非常重要的概念。

反序列化漏洞通常发生在不安全地反序列化用户提供的数据时。如果反序列化的数据被恶意篡改,攻击者可能会利用这个过程执行不安全的代码,这可能导致各种安全问题,包括但不限于:

  • 代码执行:攻击者可能会注入恶意对象,当这个对象被反序列化时,它可能会触发任意代码执行。
  • 对象注入:通过构造特殊的序列化数据,攻击者可能会在应用程序上下文中创建不正当的对象实例。
  • 数据泄露:攻击者可能通过篡改序列化字符串来访问或泄露敏感数据。
  • 服务拒绝(DoS攻击):通过发送大量复杂的序列化数据,攻击者可能试图耗尽服务器资源,导致服务不可用。

🚀一、PHP反序列化漏洞

🔎1.PHP的类与对象

在探讨PHP中的序列化与反序列化概念之前,有必要先理解面向对象编程中的一些基础概念和术语。

类(Class)
类是面向对象编程语言中的一个基本结构,它是创建对象的模板或蓝图。类定义了一组对象共有的属性(即变量)和方法(即函数)。通过类,可以描述一组具有相同特性(属性)和行为(方法)的对象。

对象(Object)
对象是类的一个实例。每个对象都拥有唯一的标识,并且包含了由其类定义的属性和方法。对象的属性用于存储特定于该对象的数据,而方法定义了对象可以执行的操作。在面向对象的软件中,对象是构成程序的基本单位。

方法(Methods)
方法是定义在类或对象中的子程序,用于执行特定的操作。方法可以处理输入参数,并可能返回一个值。在面向对象编程中,方法可以是类方法(也称为静态方法,属于类本身)、实例方法(属于对象实例),或工厂方法(用于创建并返回对象实例)。

属性(Properties)
属性是定义在类中的变量,用于描述对象的状态或特征。比如,如果你被视为一个对象,你的属性可能包括姓名、性别和年龄等。

总结以上内容,对象是通过类的实例化而创建的,包含了方法和属性。属性分为公开(public)、受保护(protected)和私有(private)三种访问级别:

  • 公开属性可以在类的内部和外部访问。
  • 受保护属性只能被所属类及其子类访问。
  • 私有属性仅能被其定义所在的类访问。

了解这些基本概念是学习PHP中序列化与反序列化过程的重要基础。

🔎2.PHP的序列化与反序列化

在介绍了面向对象编程中类和对象的基础概念之后,我们面临的一个重要问题是:如何存储这些通过类实例化出来的对象,并且如何实现与其他程序之间的数据交换?在数据库中存储简单的字符串很直接,通常只需使用文本(text)类型即可。然而,直接手工存储一个对象要复杂得多,因为你需要提取出对象的所有属性值并依次保存。如果属性值中还包含有对象,这个过程就需要递归进行,进一步提取并保存。相反的还原过程同样复杂,需要从存储形式中逐层提取并重建对象。

幸运的是,PHP提供了两个功能强大的函数来简化这一过程,这两个函数分别是 serialize()unserialize()

serialize() 函数

serialize() 函数的主要作用是将一个对象转换成一段可存储的文本表示,这个转换过程称为序列化。序列化后的字符串包含了对象的所有必要信息,允许我们在未来的某个时刻重新把这个字符串转换回原来的对象。这一功能使得对象的存储和交换变得可行,尤其是在需要将对象保存到数据库或通过网络传输对象时。

序列化的示例:

$myObject = new MyObject();
$serializedObject = serialize($myObject);
// $serializedObject 现在是一个字符串,可以保存到数据库或通过网络发送

通过序列化,PHP能够自动处理对象内部属性的递归提取和保存工作,无论这些属性是否包含更复杂的对象。这不仅简化了对象存储的流程,也为对象之间的交换提供了一种标准化的方法。

在这里插入图片描述
一般来说,它接收一个值作为参数然后返回一个字符串来代表这串值。我们用下面这段程序来做一个小实验。这个实验实例化了一个类Person的对象p,然后把这个对象传给serialize进行调用,再尝试打印调用的返回值。

代码如下:
在这里插入图片描述
运行上述代码,可以看到输出了下面这样的一段字符串,如图所示

在这里插入图片描述
像上面这样,形如0:7:…这样的字符串就是代表p这个类对象的字符串刚才我们就进行了一次序列化操作。

注意,图中有几个方框字符这些字符并非本身是方框,而是因为它们所代表的是保护和私有的属性,PHP需要用一些特殊的字符来包裹对应的属性名,用来存储它们的属性。为了更好地进行演示,这里我们给它进行一下URL编码,用于之后的演示。下面我们给输出结果包裹urlencode()来看一看代码如图所示。

在这里插入图片描述
运行上述代码,可以看到编码之后的序列化字符串,结果如图所示。
在这里插入图片描述
再来看能把字符串转换回对象的函数unserialize(),我们给它传入一个字符串,调用它之后即可得到这个字符串所对应的对象。这个把字符串转换回对象的过程就叫反序列化,如图所示。
在这里插入图片描述

看下面这样一个例子,我们把刚刚获得的字符串传入unserialize(),然后把获取到的返回值也就是这个字符串对应的对象保存到p这个变量中,再尝试调用这个对象的方法getAge(),获取它的age属性进行输出。代码如下:

在这里插入图片描述
运行上述代码,可以看到成功输出了对象的属性age的值,如图所示。
在这里插入图片描述

这证明我们成功进行了一次反序列化操作。

🔎3.PHP中的反序列化漏洞

关于对象和对象的序列化、反序列化的相关知识介绍完毕,接下来就该看看其中有什么薄弱点可供利用了在构造一个对象时,如果想要给对象动态设置一些初始属性,就需要在类的定义中添加一个叫构造方法的方法,在PHP中这个方法叫__construct。

来看下面这个例子,我们给Person类定义了一个构造方法,接受三个参数把这三个参数的值分别复制给自己的三个成员变量,再尝试输出age的值。代码如下:

在这里插入图片描述
运行上述代码,如图所示,可以看到age的值是25,也就是我们传给构造方法的值。

在这里插入图片描述
同样,在程序结束运行,对象被销毁时也可以定义一个在此时会被调用的方法,叫析构方法。在PHP中这个方法叫destruct。代码如下:

在这里插入图片描述
运行上述代码,如图所示,可以看到程序在输出完age的值之后,输出“我 被销 毁 啦 ~”这句话,说明__destruct 这个方法在程序运行结束,对象被销毁时被调用了。

在这里插入图片描述
除了这些方法,还有没有其他的方法?我们再来看__wakeup这个方法,这个方法在对象被反序列化时会被调用。来看下面这个例子,在这个例子中我们定义了一个__wakeup方法,尝试对对象进行序列化然后反序列化。

在这里插入图片描述
运行上述代码,如图所示

在这里插入图片描述

可以看到“我正在被反序列化~”这句话是在“序列化完成,正在反序列化”之后被输出的,证明 wakeup这个方法是在反序列化时被调用的。

上面介绍了几种特殊的方法定义,除此之外在PHP的类定义中还有类似__sleep(序列化时调用,用于指定那些属性会被序列化保存下来)、__cal(当调用不存在的类方法时就会调用到)、__get(当尝试获取不存在的类它,属性时会调用它)等,篇幅有限,读者可自行了解。在PHP中,这类方法统称魔术方法。

了解了那么多魔术方法,那么如何使用它们来进行进一步的利用?不知读者有没有设想过这样一种情况,如果在__destruct 等这种会在对象生命周期(创建、销毁等)被调用的方法中,存在一些只要参数可控就会造成破坏的函数调用(如后文会提到的代码执行漏洞中的eval和assert函数,以及命令执行漏洞中会提到的system等函数),并且这些函数的参数来自类的属性,那么我们只要想办法反序列化出来一个对象,对象中的属性我们可控,那么就只要程序运行结束,对象被销毁,那么我们就可以操控这些函数的调用,让它执行我们想要执行的代码或者命令了。

来看下面这样一个例子。在这个例子中我们定义了一个魔术方法__destruct,其中调用system输出形如“hello,A”这样的字符串。之后我们定义了一个类对象p,将其name属性设置为A;whoami。代码如下:
在这里插入图片描述
运行上述代码,可以看到共有两行输出,第一行是“hello,A”。第二行则是whoami的执行结果jinzhao,输出了当前系统的用户。证明我们通过控制属性,控制了__destruct这个析构函数的执行结果。

代码如下:
在这里插入图片描述
运行上述代码,如图所示,获得一串序列化之后的字符串。

在这里插入图片描述
然后再把这串字符串反序列化。代码如下:
在这里插入图片描述
运行上述代码,如图所示,可以看到一样的效果。拿到了whoami执行的结果。

在这里插入图片描述
通过上面这个例子,我们了解到了在反序列化中,通过控制属性,可以让有调用到这个属性的魔术方法朝着我们想要的方向去执行。

那么问题又来了,如何在目标上反序列化出这样一个对象?有两种方式

  • 直接控制unserialize函数的参数值,很少见,但只要能控制,就可以随心所欲反序列化出我们想要的对象了,如图所示。
    在这里插入图片描述

  • 通过文件操作函数来进行反序列化的操作,如file_get_contents、file_exist等函数。倘若它们的参数可控,不仅可以读取到任意文件的内容,同时也可以进一步扩展,达到RCE等目的,如图所示。
    在这里插入图片描述

为什么读取一个文件也会造成反序列化?我们来看PHP手册中对于PHAR这种压缩文件的一段描述,如图所示。
在这里插入图片描述
简单来说,就是可以在这个压缩文件中存储一个对象,当这个压缩文件被PHP读取时,就会反序列化这个对象,内存中就存在这个对象了。那么如果这个对象有析构函数或者是其他的魔术方法定义,则在特定的时机(如程序运行结束时)就会被调用,可控的对象+可以利用的魔术方法就构成了我们对于反序列化漏洞的利用了.

下面我们利用PHP代码来尝试生成一个带有对象的PHAR文件。代码如下
在这里插入图片描述
运行上述代码,读者会在同级目录下看到一个phar.phar文件,如图所示。

在这里插入图片描述
然后再使用下面的PHP代码去读取这个文件:
在这里插入图片描述
在这里插入图片描述
运行上述代码,如图所示
在这里插入图片描述

可以看到成功调用了Person类对象的析构方法,并像之前的例子一样,控制该对象的属性,等析构方法被调用时执行命令。

通过上述例子,我们基本了解了反序列化漏洞的概念和基本的利用方法。下面来看个比赛中的题目,加深理解

🔎4.案例解析–2019强网杯 UPLOAD

来看看“2019强网杯的UPLOAD”这个竞赛题,先打开靶机看一看,如图所示。

在这里插入图片描述
看起来是个登录和注册页面,那么就先注册然后登录试试吧,如图所示。
在这里插入图片描述

登录之后看到了如图所示的页面,测了一下只能上传能被正常查看的png。
在这里插入图片描述
跳转到了一个新的页面,这个页面似乎没有任何实际功能了。然后可以看到我们的图片是被正确上传到服务器上的/upload/da5703ef349c8b4ca65880a05514ff89/录下了,如图所示。

在这里插入图片描述
然后我们来扫描敏感文件,发现www.tar.gz下有内容,下载下来解压看看,发现是用ThinkPHP 5框架写的,如图所示。

在这里插入图片描述
而且其有.idea目录,我们将其导入PHPStorm。发现其在application/web/controller/Register.php和application/web/controller/Index.php下有两个断点,估计是Hint。

application/web/controllerRegister.php下的情况如所示。

在这里插入图片描述
application/web/controller/Index.php下的情况如图所示。

在这里插入图片描述

经查看发现这两个点的流程大概如下

  • application/web/controllerIndex.php中的断点流程:首先访问大部分页面(如index)都会调用login_check方法。该方法会先将传入的用户Profile反序列化,而后到数据库中检查相关信息是否一致。
  • application/web/controller/Registerphp中的断点流程:Register的析构方法,估计是想判断是否注册,没注册的调用check也就是Index的index方法,即跳到主页。

接下来再来审一下其他代码,发现上传图片的主要逻辑思路在applicationweb/controller/Profile.php中,如图所示。
在这里插入图片描述
先检查是否登录,然后判断是否有文件,获取扩展名,解析图片判断是否为正常图片,再从临时文件复制到目标路径。

而Profile有_call和_get两个魔术方法,分别编写了在调用不可调用方法和不可调用成员变量时怎么做,如图所示。

在这里插入图片描述
别忘了前面我们有反序列化和析构函数的调用,结合这三个地方就可以操控Profile中的参数,控制其中的upload_img方法,这样就能任意更改文件名,让其为我们所用了。

首先用蚁剑生成个Webshell,再用hex编辑器构造个“图片马”,重新注册个新号上传上去。如图所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后构造一个Profile和Register类,命名空间app\ web\ controller(要不然反序列化会出错,不知道对象实例化的是哪个类)。接下来给其except成员变量赋值[index’->'img],代表要是访问index这个变量,就会返回img。再给img赋值upload_img,让这个对象被访问不存在的方法时最终调用upload_img,如图所示。
在这里插入图片描述

接下来我们赋值控制filename tmp和filename成员变量。可以看到前面两个判断我们只要不赋值和不上传变量即可轻松绕过。cxt这里也要赋值,让它进入这个判断。而后程序就开始把filename_tmp移动到filename,这样我们就可以把png移动为php文件了。

我们还要构造一个Register,checker赋值为上面的$profile,registed赋值为false,这样在这个对象析构时就会调用profile的index方法,再跳到upload_img。

最终Poc生成脚本如下:
在这里插入图片描述
在这里插入图片描述
注意这里的文件路径,Profile的构造方法有切换路径,这里我们反序列化的话似乎不会调用构造方法,所以得自己指定一下路径。

运行上述代码,得到Poc,然后置coookie,如图所示。

在这里插入图片描述

刷新页面,结果如图所示。
在这里插入图片描述
可以看到我们的小马已经能访问了,如图所示。

在这里插入图片描述
连接“蚁剑”,打开/flag文件即可如图所示。

在这里插入图片描述


🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。