题目的大致思路就应该让我们成为admin吧
随便注册了一个后,发现有一个更改密码
之前题目的经验,那应该找回密码有漏洞的。抓包后发现,我们用户名买这个id参数修改不了
session到是有,那我们进去以后源码就出来了
将所给地址源码下载下来,便是这道题的源码了,使用flask框架写的,先看看routes.py,看看都允许访问哪些路径吧
@app.route('/code')
...//生成验证码
@app.route('/')
@app.route('/index')
...
@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')
...
@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'])
.....
@app.errorhandler(404)
.....
def strlower(username):
username = nodeprep.prepare(username)
return username
这里省略了一些不太会用到的路径,接着进行代码审计,发现了一个问题这里在注册时,会将我们传入的用户名数据转变为小写,
并且在改密码处也有相同的操作
这里使用的不是python自带的lower()方法,而是自定义的strlower(),或许有什么端倪,先来看看.
这里使用了一个nodeprep.prepare()进行小写操作,我们并不知道是什么,然后进行谷歌
发现这是Twisted包中的一个方法,接着查看requirements.txt发现当前环境Twisted为10.2.0,然后去github上查看当前最新版本已经19.2.1了,说明这里会有问题
最后找到两篇文章
<a href=”https://labs.spotify.com/2013/06/18/creative-usernames/
“>有关Twisted某个版本转换问题
最后得到信息:
说明在11.0版本以下的Twisted均存在这样的问题,我们环境版本为10.2.0刚好符合,同时提到nodeprep.prepare()不具有幂等性,即对某个数据反复进行某个操作,总会得到相同的结果举个例子
这种情况a字符串无论进行多少次lower()都会得到一个结果,因此lower()方法具有幂等性,而nodeprep.prepare(),则会出现下面的情况
也就是说,同样举个例子,也就说:
ᴺ—->N—->n(Unicode中相同的字母和符好,对应的编码可能不同,如:Ω和Ω)
nodeprep.prepare()会执行类似于这样的转换,在第一次进行转化时会将其匹配为类似的大写字母,然后第二次才将其匹配为小写字母,两次相同操作所得结果不一样,因此这个方法不具有幂等性.
首先我们注册一个
ᴬᴰmin的账户,这时候我们传入的数据会进行一次转化,这时ᴬᴰmin—>ADmin,服务器端会判断该用户是否存在,然后成功注册
登陆该账号,因为登陆时也会被进行一次转化,所以使用ᴬᴰmin登陆,但后台的账号是ADmin
我们可以看到账号登陆成功,且为ADmin
然后执行一次更改密码操作,改密码时也会进行一次转化,这时我们便从ADmin—>admin,完成了admin账户的密码更改操作,这时候再登陆admin账号,就可以得到flag了