在CTFd v2.0.0-v2.2.2的注册过程中,错误的用户名验证方式会允许攻击者接管任意帐户,前提是用户名已知并且在CTFd平台上启用了电子邮件功能。
CTFd v2.0.0版本注册部分的代码
CTFd/CTFd/auth.py#159
CTFd/CTFd/auth.py#207
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 159 def register(): 160 errors = get_errors() 161 if request.method == "POST": 162 name = request.form["name"] 163 email_address = request.form["email"] 164 password = request.form["password"] 165 166 name_len = len(name) == 0 167 names = Users.query.add_columns("name", "id").filter_by(name=name).first() 168 emails = ( 169 Users.query.add_columns("email", "id") 170 .filter_by(email=email_address) 171 .first() 172 ) 173 pass_short = len(password) == 0 174 pass_long = len(password) > 128 175 valid_email = validators.validate_email(request.form["email"]) 176 team_name_email_check = validators.validate_email(name) # 省略部分代码 207 else: 208 with app.app_context(): 209 user = Users( 210 name=name.strip(), 211 email=email_address.lower(), 212 password=password.strip(), 213 )可以看到用户注册时name参数并未经过任何处理,判断用户名是否重复时使用的就是没有经过任何处理的name值,然而存入数据库时却将这个name值做了 strip处理,去掉name值首尾的空字符。
这就意味着只要注册一个首尾添加空格的用户名即可绕过用户名不能重复的限制。
CTFd v2.0.0版本找回密码部分的代码
CTFd/CTFd/auth.py#95
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 95 @auth.route("/reset_password", methods=["POST", "GET"]) 96 @auth.route("/reset_password/<data>", methods=["POST", "GET"]) 97 @ratelimit(method="POST", limit=10, interval=60) 98 def reset_password(data=None): 99 if data is not None: 100 try: 101 name = unserialize(data, max_age=1800) 102 except (BadTimeSignature, SignatureExpired): 103 return render_template( 104 "reset_password.html", errors=["Your link has expired"] 105 ) 106 except (BadSignature, TypeError, base64.binascii.Error): 107 return render_template( 108 "reset_password.html", errors=["Your reset token is invalid"] 109 ) 110 111 112 return render_template("reset_password.html", mode="set") 113 if request.method == "POST": 114 user = Users.query.filter_by(name=name).first_or_404() 115 user.password = request.form["password"].strip() 116 db.session.commit() 117 log( 118 "logins", 119 format="[{date}] {ip} - successful password reset for {name}", 120 name=name, 121 ) 122 db.session.close() 123 return redirect(url_for("auth.login"))大致可以理解为找回密码时从链接参数中取data值,将其反序列化后获得用户名,即可重置任意用户的密码。
利用该漏洞需要以下几步:
利用首尾添加空格绕过限制,注册一个与受害者用户名相同的账号找回密码链接发送到自己的邮箱修改自己账号的用户名(与受害者不同)点击重置密码链接,设置新密码V&N 2020公开赛HappyCTFd原题,buuoj有对应的环境。
具体操作主要分为以下几步:
先在buuoj注册个邮箱
利用首尾添加空格绕过限制来注册一个与受害者用户名相同的账号
找回密码链接发送到自己的邮箱
修改自己账号的用户名(与受害者不同)
点击重置密码链接,设置新密码
由于buuoj靶机无法访问外网,所以需要在buuoj的邮件系统注册邮箱。
http://mail.buuoj.cn/admin/ui/user/signup/mail.buuoj.cn
然后我们访问靶机地址,发现该ctfd平台有一个admin账号,所以我们尝试重置admin账号的密码。
首先注册一个首或者尾带空格的admin账号,邮箱需要设置正确。
注册成功可以发现自己后台的用户名首尾并没有空格。
然后尝试重置admin密码,浏览器另外开一个页面,输入自己注册账号的邮箱,获取重置密码链接。
在收件箱中收到重置密码链接后,先不要操作,需要去用户后台页面修改用户名(任意)。
修改完自己的用户名直接点击重置密码链接,对admin账号进行密码重置,设置一个你想要的密码。
再次用admin和你设置的密码登录,就成功拿到了超级管理员权限。
flag在challenges里面找一下,发现有个”flagflag你在哪”的挑战,打开后找到一个”miaoflag.txt”的附件,即flag。
更新CTFd为最新版即可。