Overview of Attacks Against Authentication
身份验证攻击总共可以针对三个域进行。这三个领域分为以下几类: HAS域(拥有的东西) IS域(所知信息) KNOWS域(所知的事情)
Attacking the HAS Domain
谈到在涵盖Multi-Factor Authentication时描述的三个域,has域看起来很简单,因为我们要么拥有硬件令牌,要么没有。然而,事情比表面上看起来更复杂: 徽章可以在不被接管的情况下被克隆 用于生成一次性密码的加密算法可能会被破坏 任何物理设备都可能被盗 远程天线可以轻松实现50厘米的工作距离,并复制经典的NFC徽章。你可能认为攻击者必须非常靠近受害者才能成功地执行这样的攻击。想想在使用公共交通工具或在商店排队等候时,我们坐得有多近,你可能会改变主意。每天都有多个人可以进行这样的克隆攻击。 想象一下,你正在办公室附近的酒吧吃快餐。你甚至没有注意到有袭击者从你的座位旁走过,因为你正忙于一项紧急的工作任务。他们刚刚克隆了你口袋里的徽章!!!几分钟后,他们将您的徽章信息转移到一个干净的代币中,并在吃午饭时使用它进入您公司的大楼。 很明显,克隆企业徽章并没有那么困难,而且后果可能很严重。
Attacking the IS Domain
你可能会认为is域是最难攻击的。如果一个人依靠“某物”来证明自己的身份,而这个“某物”被破坏了,他们就会失去证明自己身份的独特方式,因为没有人可以改变他们的身份。视网膜扫描、指纹读取器、面部识别都被证明是可以破解的。所有这些都可以通过第三方泄露、高清图片、吝啬鬼,甚至是偷对玻璃的邪恶女仆来破解。 销售基于is域的安全措施的公司表示,它们非常安全。2019年8月,一家制造通过移动或网络应用程序管理的生物识别智能锁的公司被攻破。该公司使用指纹或面部识别来识别授权用户。该漏洞暴露了所有指纹和面部模式,包括用户名和密码、授权和注册用户的地址。虽然用户可以轻松更改密码并减轻问题,但任何能够复制指纹或面部图案的人仍然可以解锁和管理这些智能锁。
Attacking the KNOWS Domain
knows领域是我们将在本模块中深入研究的领域。这是最简单的理解,但我们应该深入了解每一个方面,因为它也是最广泛的。这个域指的是用户知道的东西,比如用户名或密码。在本模块中,我们将仅针对FBA进行工作。请记住,同样的方法也可以适用于HTTP身份验证实现。
Default Credentials
Weak Bruteforce Protections
在深入研究攻击之前,我们必须了解在测试过程中可能遇到的保护措施。如今,有许多不同的安全机制旨在防止自动攻击。最常见的有以下几种。
CAPTCHA
Rate Limits
此外,web开发人员经常创建自己的安全机制,使测试过程对我们来说更“有趣”,因为这些自定义安全机制可能包含我们可以利用的漏洞。让我们首先熟悉针对自动攻击的常见安全机制,以了解它们的功能,并为针对它们的攻击做好准备。
CAPTCHA
一种广泛使用的安全措施,以“告诉计算机和人类分开的全自动公共图灵测试”这句话命名,可以有很多不同的形式。例如,它可能需要键入图像上的单词,听到一个简短的音频样本并将你听到的内容输入到表格中,将图像与给定的模式匹配,或者执行基本的数学运算。
尽管CAPTCHA在过去已经被成功绕过,但它对自动攻击仍然非常有效。应用程序至少应该要求用户在几次尝试失败后解决CAPTCHA问题。一些开发人员经常完全跳过这种保护,而另一些开发人员则更喜欢在登录失败后提供CAPTCHA,以保持良好的用户体验。 开发人员也可以使用CAPTCHA的自定义或弱实现,例如,图像的名称由图像中包含的字符组成。保护不力往往比没有保护更糟糕,因为它提供了一种虚假的安全感。下图显示了一个弱实现,PHP代码将图像的内容放入id字段。这种类型的弱实现是罕见的,但并非不可能。
作为攻击者,我们只需读取页面的源代码即可找到CAPTCHA代码的值并绕过保护。我们应该始终阅读源代码。 作为开发人员,我们不应该开发自己的CAPTCHA,而应该依赖一个经过良好测试的CAPTCHA,并在很少失败登录后需要它。
Rate Limiting
Insufficient Protections
当攻击者可以篡改为提高安全性而考虑的数据时,他们可以绕过所有或部分保护。例如,更改用户代理标头很容易。一些web应用程序或web应用程序防火墙利用X-Forwarded-For等标头来猜测实际的源IP地址。这样做是因为许多互联网提供商、移动运营商或大公司通常将用户“隐藏”在NAT后面。在没有X-Forwarded-For等标头帮助的情况下阻止IP地址可能会导致阻止特定NAT后面的所有用户。
Brute Forcing Usernames
用户名枚举经常被忽视,可能是因为人们认为用户名不是私人信息。当你给另一个用户写消息时,我们通常认为我们知道他们的用户名、电子邮件地址等。同一个用户名经常被重复用于访问其他服务,如FTP、RDP和SSH等。由于许多web应用程序允许我们识别用户名,我们应该利用这一功能,并将其用于以后的攻击。
广泛的web应用程序都存在此漏洞。 用户名通常远没有密码复杂。当它们不是电子邮件地址时,很少包含特殊字符。拥有一个常见用户列表会给攻击者带来一些优势。除了获得良好的用户体验(UX)外,遇到随机或不易预测的用户名也很少见。用户将比计算机生成的(伪)随机用户名更容易记住他们的电子邮件地址或昵称。 拥有有效用户名列表,攻击者可以缩小暴力攻击的范围,或对支持员工或用户本身进行有针对性的攻击(利用OSINT)。此外,一个通用密码可以很容易地被喷到有效的帐户上,通常会导致成功的帐户泄露。 应该注意的是,用户名也可以通过抓取网络应用程序或使用公共信息来获取,例如社交网络上的公司简介。 防止用户名枚举攻击可能会影响用户体验。网络应用程序显示用户名是否存在可能有助于合法用户识别他们未能正确键入用户名,但这同样适用于试图确定有效用户名的攻击者。即使是像WordPress这样知名且成熟的web框架,也会受到用户枚举的影响,因为开发团队选择通过降低框架的安全级别来获得更流畅的用户体验。
User Unknown Attack
Tanin@htb[/htb]$ wfuzz -c -z file,/opt/useful/SecLists/Usernames/top-usernames-shortlist.txt -d "Username=FUZZ&Password=dummypass" --hs "Unknown username" http://brokenauthentication.hackthebox.eu/user_unknown.php
Username Existence Inference
有时,web应用程序可能不会明确声明它不知道特定的用户名,但允许攻击者推断这条信息。如果用户名有效且已知,则某些web应用程序会预先填充用户名输入值,但如果用户名未知,则保留输入值为空或使用默认值。这在移动版本的网站上很常见,我们之前看到的易受攻击的WordPress登录页面也是如此。在开发过程中,始终尝试为失败登录和授权登录提供相同的体验:即使是微小的差异也足以推断出一条信息。
虽然不常见,但当用户名有效或无效时,也可能设置不同的cookie。例如,要使用客户端控件检查密码尝试,web应用程序可以设置一个名为“failed_login”的cookie,然后仅在用户名有效时检查该cookie。仔细检查响应,注意HTTP头和HTML源代码中的差异。
Timing Attack
某些身份验证功能在设计上可能存在缺陷。一个示例是身份验证函数,其中按顺序检查用户名和密码。让我们分析一下下面的程序。
<?php
// connect to database
$db = mysqli_connect("localhost", "dbuser", "dbpass", "dbname");
// retrieve row data for user
$result = $db->query('SELECT * FROM users WHERE username="'.safesql($_POST['user']).'" AND active=1');
// $db->query() replies True if there are at least a row (so a user), and False if there are no rows (so no users)
if ($result) {
// retrieve a row. don't use this code if multiple rows are expected
$row = mysqli_fetch_row($result);
// hash password using custom algorithm
$cpass = hash_password($_POST['password']);
// check if received password matches with one stored in the database
if ($cpass === $row['cpassword']) {
echo "Welcome $row['username']";
} else {
echo "Invalid credentials.";
}
} else {
echo "Invalid credentials.";
}
?>
代码片段首先连接到数据库,然后执行查询以检索用户名与请求的用户名匹配的整行。如果没有结果,函数将以一条通用消息结束。当$result为true(用户存在并且处于活动状态)时,将对提供的密码进行散列和比较。如果使用的哈希算法足够强大,那么两个分支之间的时序差异将是显而易见的。通过使用通用hash_password()函数计算$cpass,==响应时间将高于其他情况==。这个小错误可以通过在同一步骤中检查用户和密码来避免,有效用户名和无效用户名的时间相似。 下载脚本 timing.py来见证这些类型的时间差异,并针对使用bcrypt的示例web应用程序(timing.php)运行它。
Timing Attack - Timing.py
Tanin@htb[/htb]$ python3 timing.py /opt/useful/SecLists/Usernames/top-usernames-shortlist.txt
考虑到可能存在网络故障,很容易将“admin”识别为有效用户,因为它比其他测试用户花费了更多的时间。如果使用的算法很快,则时差会更小,攻击者可能会因为网络延迟或CPU负载而产生误报。然而,通过重复大量创建模型的请求,攻击仍然是可能的。虽然我们可以假设现代应用程序使用稳健的算法对密码进行散列,以使潜在的离线暴力攻击尽可能慢,但即使使用MD5或SHA1等快速算法,也可以推断信息。 当领英的用户群在2012年被泄露时,InfoSec的专业人士就SHA1被用作用户密码的哈希算法展开了一场辩论。虽然SHA1在那些日子里没有崩溃,但它被认为是一个不安全的哈希解决方案。Infosec的专业人士开始争论是否选择使用SHA1,而不是更强大的哈希算法,如scrypt、bcrypt或PBKDF(或argon2)。 虽然使用更健壮的算法总是比使用较弱的算法更可取,但架构工程师也应该记住计算成本。这个非常基本的Python脚本有助于阐明这个问题:
import scrypt
import bcrypt
import datetime
import hashlib
rounds = 100
salt = bcrypt.gensalt()
t0 = datetime.datetime.now()
for x in range(rounds):
scrypt.hash(str(x).encode(), salt)
t1 = datetime.datetime.now()
for x in range(rounds):
hashlib.sha1(str(x).encode())
t2 = datetime.datetime.now()
for x in range(rounds):
bcrypt.hashpw(str(x).encode(), salt)
t3 = datetime.datetime.now()
print("sha1: {}\nscrypt: {}\nbcrypt: {}".format(t2-t1,t1-t0,t3-t2))
使用更稳健的算法,这会增加CPU时间和RAM使用量。core第八代i5上运行上面的脚本会得到以下结果。
Tanin@htb[/htb]$ python3 hashtime.py
sha1: 0:00:00.000082
scrypt: 0:00:03.907575
bcrypt: 0:00:22.660548
让我们通过一个粗略的例子来添加一些上下文: 领英每天有约2亿用户,这意味着每秒约有24次登录(我们不排除拥有“记住我”代币的用户)。 如果他们使用像bcrypt这样的强大算法,在我们的测试机器上每轮使用0.23秒,他们将需要六台服务器才能让人们登录。对于一家运行数千台服务器的公司来说,这听起来不是什么大问题,但这需要对架构进行彻底改革。
Enumerate through Password Reset
重置表单的保护通常不如登录表单好。因此,他们经常泄露有关有效或无效用户名的信息。正如我们已经讨论过的,当找到有效的用户名时,应用程序会回复“您应该很快收到一条消息”,而“用户名未知,请检查您的数据”则会泄露注册用户的存在。 这种攻击很吵,因为一些有效用户可能会收到要求重置密码的电子邮件。也就是说,这些电子邮件经常得不到最终用户的适当关注。
Enumerate through Registration Form
默认情况下,当所选用户名已经存在时,提示用户选择用户名的注册表单通常会回复一条明确的消息,或者在这种情况下提供其他“告诉”。通过滥用这种行为,攻击者可以注册常见的用户名,如admin、administrator、tech,以枚举有效的用户名。在检查所选用户名(如CAPTCHA)是否存在之前,安全注册表应该实现一些保护。 在测试时,许多人不知道或没有准备好电子邮件地址的一个有趣功能是子寻址。这个在RFC5233中定义的扩展表示,邮件传输代理(MTA)应该忽略电子邮件地址左侧的任何+标记,并将其用作筛选过滤器的标记。这意味着写信给学生这样的电子邮件地址+htb@hackthebox.eu将电子邮件发送到student@hackthebox.eu并且,如果支持并正确配置了过滤器,则会将其放置在文件夹htb中。很少有网络应用程序尊重这个RFC,这导致了通过使用一个标签和一个实际的电子邮件地址来注册几乎无限用户的可能性。
Predictable Usernames
在用户体验需求较少的网络应用程序中,例如家庭银行,或者需要批量创建多个用户时,我们可能会看到按顺序创建的用户名。 虽然不常见,但您可能会遇到像user1000、user1001这样的帐户。“管理”用户也可能有一个可预测的命名约定,如support.It、support.fr或类似的。攻击者可以推断用于创建用户的算法(增量四位数、国家代码等),并从一些已知的用户帐户开始猜测现有用户帐户。
Brute Forcing Passwords
Password Issues
从历史上看,密码有三个重要问题。第一个问题在于名称本身。很多时候,用户认为密码可以只是一个单词,而不是一个短语。第二个问题是,用户大多设置了易于记住的密码。这样的密码通常很弱,或者遵循可预测的模式。即使用户选择了一个更复杂的密码,它通常也会写在便利贴上或保存在明文中。在提示字段中写入密码的情况也并不少见。当访问企业网络的频繁密码轮换要求开始发挥作用时,第二个密码问题会变得更糟。这一要求通常会导致诸如Spring2020、Autumn2020或CompanynameTown1、CompanynameTown2等密码。 最近,美国国家标准与技术研究院更新了其关于密码策略测试、密码年龄要求和密码组成规则的指导方针。
最后,众所周知,许多用户在多个服务上重复使用相同的密码。其中一个密码泄露或泄露会让攻击者访问广泛的网站或应用程序。这种攻击被称为凭据填充,与Hashcat破解密码模块中教授的单词列表生成密切相关。存储和使用复杂密码的可行解决方案是密码管理器。有时您可能会遇到弱密码要求。这种情况通常发生在有额外安全措施的情况下。ATM就是一个很好的例子。密码,或者更好的是PIN,只是一个4或5位数字的序列。相当弱,但缺乏复杂性,但总尝试次数有限(在失去对设备的物理访问之前,不超过3个PIN)。
现在让我们假设这个web应用程序需要一个介于8到12个字符之间的字符串,其中至少有一个大写和小写字符。我们现在使用一个巨大的单词列表,只提取符合此策略的密码。Unix grep不是速度最快的工具,但它允许我们使用POSIX正则表达式快速完成这项工作。下面的命令将针对rockyou-50.txt,这是SecLists中常见的rockyou密码泄漏的子集。此命令使用扩展正则表达式(-E)查找至少有一个大写字符(“[:upper:]]”)的行,然后仅查找同时有一个小写字符(“[[:lower:]]’”)并且长度为8和12个字符(“^.{8,12}$”)的线。
Tanin@htb[/htb]$ grep '[[:upper:]]' rockyou.txt | grep '[[:lower:]]' | grep -E '^.{8,12}$'
Predictable Reset Token
重置令牌(以代码或临时密码的形式)是主要由应用程序在请求重置密码时生成的秘密数据。在实际更改凭据之前,用户必须提供它来证明自己的身份。有时,应用程序要求您选择一个或多个安全问题,并在注册时提供答案。如果您忘记了密码,您可以通过再次回答这些问题来重置密码。我们也可以将这些答案视为象征。 此功能允许我们在不知道密码的情况下重置用户的实际密码。
Reset Token by Email
如果应用程序允许用户使用URL或通过电子邮件发送的临时密码重置密码,那么它应该包含强大的令牌生成功能。框架通常具有用于此目的的专用功能。然而,开发人员通常会实现自己的功能,这些功能可能会引入逻辑缺陷和弱加密,或者通过模糊实现安全性。
Weak Token Generation
一些应用程序使用已知或可预测的值(如本地时间或请求操作的用户名)创建令牌,然后对值进行散列或编码。这是一种糟糕的安全做法,因为令牌不需要包含来自要验证的实际用户的任何信息,并且应该是一个纯随机值。在可逆编码的情况下,对令牌进行解码以了解它是如何构建的并伪造一个有效的令牌就足够了。
作为渗透测试人员,我们应该意识到这些类型的糟糕实现。当为给定用户请求重置令牌时,我们应该尝试使用已知的组合(如时间+用户名或时间+电子邮件)来强制执行任何弱哈希。以这段PHP代码为例。它在逻辑上等同于Apache OpenMeeting上报告的CVE-2016-0783漏洞:
<?php
function generate_reset_token($username) {
$time = intval(microtime(true) * 1000);
$token = md5($username . $time);
return $token;
}
很容易发现漏洞。知道有效用户名的攻击者可以通过读取Date标头(它几乎总是出现在HTTP响应中)来获取服务器时间。然后,攻击者可以在几秒钟内强行执行$time值,并获得有效的重置令牌。在这个例子中,我们可以看到一个常见的请求泄露日期和时间。
我们可以使用wfuzz。具体来说,我们可以为区分大小写的字符串Valid(–ss“Valid”)使用字符串匹配。当然,如果我们不知道提交有效令牌时web应用程序是如何回复的,我们可以使用“反向匹配”,通过使用–hs “Invalid.”查找不包含无效令牌的任何响应。
Tanin@htb[/htb]$ wfuzz -z range,00000-99999 --ss "Valid" "https://brokenauthentication.hackthebox.eu/token.php?user=admin&token=FUZZ"
<待续…>