最简单的OTP泄露漏洞:利用头部污染实现OTP绕过

本文详细介绍了通过HTTP头部参数污染实现OTP验证码泄露的技术细节。作者通过Shodan搜索发现目标应用,在注册过程中发现API通过GET请求的头部发送用户数据,利用重复Mobileusername头部成功让服务器向多个邮箱发送相同OTP验证码,实现了账户劫持。

OTPs For Everyone: 最简单的OTP泄露漏洞

Akwaaba! abusua。我遇到了一个"参数"污染案例,但不是通常的那种。请继续阅读并跟随我的思路。另外,如果你还没有看过,请查看我之前的Android应用内容提供程序漏洞利用文章。是的,你也可以通过这个SSO漏洞利用获得轻松的150美元赏金。

来源:Zaraki

前段时间,我在Hackerone上遇到了一个程序,它有一个通配符()子域名,比如.target.com。

侦查过程

如何识别目标?

为了识别潜在目标,我使用shodan.io搜索共享相同SSL证书通用名称(CN)的域名和IP地址,特别是那些匹配通配符模式如*.target.com的。这种方法有助于识别可能属于同一组织或基础设施的服务。

例如,如果你正在调查像medium.com这样的域名,你可以检查其SSL证书以找到CN值。一旦识别,你可以使用Shodan枚举具有包含相同CN的证书的其他主机,可能揭示相关资产或配置错误的端点。

检查CN值的步骤:

  1. 点击地址栏中的挂锁图标
  2. 点击"连接安全",将打开另一个弹出框
  3. 在新的弹出框中,点击证书图标,将出现最终弹出框 查看通用名称(CN),它应该告诉你证书是为哪个域名颁发的。

在漏洞搜寻过程中,有时我会遇到一个奇怪的子域名,不确定它是否属于目标组织。一个快速的检查方法是将其SSL证书的CN与其主网站(或任何其他已知应用程序)的CN进行比较。如果匹配,则可能是其基础设施的一部分。

使用的Shodan搜索查询:

1
Ssl.cert.subject.CN:"*.target.com" http.title:"Login"

从结果中,我识别出一个允许用户账户注册的应用程序。我们称这个应用程序为insecure.target.com。

识别漏洞

在注册过程中,有一个最终阶段需要验证电子邮件。一个6位数的代码会发送到你的邮箱,你可以进行验证。

回到Burp Suite查看API请求的样子,我注意到一些奇怪的地方:

  • 所有API请求都是GET方法
  • 它们没有表单数据参数

起初我有点困惑,应用程序到底是如何将我的注册数据发送到服务器的?于是,我仔细查看了每个API请求。然后我发现了:一个对/api/auth/mobileVerifyRequestOTP的GET请求。令人惊讶的是,没有特殊的查询参数。相反,用户的注册数据完全通过HTTP头部发送。

其他表单数据也通过不同的API端点通过GET请求发送。

通过GET请求头部发送的表单数据

这很不寻常,但我想这对他们来说是有效的。

在阅读我如何利用这个漏洞之前,请查看我如何操作API路径来访问所有用户PII。

利用头部污染

使用与参数污染相同的思路,我尝试用两个不同的电子邮件地址复制Mobileusername头部,看看服务器会如何处理。令人惊讶的是,它接受了两个并响应了一个verificationID。(注意:这不是OTP本身,只是一个用于跟踪注册会话的ID。)

复制电子邮件头部

其中一个电子邮件是我的wearehackerone.com(重定向到Gmail),另一个来自Temp-Mail.org。首先检查我的Gmail,我看到OTP代码已经到达,OTP代码是399336。

OTP到达第一个邮箱

我快速检查了Temp-Mail的收件箱,相同的399336也到达了那里。这确认了漏洞的存在。服务器接受了两个电子邮件并向每个发送了相同的代码。

第二个不同邮箱中的相同OTP

这是因为服务器以奇怪的方式处理重复的Mobileusername头部。它不是拒绝或覆盖重复项,而是将两个值视为数组的一部分,并向列出的每个电子邮件地址发送相同的OTP代码。通过检查我Gmail账户收到的电子邮件中的To头部,确认了这种行为,它清楚地显示了两个用逗号(,)分隔的电子邮件地址。甚至Gmail也显示另一个收件人收到了相同的OTP消息。

在第一个电子邮件中,我们看到第二个电子邮件显示在抄送中

在这里无耻地插入我的另一篇文章:另一种绕过Google CAPTCHA以击败速率限制的独特方法。Medaase。

什么可能导致这个漏洞?

我没有看过他们的后端代码;这只是我的推测。但为了演示这种漏洞可能如何发生,请查看下面的Flask代码片段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from flask import Flask, request
import random

@app.route('/api/auth/mobileVerifyRequestOTP?isEmail=true', methods=['GET'])
def send_otp():
    # 从GET请求获取'Mobileusername'头的所有值
    usernames = request.headers.getlist('Mobileusername')
    # 生成单个OTP代码
    otp = str(random.randint(100000, 999999))
    verification_id = str(random.randint(100000, 999999))
    '''
     这里应该有一个重复头部检查但没有实现
    '''
    # 向所有电子邮件发送相同的OTP
    send_otp_email(usernames, otp)
    return {'verificationID': verification_id}

这种发现的影晌是什么?

首先,可以使用他人的电子邮件创建和验证账户。我发现服务器将第一个Mobileusername头部与正在创建的账户绑定。因此,恶意用户可以将受害者的电子邮件设置为第一个头部,将自己的电子邮件设置为第二个。他们会收到OTP,完成验证,账户将在受害者的电子邮件下创建。

使用其他用户的电子邮件注册账户

缓解措施

可以通过实施简单的重复头部检查来适当缓解此问题。如果检测到Mobileusername头部有多个值,服务器应拒绝请求或仅安全地处理第一个值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from flask import Flask, request
import random

@app.route('/api/auth/mobileVerifyRequestOTP?isEmail=true', methods=['GET'])
def send_otp():
    # 从GET请求获取'Mobileusername'头的所有值
    usernames = request.headers.getlist('Mobileusername')
    # 生成单个OTP代码
    otp = str(random.randint(100000, 999999))
    verification_id = str(random.randint(100000, 999999))
    
    # 验证只有一个头部存在
    if len(usernames) != 1:
        abort(400, description="无效请求:只需要一个Mobileusername头部。")
    
    # 向所有电子邮件发送相同的OTP
    send_otp_email(usernames, otp)
    return {'verificationID': verification_id}

嘿,后面的你,是的你…再见 Marsh先生

感谢阅读本文,如果你有任何问题,可以在Twitter @tinopreter上私信我。在LinkedIn上与我联系:Clement Osei-Somuah。

Otp绕过 参数污染 Hackerone 漏洞赏金报告 漏洞赏金

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计