千家信息网

如何进行CVE-2020-7245漏洞分析

发表于:2024-10-21 作者:千家信息网编辑
千家信息网最后更新 2024年10月21日,这篇文章将为大家详细讲解有关如何进行CVE-2020-7245漏洞分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。简介该漏洞是一个CTFd的账户接管
千家信息网最后更新 2024年10月21日如何进行CVE-2020-7245漏洞分析

这篇文章将为大家详细讲解有关如何进行CVE-2020-7245漏洞分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

简介

该漏洞是一个CTFd的账户接管漏洞,在注册和修改密码处,存在逻辑漏洞,从而导致可以修改任意账号密码。

影响版本:v2.0.0-2.2.2

漏洞分析

首先定位到用户注册处:/CTFd/auto.py

@auth.route("/register", methods=["POST", "GET"])@check_registration_visibility@ratelimit(method="POST", limit=10, interval=5)def register():errors = get_errors()if request.method == "POST":name = request.form["name"]email_address = request.form["email"]password = request.form["password"]name_len = len(name) == 0names = Users.query.add_columns("name", "id").filter_by(name=name).first()emails = (Users.query.add_columns("email", "id").filter_by(email=email_address).first())pass_short = len(password.strip()) == 0pass_long = len(password) > 128valid_email = validators.validate_email(request.form["email"])team_name_email_check = validators.validate_email(name)if not valid_email:errors.append("Please enter a valid email address")if email.check_email_is_whitelisted(email_address) is False:errors.append("Only email addresses under {domains} may register".format(domains=get_config("domain_whitelist")))if names:errors.append("That user name is already taken")if team_name_email_check is True:errors.append("Your user name cannot be an email address")if emails:errors.append("That email has already been used")if pass_short:errors.append("Pick a longer password")if pass_long:errors.append("Pick a shorter password")if name_len:errors.append("Pick a longer user name")if len(errors) > 0:return render_template("register.html",errors=errors,name=request.form["name"],email=request.form["email"],password=request.form["password"],)else:with app.app_context():user = Users(name=name.strip(),email=email_address.lower(),password=password.strip(),)db.session.add(user)db.session.commit()db.session.flush()login_user(user)if config.can_send_mail() and get_config("verify_emails"):  # Confirming users is enabled and we can send email.log("registrations",format="[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}",)email.verify_email_address(user.email)db.session.close()return redirect(url_for("auth.confirm"))else:  # Don't care about confirming usersif (config.can_send_mail()):  # We want to notify the user that they have registered.email.sendmail(request.form["email"],"You've successfully registered for {}".format(get_config("ctf_name")),)log("registrations", "[{date}] {ip} - {name} registered with {email}")db.session.close()if is_teams_mode():return redirect(url_for("teams.private"))return redirect(url_for("challenges.listing"))else:return render_template("register.html", errors=errors)

上述代码,有一大半是进行输入检测的,提取出来关键部分:

def register():errors = get_errors()if request.method == "POST":name = request.form["name"]email_address = request.form["email"]password = request.form["password"]name_len = len(name) == 0names = Users.query.add_columns("name", "id").filter_by(name=name).first()emails = (Users.query.add_columns("email", "id").filter_by(email=email_address).first())pass_short = len(password.strip()) == 0pass_long = len(password) > 128valid_email = validators.validate_email(request.form["email"])team_name_email_check = validators.validate_email(name)if len(errors) > 0:         #检测出错'''注册账户密码插入数据库'''else:                   with app.app_context():user = Users(name=name.strip(),email=email_address.lower(),password=password.strip(),)db.session.add(user)db.session.commit()db.session.flush()login_user(user)if config.can_send_mail() and get_config("verify_emails"):  # Confirming users is enabled and we can send email.log("registrations",format="[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}",)email.verify_email_address(user.email)db.session.close()return redirect(url_for("auth.confirm"))

上方的上半部分,接受用户的输入信息:

def register():errors = get_errors()if request.method == "POST":name = request.form["name"]email_address = request.form["email"]password = request.form["password"]name_len = len(name) == 0names = Users.query.add_columns("name", "id").filter_by(name=name).first()emails = (Users.query.add_columns("email", "id").filter_by(email=email_address).first())pass_short = len(password.strip()) == 0pass_long = len(password) > 128valid_email = validators.validate_email(request.form["email"])team_name_email_check = validators.validate_email(name)

