LDAP authentication in Active Directory environments

渗透技巧 6个月前 admin
109 0 0

According to Microsoft, Active Directory supports 3 authentication methods on LDAP connection:
根据Microsoft的说法,Active Directory在LDAP连接上支持3种身份验证方法:

  • Simple: Simple username/password as defined in (one of) the LDAP RFC.
    简单:LDAP RFC(其中一个)中定义的简单用户名/密码。
  • Sicily: This legacy protocol is another protocol to negotiate underlying authentication method. Active Directory only supports NTLM as an authentication protocol with Sicily.
    西西里岛:此旧协议是协商基础身份验证方法的另一种协议。Active Directory 仅支持 NTLM 作为 Sicily 的身份验证协议。
  • Simple Authentication and Security Layer (SASL)SASL is a framework for authentication, it allows client and server to negotiate an authentication method among those supported. SASL authentication adds 4 authentication subtypes:
    简单身份验证和安全层 (SASL):SASL 是一个身份验证框架,它允许客户端和服务器在支持的身份验证方法之间协商身份验证方法。SASL 身份验证增加了 4 个身份验证子类型:

    • GSS-SPNEGO: Simple and Protected GSSAPI Negotiation Mechanism, yet another protocol to negotiate authentication. Active Directory provides NTLM or Kerberos as underlaying methods.
      GSS-SPNEGO:简单且受保护的 GSSAPI 协商机制,另一种协商身份验证的协议。Active Directory 提供 NTLM 或 Kerberos 作为底层方法。
    • GSSAPI: Kerberos (well, actually, GSSAPI is a lot more than “Kerberos”, but in Active Directory environment and to simplify the topic, consider they are the same)
      GSSAPI:Kerberos(实际上,GSSAPI比“Kerberos”要多得多,但是在Active Directory环境中,为了简化主题,请考虑它们是相同的)
    • EXTERNAL: in the SASL framework, EXTERNAL mode is used by the client to tell the server to use information provided outside of what is strictly considered LDAP communications, for example, the GID/UID of the client process if the communication is done through a Unix socket or the TLS client certificate if the connection is done via TLS. Active Directory seems to exclusively use this mode to support TLS authentication via Schannel.
      EXTERNAL:在 SASL 框架中,客户端使用 EXTERNAL 模式来告诉服务器使用在严格认为是 LDAP 通信之外提供的信息,例如,如果通信是通过 Unix 套接字完成的,则客户端进程的 GID/UID,如果连接是通过 TLS 完成的,则为 TLS 客户端证书。Active Directory 似乎专门使用此模式来支持通过 Schannel 进行 TLS 身份验证。
    • DIGEST-MD5: This legacy protocol is based on the HTTP Digest Authentication; it is thus a challenge/response authentication protocol. It is required by the LDAPv3 RFC but is now deprecated.
      DIGEST-MD5:此旧协议基于 HTTP 摘要式身份验证;因此,它是一种质询/响应身份验证协议。它是 LDAPv3 RFC 所必需的,但现在已弃用。

In total, LDAP on Active Directory supports 6 “kinds” of authentication.
Active Directory 上的 LDAP 总共支持 6 种“类型”身份验证。

Side note: SASL mechanisms supported by the target domain controller can be anonymously requested:
旁注:可以匿名请求目标域控制器支持的 SASL 机制:

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local', get_info=ldap3.ALL)
>>> connection = ldap3.Connection(server)
>>> connection.bind()
True
>>> connection.server.info.supported_sasl_mechanisms
['GSSAPI', 'GSS-SPNEGO', 'EXTERNAL', 'DIGEST-MD5']

Protections 保护

The most (in)famous attacks against LDAP authentication are relay attacks, and more precisely NTLM relay. Keep in mind that relaying Kerberos authentication is theoretically possible but not as easy to do in practice and beyond the scope of this blog post. Moreover, authenticating with certificates through Schannel is not impacted by relay attacks because of how it intrinsically works.
针对 LDAP 身份验证的最著名攻击是中继攻击,更准确地说是 NTLM 中继。请记住,中继 Kerberos 身份验证在理论上是可行的,但在实践中并不容易做到,超出了本博客文章的范围。此外,通过 Schannel 使用证书进行身份验证不会受到中继攻击的影响,因为它本质上是如何工作的。

Microsoft provides two countermeasures to protect LDAP authentications against relay attacks, as described in KB4520412. Those two protections are LDAP signing and LDAP channel binding.
Microsoft提供了两种对策来保护 LDAP 身份验证免受中继攻击,如KB4520412中所述。这两种保护措施是 LDAP 签名和 LDAP 通道绑定。

LDAP Signing LDAP 签名

The idea here is to encrypt or sign the LDAP payload with a shared secret between the client and the server. NTLM, Kerberos and DIGEST-MD5 based authentications implement this protection. Unlike what the name suggests, “LDAP signing” protection entails the signing of the LDAP payload, as well as its (optional) encryption, depending on what is negotiated between the client and the server. When signature or encryption is negotiated, it is no longer possible for an attacker to send arbitrary command after the authentication relay.
这里的想法是使用客户端和服务器之间的共享密钥对 LDAP 有效负载进行加密或签名。基于 NTLM、Kerberos 和 DIGEST-MD5 的身份验证实现此保护。与顾名思义不同,“LDAP 签名”保护需要对 LDAP 有效负载及其(可选)加密进行签名,具体取决于客户端和服务器之间的协商内容。协商签名或加密后,攻击者不再可能在身份验证中继后发送任意命令。

To enable LDAP signing, Domain controller: LDAP server signing requirements must be set to Require signing within the domain controller policy.
若要启用 LDAP 签名, Domain controller: LDAP server signing requirements 必须在域控制器策略 Require signing 中设置为。

