1、問題分析
熟悉一下網站,有這些功能:註冊、登錄、上傳文章、修改密碼、登出
對源碼進行觀察,發現
源碼中存在
<!-- you are not admin -->
可能我們要以管理員身份登錄吧。
繼續找線索,最後發現在修改密碼的源碼中有:
https://github.com/woadsl1234/hctf_flask/
這個是網站用的flask源碼,然後clone下來就可以進行代碼審計了。
routes.py裏面有頁面所有功能的代碼
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
from flask_login import logout_user, LoginManager, current_user, login_user
from app import app, db
from config import Config
from app.models import User
from forms import RegisterForm, LoginForm, NewpasswordForm
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
from io import BytesIO
from code import get_verify_code
@app.route('/code')
def get_code():
image, code = get_verify_code()
# 圖片以二進制形式寫入
buf = BytesIO()
image.save(buf, 'jpeg')
buf_str = buf.getvalue()
# 把buf_str作爲response返回前端,並設置首部字段
response = make_response(buf_str)
response.headers['Content-Type'] = 'image/gif'
# 將驗證碼字符串儲存在session中
session['image'] = code
return response
@app.route('/')
@app.route('/index')
def index():
return render_template('index.html', title = 'hctf')
@app.route('/register', methods = ['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegisterForm()
if request.method == 'POST':
name = strlower(form.username.data)
if session.get('image').lower() != form.verify_code.data.lower():
flash('Wrong verify code.')
return render_template('register.html', title = 'register', form=form)
if User.query.filter_by(username = name).first():
flash('The username has been registered')
return redirect(url_for('register'))
user = User(username=name)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('register successful')
return redirect(url_for('login'))
return render_template('register.html', title = 'register', form = form)
@app.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data)
session['name'] = name
user = User.query.filter_by(username=name).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title = 'login', form = form)
@app.route('/logout')
def logout():
logout_user()
return redirect('/index')
@app.route('/change', methods = ['GET', 'POST'])
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name'])
user = User.query.filter_by(username=name).first()
user.set_password(form.newpassword.data)
db.session.commit()
flash('change successful')
return redirect(url_for('index'))
return render_template('change.html', title = 'change', form = form)
@app.route('/edit', methods = ['GET', 'POST'])
def edit():
if request.method == 'POST':
flash('post successful')
return redirect(url_for('index'))
return render_template('edit.html', title = 'edit')
@app.errorhandler(404)
def page_not_found(error):
title = unicode(error)
message = error.description
return render_template('errors.html', title=title, message=message)
def strlower(username):
username = nodeprep.prepare(username)
return username
開頭看到
app.route('/')
@app.route('/index')
def index():
return render_template('index.html', title = 'hctf')
說明用了模板渲染,用的是index.html,查看index.html的代碼:
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>
{% include('footer.html') %}
顯示flag的條件是:
current_user.is_authenticated and session[‘name’] == ‘admin’
session的那麼需要修改,當前的用戶還需要得到授權,當然admin用戶是有授權的。
2、flask session僞造
關於flask,我們知道flask 的session是存在於客戶端(瀏覽器)的cookie中的,我們在請求頭中可以看到,而在安全方面,flask對數據僅僅是進行了簽名,防止數據被篡改,但是數據仍然可以被讀取。
而用戶的身份信息在也存在於session中,可以對session進行修改來達到僞造身份的目的。
我們需要的是對session進行加解密來進行session修改。
拿到session:
.eJw9UE2LwkAM_StLzh7aai8FDy61RSFTWkZK5iLdWrWZGReqUjvif9_BBQ8hHy95vJcn7I9Ddz1Dchvu3Qz2_QGSJ3z9QAIqVRplG2IuWMhTKPJ1QFFmyZUPJTNDvBlVWjE51WMquEg3C5GXC-F0hLyKKSpjvxsTn-YojcY60ySFRda-1zFamitJI6Y4Vz78zVjk1ZlYGcVrz91Ofh4p1hNFgol3EzpfOQqFax_E31qkqxHrzRJeM2ivw3F_-9Xd5WMB2ZxJZqzs1svDgNzW-ByRPFgvpfcWAuI2RlcGoq50IZWhcvmm621z6j5McndbVOM_cmmsB6BpYAb3aze8nwZhAK8_y-pr_w.XYzBwA.8riiXn6Rgx5ONsRDxM5CJMp_9r8
然後進行解密:
解密之後的session結構就是:
{’_fresh’: True, ‘_id’: b’d6d1750cc5854a4cafc41e1eb20d4cc6b03c8284d8796209cd9c49b8719d1ada3f2971992f7e60037d372008dab6ef10d72d36f92cccb52366765771b0d4001b’, ‘csrf_token’: b’29aa1cfbc834c2e836a7fa3bb24b793445dd96ea’, ‘image’: b’MKxG’, ‘name’: ‘aa’, ‘user_id’: ‘10’}
然後把name修改爲admin再進行加密:
然後把session修改爲我們僞造的加密結果:
然後就拿到flag了。