其关键在于这里:

names = Users.query.add_columns("name", "id").filter_by(name=name).first()

在判断用户是否已经注册时,是直接用的name,也就是用户输入的账户名,并且没有任何的过滤。

在下半部分,注册成功时,将账户、密码、邮箱插入到数据库中:

with app.app_context():user = Users(name=name.strip(),email=email_address.lower(),password=password.strip(),)db.session.add(user)db.session.commit()db.session.flush()

但是这里又对用户输入的账户进行了去除空格的操作。(也就是说,如果数据库中存在m1sn0w这个账户,但是,如果我在注册时输入的账户名为:空格m1sn0w,那么,注册时不会提示账户已存在,而是将m1sn0w这个用户名插入到数据库中,也就是数据库中有了同名用户)

接下来是第二个利用点(修改密码):提取出主要代码

@auth.route("/reset_password", methods=["POST", "GET"])@auth.route("/reset_password/", methods=["POST", "GET"])@ratelimit(method="POST", limit=10, interval=60)def reset_password(data=None):if data is not None:try:name = unserialize(data, max_age=1800)except (BadTimeSignature, SignatureExpired):return render_template("reset_password.html", errors=["Your link has expired"])except (BadSignature, TypeError, base64.binascii.Error):return render_template("reset_password.html", errors=["Your reset token is invalid"])if request.method == "GET":return render_template("reset_password.html", mode="set")if request.method == "POST":user = Users.query.filter_by(name=name).first_or_404()user.password = request.form["password"].strip()db.session.commit()log("logins",format="[{date}] {ip} -  successful password reset for {name}",name=name,)db.session.close()return redirect(url_for("auth.login"))

我们知道,在修改密码时,会向相应的邮箱发送一封邮件,点击之后,才能修改密码。(上面的data值,也就是发送给指定邮箱的URL后面的一串值)

接下来,看看data值是什么:/CTFd/utils/email/__init__.py

def forgot_password(email, team_name):token = serialize(team_name)text = """Did you initiate a password reset? Click the following link to reset your password:{0}/{1}""".format(url_for("auth.reset_password", _external=True), token)return sendmail(email, text)

可以看到,它是将用户名序列化之后,拼接到相应URL后面,发送给邮箱。(通过前面的分析,我们知道数据库中的账号有两个是同名,那么进行修改密码操作时,就会修改第一个用户的密码)

(有些文章说需要修改当前用户为其他的用户名,但感觉好像不需要)

if request.method == "POST":user = Users.query.filter_by(name=name).first_or_404()user.password = request.form["password"].strip()db.session.commit()

它这里取出来的用户就是第一个用户(也就是先前注册的那个用户)

所以,大致的利用方法如下:

1、注册一个账号,和想要修改的那个用户名同名,但在注册时加上空格

2、点击修改密码,在邮箱确认,即可修改指定用户密码

关于如何进行CVE-2020-7245漏洞分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

用户 密码 账户 漏洞 数据 数据库 也就是 邮箱 输入 分析 用户名 漏洞分析 文章 空格 账号 接下来 代码 关键 内容 更多 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 服务器独立显卡驱动 政务网络安全管理总结 国际间网络安全合作 服务器对外开放配置文件 软件开发首选桌面指纹仪哪家便宜 emory服务器 pk fk 数据库 软件开发专业知识积累 软件开发企业税收的相关规定 网络安全知识竞赛有奖项吗 长沙软件开发驻场公司有哪些 什么是服务器服务器的功能是什么 迷你水手服务器 静安区无线网络技术有哪些 无锡原则软件开发来电咨询 珠海做旅游平台的软件开发 信息与网络安全的推荐信 浙江综合软件开发性价比 江门物联网软件开发多少钱 局域网服务器共享软件 国外服务器ice失败 长宁区信息化数据库系统研发资格 苹果手机本地备份泄露数据库 中科工作站服务器是什么 结婚仪式视频软件开发 ui软件开发培训 网络安全绘画作品小学生一等奖 我的世界服务器风云第一集 发生在自己身边的网络安全故事 黄浦区创新数据库服务前景
0