LDAP authentication in Active Directory environments

Channel Binding 通道绑定

This protection is designed to prevent relaying authentications to LDAPS. The idea is to bind the outer secure connection (TLS in our case) to application data over an inner client-authenticated channel (NTLM here). More precisely, Active Directory implements ‘tls-server-end-point’ channel binding Type, the general concept is covered here. This kind of channel binding seems to be the only one supported by Active Directory during NTLM and Kerberos authentications with LDAP. Channel binding during NTLM authentication is performed by adding a new AV_PAIR (attributes/value pair structure defined in MS-NLMP 2.2.2.1) within the AUTHENTICATE_MESSAGE (MS-NLMP 2.2.1.3). This new AV_PAIR has AvId which is 0x000A (MsvAvChannelBindings).
此保护旨在防止将身份验证中继到 LDAPS。这个想法是通过内部客户端身份验证通道(此处为 NTLM)将外部安全连接(在本例中为 TLS)绑定到应用程序数据。更准确地说,Active Directory 实现了“tls-server-end-point”通道绑定类型,此处介绍了一般概念。在使用 LDAP 进行 NTLM 和 Kerberos 身份验证期间,Active Directory 似乎唯一支持这种通道绑定。NTLM 身份验证期间的通道绑定是通过在 AUTHENTICATE_MESSAGE (MS-NLMP 2.2.1.3) 中添加新的 (MS-NLMP 2.2.2.1) 中定义的属性 AV_PAIR /值对结构来执行的。这个新 AV_PAIR 有 AvId 0x000A 是 ( MsvAvChannelBindings )。

LDAP authentication in Active Directory environments

The Value field contains a MD5 hash of a gss_channel_bindings_struct struct which basically is a magic constant concatenated with the SHA256 hash of the server certificate. Here is an example of the simplified struct represented in pseudo-code:
该字段包含结构的 MD5 哈希值,该 Value 结构 gss_channel_bindings_struct 体基本上是与服务器证书的 SHA256 哈希值连接的魔术常量。下面是伪代码中表示的简化结构的示例:

gss_channel_bindings_struct {
    initiator_addrtype: '\x00\x00\x00\x00';
    initiator_address:  '\x00\x00\x00\x00';
    acceptor_addrtype:  '\x00\x00\x00\x00';
    acceptor_address:   '\x00\x00\x00\x00';
    application_data:   '\x55\x00\x00\x00' + 'tls-server-end-point:' + 'ae2670228880c00ad0448c156575212d1bbdaea7dde68f88a41ff5e084940f92';
                        # [len(magic+hash)]          # [magic]                              # [hash]
}

Active Directory services use Schannel as Security Service Provider (SSP) to handle TLS. According to the documentation, this SSP only uses the application_data field to perform channel binding.
Active Directory 服务使用 Schannel 作为安全服务提供商 (SSP) 来处理 TLS。根据文档,此 SSP 仅使用该 application_data 字段来执行通道绑定。

Regarding Kerberos, instead of being added within the AUTHENTICATE_MESSAGE, the hash is put into the cksum field and then the client adds it to the authenticator.
对于 Kerberos,哈希不是添加到 中,而是放入 cksum 字段中,然后客户端将其添加到 authenticator AUTHENTICATE_MESSAGE 中。

To require channel binding on LDAP, Domain controller: LDAP server channel binding token requirements must be set to Always within the domain controller policy. Keep in mind that GPO support for LDAP channel binding has been added on March 2020 for all Domain Controllers running at least Windows Server 2008, so be sure to have an up-to-date domain controller (though, if your domain controllers have not been upgraded since March 2020, maybe GPO support for channel binding is not your priority). Microsoft provides a guide to secure LDAP/S services: ADV190023.
若要要求在 LDAP 上进行通道绑定, Domain controller: LDAP server channel binding token requirements 必须在域控制器策略 Always 中设置为。请记住,2020 年 3 月已为至少运行 Windows Server 2008 的所有域控制器添加了对 LDAP 通道绑定的 GPO 支持,因此请确保具有最新的域控制器(但是,如果域控制器自 2020 年 3 月以来未升级,则通道绑定的 GPO 支持可能不是你的首要任务)。Microsoft提供了保护 LDAP/S 服务的指南:ADV190023。

LDAP authentication in Active Directory environments

ldap3 LDAP3型

The ldap3 library is a pure python implementation of the LDAP 3 RFC and is widely used in offensive tools. It natively supports 5 (sub) authentication methods when used against domain controllers:
ldap3 库是 LDAP 3 RFC 的纯 python 实现,广泛用于攻击性工具。当用于域控制器时,它本机支持 5 种 (子) 身份验证方法:

  • Simple 简单
  • Sicily 西西里岛
  • SASL (GSSAPI) SASL (GSSAPI)
  • SASL (EXTERNAL) SASL(外部)
  • SASL (DIGEST-MD5) SASL (摘要-MD5)

But this number decreases if the target domain implements protection.
但是,如果目标域实施保护,则此数字会减少。

NTLM authentication NTLM 身份验证

LDAP signing LDAP 签名

If Domain controller: LDAP server signing requirements is set to Require signing and a client uses Sicily on LDAP port 389 during the authentication, the following response will be returned by the server:
如果 Domain controller: LDAP server signing requirements 设置为 Require signing 并且客户端在身份验证期间在 LDAP 端口 389 上使用 Sicily,则服务器将返回以下响应:

>>> import ldap3
>>> server = ldap3.Server('ldap://192.168.56.10')
>>> connection = ldap3.Connection(server, authentication=ldap3.NTLM, user='sevenkingdoms.local\\cersei.lannister', password='il0vejaime')
>>> connection.bind()
False
>>> connection.result
{'result': 8, 'description': 'strongerAuthRequired', 'dn': '', 'message': '00002028: LdapErr: DSID-0C090254, comment: The server requires binds to turn on integrity checking if SSL\\TLS are not already active on the connection, data 0, v4f7c\x00', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

The error message is straightforward: the authentication scheme does not provide integrity for communication; thus, the authentication is rejected. Fortunately, user CravateRouge submitted a pull request to the ldap3 project that implements message confidentiality as described within MS-NLMP 3.4.3. With this PR, the DC accepts the connection:
错误消息很简单:身份验证方案不为通信提供完整性;因此,身份验证将被拒绝。幸运的是,用户 CravateRouge 向 ldap3 项目提交了一个拉取请求,该项目实现了 MS-NLMP 3.4.3 中描述的消息机密性。使用此 PR,DC 接受连接:

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication=ldap3.NTLM, user='sevenkingdoms.local\\cersei.lannister', password='il0vejaime', session_security=ldap3.ENCRYPT)
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

Here is an example of an LDAP PDU containing a whoami response without confidentiality:
下面是一个 LDAP PDU 示例,其中包含没有机密性的 whoami 响应:

LDAP authentication in Active Directory environments

The same request with confidentiality enabled:
启用机密性的相同请求:

LDAP authentication in Active Directory environments

The pull request implements encryption with Extended Session Security, so the encrypted LDAP PDU is built like this:
拉取请求使用扩展会话安全性实现加密,因此加密的 LDAP PDU 的构建方式如下:

[size_of_the_PDU][signature][encrypted_payload]
    4 bytes       16 bytes        X bytes
                 |         |
                 |         |
                 |         |
  _______________|         |__________________
 |                                            |
 |                                            |
 [signature_version][checksum][sequence_number]
      4 bytes        8 bytes     4 bytes

Where: 哪里:

  • signature_version should always be 1.
    signature_version 应始终是 1 。
  • checksum is the 8 first bytes of the resulting MD5: HMAC_MD5(SIGNING_KEY, CONCAT(SEQUENCE_NUMBER,CLEAR_TEXT_MESSAGE))SIGNING_KEY is derived from the user’s password.
    checksum 是生成的 MD5 的前 8 个字节: HMAC_MD5(SIGNING_KEY, CONCAT(SEQUENCE_NUMBER,CLEAR_TEXT_MESSAGE)) 。 SIGNING_KEY 派生自用户的密码。
  • sequence_number: number incrementing every time the client sends a message.
    sequence_number :每次客户端发送消息时数字递增。
  • encrypted_payload is the cleartext message encrypted with RC4. The key used by this algorithm is also derived from the user’s password.
    encrypted_payload 是使用 RC4 加密的明文消息。此算法使用的密钥也派生自用户的密码。

The previous Wireshark screenshot can be interpreted as:
前面的 Wireshark 屏幕截图可以解释为:

[00000061][01000000][206b9cc72ddf70e0][03000000][c13b.........b67d]
[PDU size][version ][    checksum    ][seq num ][encrypted payload]

Moreover, the connection is now protected against network sniffing, thus it is possible to create users or computers within the directory as it would be possible with LDAPS (not possible by default on unencrypted connections):
此外,该连接现在受到网络嗅探保护,因此可以在目录中创建用户或计算机,就像使用 LDAPS 一样(默认情况下在未加密的连接上是不可能的):

>>> import ldap3, string, random
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication=ldap3.NTLM, user='sevenkingdoms.local\\cersei.lannister', password='il0vejaime', session_security=ldap3.ENCRYPT)
>>> connection.bind()
True
>>> new_user_dn = 'CN=%s,%s' % ('newuser', 'OU=Crownlands,DC=sevenkingdoms,DC=local') # sorry for the GoT lore...
>>> new_password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))
>>> ucd = {
... 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,DC=sevenkingdoms,DC=local',
... 'distinguishedName': 'CN=%s,%s' % ('newuser', 'OU=Crownlands,DC=sevenkingdoms,DC=local'),
... 'cn': 'newuser',
... 'sn': 'newuser',
... 'givenName': 'newuser',
... 'displayName': 'newuser',
... 'name': 'newuser',
... 'userAccountControl': 512,
... 'accountExpires': '0',
... 'sAMAccountName': 'newuser',
... 'unicodePwd': '"{}"'.format(new_password).encode('utf-16-le')
... }
>>> 
>>> connection.add(new_user_dn, ['top', 'person', 'organizationalPerson', 'user'], ucd)
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'addResponse'}

This result is consistent with the Microsoft documentation:
此结果与 Microsoft 文档一致:

To modify this attribute [the password], the client must have a 128-bit Transport Layer Security (TLS)/Secure Socket Layer (SSL) connection to the server. An encrypted session using SSP-created session keys using Windows New Technology LAN Manager (NTLM) or Kerberos are also acceptable as long as the minimum key length is met.
若要修改此属性 [密码],客户端必须与服务器建立 128 位传输层安全性 (TLS)/安全套接字层 (SSL) 连接。只要满足最小密钥长度,使用 Windows 新技术 LAN 管理器 (NTLM) 或 Kerberos 的 SSP 创建的会话密钥的加密会话也是可以接受的。

As an alternative, it is also possible to use LDAPS instead of LDAP. Since the TLS layer provides integrity, LDAP signing is not enforced on LDAPS connections.
作为替代方案,也可以使用 LDAPS 而不是 LDAP。由于 TLS 层提供完整性,因此不会在 LDAPS 连接上强制执行 LDAP 签名。

Channel Binding

If the domain controller forces channel binding by setting the option LDAP server channel binding token req to Always, the vanilla ldap3 library is not able to authenticate:

>>> import ldap3
>>> server = ldap3.Server('ldaps://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication=ldap3.NTLM, user='sevenkingdoms.local\\cersei.lannister', password='il0vejaime')
>>> connection.bind()
False
>>> connection.result
{'result': 49, 'description': 'invalidCredentials', 'dn': '', 'message': '80090346: LdapErr: DSID-0C0906B0, comment: AcceptSecurityContext error, data 80090346, v4f7c\x00', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

The server returns an invalidCredential error with AcceptSecurityContext error, data 80090346 comment. This behavior is not well documented, but we can extrapolate that this error code means that the channel binding token is wrong according to Microsoft documentation on Digest authentication (see SEC_E_BAD_BINDINGS on this page). Tools such as LdapRelayScan use this technique to determine if channel binding is enforced or not.

Inspired by CravateRouge’s work, yours truly submitted a PR to implement channel binding support in ldap3. Thus, the DC now accepts the binding:

>>> import ldap3
>>> server = ldap3.Server('ldaps://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication=ldap3.NTLM, user='sevenkingdoms.local\\cersei.lannister', password='il0vejaime', channel_binding=ldap3.TLS_CHANNEL_BINDING)
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

Now, a new AV_PAIR bears the channel binding token:

LDAP authentication in Active Directory environments

Sending a wrong token raises an authentication error, which shows that the domain controller verifies it on its side too.

Side note: You can easily debug TLS protected connection with ldap3 using the SSLKEYLOGFILE environment variable.

$ export SSLKEYLOGFILE="sslkey.log"
$ python3 my_custom_ldap3_script.py

Then all you need to do is setting the sslkey.log file under the Wireshark menu Edit-> Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filename.

Keep in mind that both protections (channel binding and LDAP signing) are checked during the bind operation, thus a channel binding bypass can be performed on domain controller with LDAP signing not required.

Side quest: the curious case of NTLM signing

Interestingly, unlike DIGEST-MD5 (see associated section below), it seems that it is not possible to send “signed only” requests when authenticating with Sicily. Indeed, according to Microsoft documentation, NTLM Integrity allows to send a cleartext message and use the signing key to sign it. To enable this feature, the client needs to include the NTLMSSP_NEGOTIATE_SIGN flag within the NEGOTIATE_MESSAGE message sent by the client.

However, during testing, after sending packets like the following, the domain controller systematically returned errors “Unable to decrypt the payload”:

[size_of_the_PDU][signature][cleartext_payload]
    4 bytes       16 bytes        X bytes

It seems that it tries to decrypt the cleartext payload even if the client only sent the NTLMSSP_NEGOTIATE_SIGN flag. It is unclear if “signed only” messages are supported with this authentication method. If you have already done some tests on this topic, feel free to contact us.

Kerberos authentication

All the examples in this section are done after this simple setup:

$ # cd into your impacket venv
$ getST.py sevenkingdoms.local/cersei.lannister:'il0vejaime' -dc-ip kingslanding.sevenkingdoms.local -spn ldap/kingslanding.sevenkingdoms.local
Impacket v0.12.0.dev1+20231015.203043.419e6f24 - Copyright 2023 Fortra

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Getting ST for user
[*] Saving ticket in cersei.lannister.ccache
$ 
$ KRB5CCNAME=cersei.lannister.ccache python3

ldap3 is based on python-gssapi to handle Kerberos authentication. This library needs the full hostname in the ccache file’s SPN and the realm needs to be in uppercase, otherwise it throws an error. So, the format needs to be ldap/[email protected]. For example, ldap/[email protected] will not work. However, because the SPN is not encrypted, it is possible to change it in order to be accepted by gssapi. This modification can be done with impacket (see impacket’s #1256). Because python-gssapi is a front-end to the system package gssapi, using a TGT is also possible with the same limitation about the format (it needs to be krbtgt/[email protected]).

LDAP signing

If LDAP signing is enforced by the domain controller, ldap3 is unable to bind against the DC using the LDAP scheme:

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local')
>>> # Remember: realm must be in uppercase
>>> connection = ldap3.Connection(server, user='[email protected]', authentication = ldap3.SASL, sasl_mechanism=ldap3.KERBEROS)
>>> connection.bind()
False
>>> connection.result
{'result': 8, 'description': 'strongerAuthRequired', 'dn': '', 'message': '00002028: LdapErr: DSID-0C090259, comment: The server requires binds to turn on integrity checking if SSL\\TLS are not already active on the connection, data 0, v4563\x00', 'referrals': None, 'saslCreds': b'', 'type': 'bindResponse'}

Once again, pull request #1042 enhances the capabilities of ldap3 by implementing Kerberos payload encryption:

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, user='[email protected]', authentication = ldap3.SASL, sasl_mechanism=ldap3.KERBEROS, session_security=ldap3.ENCRYPT)
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': b'', 'type': 'bindResponse'}
>>> # Payloads are now encrypted, it is now possible to create computer or user as it would be possible with LDAPS.

Sealed flag is set within the Kerberos context, which means that the payload is now encrypted and signed:

LDAP authentication in Active Directory environments

The symmetric key used to encrypt those exchanges is sent by the server in the authenticator part of the AP-REP (keytype 18 usage 22 and 24).

Channel Binding

Regarding channel binding support, python-gssapi is compatible with it, so it is possible to authenticate ldap3 against domain controller with this protection.

>>> import ldap3
>>> server = ldap3.Server('ldaps://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, user='[email protected]', authentication = ldap3.SASL, sasl_mechanism=ldap3.KERBEROS)
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': b'', 'type': 'bindResponse'}

The channel binding token is sent within the authenticator in the AP-REQ Kerberos message:

LDAP authentication in Active Directory environments

During our tests, we were not able to send a crafted channel binding token without patching the libkrb5-dev low level package. Thus, it is unclear if the domain controller checks it (more on this in the GSS-SPNEGO section).
在我们的测试中,如果不修补低级包,我们就无法发送构建的 libkrb5-dev 通道绑定令牌。因此,不清楚域控制器是否对其进行了检查(在 GSS-SPNEGO 部分中对此进行了更多介绍)。

Side note: You can debug Kerberos protected connections with Wireshark as explained here.
旁注:您可以使用 Wireshark 调试受 Kerberos 保护的连接,如此处所述。

Simple authentication 简单身份验证

Simple authentication is a classic user/password authentication, so it is not “vulnerable” to relay attack: if an attacker manages to trick the victim to authenticate against their machine, they don’t need to relay the authentication as they got the cleartext password. This authentication method is not compatible with LDAP signing, a strongerAuthRequired error is returned by the server if the client tries to authenticate. However, it turns out that simple authentication is not impacted by channel binding enforcement. Thus, if the attacker has a valid account, its corresponding password (it will not work with the NT hash, so pth attack is not possible), and the DC exposes an LDAPS service, they can use it to query the DC.
简单身份验证是经典的用户/密码身份验证,因此中继攻击并不“易受攻击”:如果攻击者设法诱骗受害者对其计算机进行身份验证,则他们不需要中继身份验证,因为他们获得了明文密码。此身份验证方法与 LDAP 签名不兼容,如果客户端尝试进行身份验证,服务器将返回 strongerAuthRequired 错误。但是,事实证明,简单身份验证不受通道绑定强制的影响。因此,如果攻击者拥有有效的帐户、其对应的密码(它不适用于 NT 哈希,因此不可能进行 pth 攻击),并且 DC 暴露了 LDAPS 服务,则可以使用它来查询 DC。

>>> server = ldap3.Server('ldaps://192.168.56.10')
>>> connection = ldap3.Connection(server, authentication=ldap3.SIMPLE, user='[email protected]', password='il0vejaime')
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

DIGEST-MD5 authentication
DIGEST-MD5 身份验证

DIGEST-MD5 is an obsolete authentication mechanism, but it is still supported by Active Directory. It is a challenge/response authentication protocol, so it works with a shared secret between the client and the server. According to the RFC, the shared secret is based on the following elements:
DIGEST-MD5 是一种过时的身份验证机制,但 Active Directory 仍支持它。它是一种质询/响应身份验证协议,因此它适用于客户端和服务器之间的共享密钥。根据 RFC,共享密钥基于以下元素:

a0 = MD5(username-value + ":" + realm-value + ":" + password)

However, as Active Directory does not store the password in a reversible form (unless this behavior is explicitly enabled through GPO/registry), we can ask ourselves how it is possible to compute a0 without knowing the cleartext password.
但是,由于 Active Directory 不会以可逆形式存储密码(除非通过 GPO/注册表显式启用此行为),因此我们可以问自己如何在 a0 不知道明文密码的情况下进行计算。

$ echo -n 'cersei.lannister:sevenkingdoms.local:il0vejaime' | md5sum
532062350b784ce18e7680a2447f6fa1  -
$ echo -n 'CERSEI.LANNISTER:SEVENKINGDOMS.LOCAL:il0vejaime' | md5sum
f39ea5d591925fa63047235f63927a71  -

Turns out that Active Directory stores 29 MD5 hashes which represent various formats of this information. DSIntenals can retrieve those hashes:
事实证明,Active Directory 存储了 29 个 MD5 哈希值,这些哈希值表示此信息的各种格式。DSIntenals 可以检索这些哈希值:

PS C:\Windows\system32> Get-ADReplAccount -SamAccountName cersei.lannister -Server sevenkingdoms.local

DistinguishedName: CN=cersei.lannister,OU=Crownlands,DC=sevenkingdoms,DC=local
Sid: S-1-5-21-3990748329-3747950281-3952183803-1115
Guid: 2090a2ab-aeb5-4770-8b9c-de268cb2fc7b
SamAccountName: cersei.lannister
[...]
Owner: S-1-5-21-3990748329-3747950281-3952183803-512
Secrets
  NTHash: c247f62516b53893c7addcf8c349954b
[...]
  SupplementalCredentials:
    [...]
    WDigest:
      Hash 01: ae9746cdc9ab4c98389c931240bfef49
      Hash 02: d134ad87efe27cb920f7261c716e071e
      Hash 03: 2cf4c83f64539f3379e14bbc6bb62f7a
      Hash 04: ae9746cdc9ab4c98389c931240bfef49
      Hash 05: d134ad87efe27cb920f7261c716e071e
      Hash 06: ee1f53e170afc2923c76ca1608e24a7c
      Hash 07: ae9746cdc9ab4c98389c931240bfef49
      Hash 08: 532062350b784ce18e7680a2447f6fa1
      Hash 09: 532062350b784ce18e7680a2447f6fa1
      Hash 10: f39ea5d591925fa63047235f63927a71
      Hash 11: 637e50611556b02b161e139f6721df9a
      Hash 12: 532062350b784ce18e7680a2447f6fa1
      Hash 13: a89bf491596edce803f45c0fe8b85c2d
      Hash 14: 637e50611556b02b161e139f6721df9a
      Hash 15: 6228c58f5f6bb52c8d292aedc6747ce2
      Hash 16: 6228c58f5f6bb52c8d292aedc6747ce2
      Hash 17: 633a4bbaf44681cc7c476633044f965a
      Hash 18: f2df7fedf3c221c684f98119dd1d143b
      Hash 19: 342b3287424f1021b1efc474694b69c4
      Hash 20: d386808d89c8360df3746b66ee3e3b46
      Hash 21: 8cf3aecb2eae565c0e91088f1168a753
      Hash 22: 8cf3aecb2eae565c0e91088f1168a753
      Hash 23: e80735b18be0aeec5ed4cd330ac9e1d3
      Hash 24: 894cc33e043d29a258037531fb1af135
      Hash 25: 894cc33e043d29a258037531fb1af135
      Hash 26: d1f74dbf597d85638150322df881678b
      Hash 27: 3214f15a66cb313eb40d94582caaacd4
      Hash 28: 51fdf783ee508745d0472b36d6e89033
      Hash 29: 4f3bba9380e6e3ad7297608c91fa99dc
[...]

LDAP signing LDAP 签名

According to the RFC, DIGEST-MD5 can negotiate signing and encryption during the authentication phase. Active Directory also supports encryption and signature with DIGEST-MD5: when initiating a SASL DIGEST-MD5 authentication the server returns qop="auth,auth-int,auth-conf" which means:
根据 RFC,DIGEST-MD5 可以在身份验证阶段协商签名和加密。Active Directory 还支持使用 DIGEST-MD5 进行加密和签名:启动 SASL DIGEST-MD5 身份验证时,服务器返回 qop="auth,auth-int,auth-conf" 以下命令:

qop-options: A quoted string of one or more tokens indicating the “quality of protection” values supported by the server. The value “auth” indicates authentication; the value “auth-int” indicates authentication with integrity protection; the value “auth-conf” indicates authentication with integrity protection and encryption.
qop-options:一个或多个标记的带引号的字符串,指示服务器支持的“保护质量”值。值“auth”表示身份验证;值“auth-int”表示具有完整性保护的身份验证;值“auth-conf”表示具有完整性保护和加密的身份验证。

Vanilla ldap3 supports signature with DIGEST-MD5 and can be used to circumvent the lack of NTLM signing support.

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local') # You cannot use an IP address here, must be a FQDN with a LDAP SPN registred
>>> connection = ldap3.Connection(server, authentication = ldap3.SASL, sasl_mechanism = ldap3.DIGEST_MD5, sasl_credentials = (None, 'cersei.lannister', 'il0vejaime', None, None))
>>> connection.bind()
False
>>> connection.result
{'result': 8, 'description': 'strongerAuthRequired', 'dn': '', 'message': '00002028: LdapErr: DSID-0C090254, comment: The server requires binds to turn on integrity checking  if SSL\\TLS are not already active on the connection, data 0, v4f7c\x00', 'referrals': None, 'saslCreds': b'rspauth=5f33da03b10aa1b509ea9d71758279b5', 'type': 'bindResponse'}
>>> # LDAP signing is enabled on the server and ldap3's DIGEST-MD5 implementation supports it
>>> connection = ldap3.Connection(server, authentication = ldap3.SASL, sasl_mechanism = ldap3.DIGEST_MD5, sasl_credentials = (None, 'cersei.lannister', 'il0vejaime', None, 'sign'))
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': b'rspauth=f71443de6af69750719e03445bf9ff03', 'type': 'bindResponse'}

Pull request #1042 also implements encryption for DIGEST-MD5.
拉取请求 #1042 还实现了 DIGEST-MD5 的加密。

>>> import ldap3
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication = ldap3.SASL, sasl_mechanism = ldap3.DIGEST_MD5, sasl_credentials = (None, 'cersei.lannister', 'il0vejaime', None, 'ENCRYPT'))
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': b'rspauth=7a9f565a4f9bf185c9eb7927bf6621ed', 'type': 'bindResponse'}
>>> # Payloads are now encrypted, it is now possible to create computer or user as it would be possible with LDAPS.

Below, a signed whoami response:
以下是签名 whoami 的回应:

LDAP authentication in Active Directory environments

The same response encrypted:
加密的相同响应:

LDAP authentication in Active Directory environments

The format of the LDAP PDUs with DIGEST-MD5 confidentiality/integrity is similar to those with NTLM confidentiality:
具有 DIGEST-MD5 机密性/完整性的 LDAP PDU 的格式与具有 NTLM 机密性的 LDAP PDU 的格式类似:

[size_of_the_PDU][encrypted or cleartext payload][checksum][signature_version][sequence_number]
      4 bytes                   X bytes           10 bytes       2 bytes           4 bytes

Channel Binding 通道绑定

The drawback of using DIGEST-MD5 is the lack of channel binding support (even if an IETF draft exists):
使用 DIGEST-MD5 的缺点是缺乏通道绑定支持(即使存在 IETF 草案):

>>> import ldap3
>>> server = ldap3.Server('ldaps://kingslanding.sevenkingdoms.local')
>>> connection = ldap3.Connection(server, authentication = ldap3.SASL, sasl_mechanism = ldap3.DIGEST_MD5, sasl_credentials = (None, 'cersei.lannister', 'il0vejaime', None, None))
>>> connection.bind()
False
>>> connection.result
{'result': 49, 'description': 'invalidCredentials', 'dn': '', 'message': '80090346: LdapErr: DSID-0C09058A, comment: AcceptSecurityContext error, data 80090346, v4563\x00', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

Schannel authentication Schannel 身份验证

Another interesting mode is Schannel: it relies on TLS so it is, by design, not subject to channel binding, as the authentication is borne by TLS itself.
另一个有趣的模式是 Schannel:它依赖于 TLS,因此根据设计,它不受通道绑定的约束,因为身份验证由 TLS 本身承担。

>>> import ldap3, ssl
>>> tls = ldap3.Tls(local_private_key_file='jaime.key', local_certificate_file='jaime.crt', validate=ssl.CERT_NONE)
>>> server = ldap3.Server('ldaps://kingslanding.sevenkingdoms.local', tls=tls)
>>> connection = ldap3.Connection(server)
>>> connection.open()
>>> print(connection.extend.standard.who_am_i())
u:SEVENKINGDOMS\jaime.lannister

Schannel is not subject to LDAP signing either as the bind is performed after a StartTLS command when used on the LDAP TCP port.
Schannel 也不受 LDAP 签名的约束,因为在 LDAP TCP 端口上使用时, bind 会在 StartTLS 命令之后执行。

>>> import ldap3, ssl
>>> tls = ldap3.Tls(local_private_key_file='jaime.key', local_certificate_file='jaime.crt', validate=ssl.CERT_NONE)
>>> server = ldap3.Server('ldap://kingslanding.sevenkingdoms.local', tls=tls)
>>> connection = ldap3.Connection(server, authentication=ldap3.SASL, sasl_mechanism=ldap3.EXTERNAL)
>>> connection.open()
>>> connection.start_tls()
True
>>> connection.bind()
True
>>> connection.result
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}

PassTheCert and Certipy use this authentication mechanism to perform various attacks.
PassTheCert 和 Certipy 使用此身份验证机制来执行各种攻击。

impacket.ldap

The impacket library also implements the LDAP RFC, however, its implementation is minimalistic and not as powerful as ldap3 (developers urge users to use third party libraries for more complex use cases).
impacket 库还实现了 LDAP RFC,但是,它的实现是简约的,不如 ldap3 强大(开发人员敦促用户在更复杂的用例中使用第三方库)。

Based on our experience, the main limitations of impacket.ldap are related to bad Unicode handling (“Utilisateurs du Bureau à distance” 🥖🇫🇷) and bugs when using complex search filters containing special characters (if your DN is like CN=John Doe (admin account) for example). However, it is still used by some of impacket’ scripts and according to the code, impacket.ldap supports 4 (sub) authentication methods:
根据我们的经验,主要 impacket.ldap 限制与糟糕的 Unicode 处理(“Utilisateurs du Bureau à distance”🥖🇫🇷)和使用包含特殊字符的复杂搜索过滤器时的错误有关(例如,如果您的 DN 是这样的 CN=John Doe (admin account) )。但是,它仍然被一些 impacket 脚本使用,并且根据代码, impacket.ldap 支持 4 种(子)身份验证方法:

  • Simple 简单
  • Sicily 西西里岛
  • SASL (GSS-SPNEGO NTLM) SASL (GSS-SPNEGO NTLM)
  • SASL (GSS-SPNEGO Kerberos)
    SASL (GSS-SPNEGO Kerberos)

Impacket’s Sicily implementation does not support LDAP signing nor channel binding.
Impacket 的 Sicily 实现不支持 LDAP 签名或通道绑定。

GSS-SPNEGO NTLM

To sum up, GSS-SPNEGO NTLM encapsulates the NTLM protocol within a SASL structure, so it is pretty similar to Sicily authentication. As confidentiality, integrity and channel binding are not implemented within impacket.ldap, this library will not work against hardened domain controllers. However, it seems easy to implement them as impacket implements a lot of NTLM features.
总而言之,GSS-SPNEGO NTLM 将 NTLM 协议封装在 SASL 结构中,因此它与西西里岛身份验证非常相似。由于 中未实现机密性、完整性和通道绑定 impacket.ldap ,因此此库不适用于强化的域控制器。但是,由于 impacket 实现了很多 NTLM 功能,因此实现它们似乎很容易。

>>> from impacket.ldap import ldap, ldapasn1
>>> ldap_connection = ldap.LDAPConnection('ldap://kingslanding.sevenkingdoms.local', 'dc=sevenkingdoms,dc=local', '192.168.56.10')
>>> ldap_connection.login('cersei.lannister', 'il0vejaime', 'sevenkingdoms.local', '', '', authenticationChoice="sasl")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/impacket/impacket/ldap/ldap.py", line 369, in login
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> strongerAuthRequired: 00002028: LdapErr: DSID-0C090259, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v4563
>>> 
>>> # impacket.ldap does not implement NTLM confidentiality, now let's try with channel binding
>>> 
>>> ldap_connection = ldap.LDAPConnection('ldaps://kingslanding.sevenkingdoms.local', 'dc=sevenkingdoms,dc=local', '192.168.56.10')
>>> ldap_connection.login('cersei.lannister', 'il0vejaime', 'sevenkingdoms.local', '', '', authenticationChoice="sasl")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/impacket/impacket/ldap/ldap.py", line 369, in login
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C09058A, comment: AcceptSecurityContext error, data 80090346, v4563

GSS-SPNEGO Kerberos

As mentioned before, it is not clear if Kerberos authentication is impacted by channel binding with GSSAPI. However, we can state that, it is possible to query a hardened domain controller with impacket’s ldap even if channel binding is enforced on LDAPS:
如前所述,目前尚不清楚 Kerberos 身份验证是否受到与 GSSAPI 的通道绑定的影响。但是,我们可以说,即使对 LDAPS 强制执行通道绑定,也可以使用 impacket 的 LDAP 查询强化域控制器:

>>> from impacket.ldap import ldap, ldapasn1
>>> ldap_connection = ldap.LDAPConnection('ldap://kingslanding.sevenkingdoms.local', 'dc=sevenkingdoms,dc=local', '192.168.56.10')
>>> ldap_connection.kerberosLogin('cersei.lannister', 'il0vejaime', 'sevenkingdoms.local', '', '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/impacket/impacket/ldap/ldap.py", line 252, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> strongerAuthRequired: 00002028: LdapErr: DSID-0C090259, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v4563
>>> 
>>> # impacket.ldap does not implement Kerberos confidentiality, now let's try with channel binding
>>> 
>>> dap_connection = ldap.LDAPConnection('ldaps://kingslanding.sevenkingdoms.local', 'dc=sevenkingdoms,dc=local', '192.168.56.10')
>>> ldap_connection.kerberosLogin('cersei.lannister', 'il0vejaime', 'sevenkingdoms.local', '', '')
True
>>> attributes=list()
>>> paged_search_control = ldapasn1.SimplePagedResultsControl(criticality=True,size=10)
>>> search_filter = '(&(objectCategory=group)(name=Domain Admins))'
>>> search_results = ldap_connection.search(searchFilter=search_filter,searchControls=[paged_search_control],attributes=attributes)

The channel binding token is not present within the authenticator part of the AP-REQ, however the bind request is accepted:
通道绑定令牌不存在于 AP-REQ 的部分中 authenticator ,但接受绑定请求:

LDAP authentication in Active Directory environments

If the token sent by the client is wrong, the bind request fails with the error code AcceptSecurityContext error, data 80090346:
如果客户端发送的令牌错误,则绑定请求将失败,并显示错误代码 AcceptSecurityContext error, data 80090346 :

LDAP authentication in Active Directory environments

Summary 总结

LDAP LDAP (英语) LDAPS LDAPS的 LDAP + Signing LDAP + 签名 LDAPS + Channel Binding
LDAPS + 通道绑定
NTLM (ldap3 vanilla) NTLM ( ldap3 vanilla)
Kerberos (ldap3 vanilla)
Kerberos ( ldap3 vanilla)
Schannel (ldap3 vanilla)
Schannel ( ldap3 vanilla)
✅ (*) N/A N/A
Simple Authentication (ldap3 vanilla)
简单身份验证 ( ldap3 vanilla)
DIGEST-MD5 (ldap3 vanilla)
DIGEST-MD5 ( ldap3 香草)
✅ (**)
NTLM (ldap3 patched) NTLM(已修补 ldap3)
Kerberos (ldap3 patched)
Kerberos(已修补 ldap3)
GSS-SPNEGO NTLM (impacket.ldap)
GSS-SPNEGO NTLM ( impacket.ldap)
GSS-SPNEGO Kerberos (impacket.ldap)
GSS-SPNEGO Kerberos ( impacket.ldap)

* Using StartTLS * 使用 StartTLS

** Using the option sign
** 使用选项 sign

Conclusion 结论

This brief overview of LDAP authentication protocols usable in Active Directory environments shows that client-side support can vary widely depending on the implementation, but can often be “hacked in” when needed. Since a lot of impacket’s examples are based on ldap3, it seems easy to adapt them to work against hardened domain controllers by installing patches (as shown here by snovvcrash). A fork has been created with the 2 PRs to easily use them in tools. Certipy has recently started supporting channel binding and pywerview implements a logic to automatically detect and handle LDAP protections for you (full disclosure: the author is a pywerview maintainer). If patching ldap3 library is not an option, it is still possible to directly patch the tools to circumvent the problem (see here, and here). Lastly, an even more radical approach can be used: tools such as msldap have chosen to reimplement the authentication stack, via asysocksasyauth or minikerberos.
本文简要概述了 Active Directory 环境中可用的 LDAP 身份验证协议,这些协议表明,客户端支持可能因实现的不同而有很大差异,但通常可以在需要时被“黑客入侵”。由于许多 impacket 的示例都是基于 ldap3 的,因此通过安装补丁(如此处的 snovvcrash 所示)来调整它们以对抗强化的域控制器似乎很容易。已使用 2 个 PR 创建了一个分支,以便在工具中轻松使用它们。Certipy 最近开始支持通道绑定,pywerview 实现了一个逻辑来自动检测和处理 LDAP 保护(完全披露:作者是 pywerview 维护者)。如果修补 ldap3 库不是一种选择,仍然可以直接修补工具来规避问题(请参阅此处和此处)。最后,可以使用更激进的方法:msldap 等工具选择通过 asysocks、asyauth 或 minikerberos 重新实现身份验证堆栈。

Credits 学分

The author would like to thank:
作者要感谢:

  • CravateRouge for kickstarting the idea of this blogpost with their pull request.
    CravateRouge 通过他们的拉取请求启动了这篇博文的想法。
  • Giovanni Cannata and all the contributors to the ldap3 project.
    Giovanni Cannata 和 ldap3 项目的所有贡献者。
  • Forta, SecureAuth, Alberto Solino and all the contributors to the impacket project.
    Forta、SecureAuth、Alberto Solino 和 impacket 项目的所有贡献者。
  • SkelSec for their tools, sometimes better than the official Microsoft documentation.
    SkelSec 为他们的工具,有时比 Microsoft 官方文档更好。
  • Mayfly / Orange-CyberDefense for the GOAD project, used in the examples.
    Mayfly / Orange-CyberDefense 用于 GOAD 项目,在示例中使用。

原文始发于@lowercase_drmLDAP authentication in Active Directory environments

版权声明:admin 发表于 2023年11月1日 上午9:21。
转载请注明:LDAP authentication in Active Directory environments | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...