LDAP Channel Binding has two pre-requisites that must be fulfilled before it can be used. Firstly, the LDAP connection must flow through a TLS channel and secondly the chosen authentication information must be capable of carrying channel binding information.
The LDAP TLS channel can be established either via the LDAP StartTLS operation or by connecting to the “ldaps” port (636).
Under Windows, the core routine used by clients to both establish a TLS channel and to perform Kerberos/NTLM/Negotiate type authentication is InitializeSecurityContext; one of the outputs of this routine is a “security context handle”.
If the TLS channel establishment is successful, the client will have a security context handle that can be used in a call to the routine QueryContextAttributes to retrieve the SECPKG_ATTR_ENDPOINT_BINDINGS attribute; the value of this attribute is a structure of type SEC_CHANNEL_BINDINGS. A dump of some typical “application data” appended to this structure looks like:
74 6c 73 2d 73 65 72 76-65 72 2d 65 6e 64 2d 70 tls-server-end-p
6f 69 6e 74 3a f3 c1 2c-55 13 61 ae 65 13 56 dc oint:..,U.a.e.V.
4e de fa fa e0 12 bb 0f-dc 96 64 cf 98 84 9f a4 N.........d.....
ee 14 de 0e d0 .....
The format of the data is the string “tls-server-end-point:” followed by a hash of the server-side certificate used in establishing the TLS channel. The hash function is chosen according to the procedure described in RFC 5929.
The use of the “tls-server-end-point” channel binding type rather than the stricter “tls-unique” type means that LDAP channel binding (quoting from RFC 5929) “may be used with existing TLS server-side proxies ("concentrators") without modification to the proxies”. I interpret this to mean that TLS proxies which use the same certificate and private key as the LDAP server can be used.
When generating the authentication messages to be exchanged with the LDAP server, the channel binding information can be included in the messages by providing the information in a SECBUFFER_CHANNEL_BINDINGS input to the InitializeSecurityContext routine.
The format for channel binding data in a NTLM AUTHENTICATE_MESSAGE is of the same format as that used by Kerberos (documented in RFC 4121), namely a MD5 hash of the channel binding information. This is a dump of an NTLM AUTHENTICATE_MESSAGE used in LDAP authentication, with the channel binding data highlighted:
The dump of the LDAP bind response packet above is not easy to obtain, since the channel binding information is only present if the response is sent inside a TLS channel – one needs to be able to either decrypt the TLS data or capture data inside the TLS channel (perhaps via Event Tracing for Windows (ETW), using perhaps the Microsoft-Windows-LDAP-Client provider).
Viewing the channel binding information when Kerberos is used rather than NTLM is even more difficult. The following image is the Kerberos equivalent of the NTLM data above:
The channel binding information is in the encrypted “Authenticator” and the (session) key needed to decode that is in the encrypted part of the ticket (which is encrypted with the long-term key of the service). Decrypting the ticket reveals:
flags = 40-A1-00-00
key = [23] q11qognXq26zJ5H6s75RQA==
crealm = LEIMGRUBE.CH
cname = cisco
transited = [1]
authtime = 2020-01-28T16:48:52.0000000+01:00
starttime = 2020-01-29T22:14:45.0000000+01:00
endtime = 2020-01-30T08:03:52.0000000+01:00
renew-till = 2020-02-04T16:48:52.0000000+01:00
authorization-data = [AD-IF-RELEVANT] {
[AD-WIN2K-PAC]
LogonTime: 2020-01-24T11:05:53.3797010Z
LogoffTime: 0001-01-01T00:00:00.0000000
KickOffTime: 0001-01-01T00:00:00.0000000
PasswordLastSet: 2020-01-21T15:21:39.6988208Z
PasswordCanChange: 2020-01-22T15:21:39.6988208Z
PasswordMustChange: 0001-01-01T00:00:00.0000000
EffectiveName: cisco
FullName: Cisco
LogonScript:
ProfilePath:
HomeDirectory:
HomeDirectoryDrive:
LogonCount: 1
BadPasswordCount: 0
UserId: 1113
PrimaryGroupId: 513
GroupIds: 7 513
UserFlags: 0x20
UserSessionKey: 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
LogonServer: EC2AMAZ-EHM3HUN
LogonDomainName: LEIMGRUBE
LogonDomainId: S-1-5-21-1700842961-3107118889-547158845
UserAccountControl: 0x210
SubAuthStatus: 0x0
LastSuccessfulILogon: 1601-01-01T00:00:00.0000000Z
LastFailedILogon: 1601-01-01T00:00:00.0000000Z
FailedILogonCount: 0x0
ExtraSids: 0x7 S-1-18-1
PAC Type ClientInformation: 00-0A-4A-70-F2-D5-D5-01 cisco
PAC Type UpnDns: 0 cisco@Leimgrube.ch LEIMGRUBE.CH
PAC Type ServerChecksum: 76-FF-FF-FF-0A-06-6F-27-CB-CE-81-C9-94-60-EB-E8-AA-07-42-DA
PAC Type KdcChecksum: 10-00-00-00-76-6D-58-0C-35-85-02-54-0E-28-85-50
}
Using the session key to decrypt the authenticator reveals:
authenticator-vno = 5
crealm = LEIMGRUBE.CH
cname = cisco
cksum = [32771] bind = 44-B8-1F-E3-18-3E-1D-C2-26-73-9C-7A-03-DB-16-0F, flags = 22-00-00-00
ctime = 2020-01-29T22:22:04.0440810+01:00
subkey = [23] 5/ltpN4vZsmj+ldkN1fZ7Q==
seq-number = 737976894
authorization-data = [AD-IF-RELEVANT] {
[AD-ETYPE-NEGOTIATION]
{ 18 17 23 }
[KERB_AUTH_DATA_TOKEN_RESTRICTIONS]
{ { [0] { 0 } [1] { byte[40] } } } (Flags = 0x0, TokenIL = 0x3000)
[KERB-LOCAL]
20-CA-25-1A-C4-02-00-00-E4-05-82-3F-00-00-00-00
[AD-AUTH-DATA-AP-OPTIONS]
00-40-00-00
[144]
ldap/dummy@LEIMGRUBE.CH
}
The “bind” data in the authenticator above matches the data in the NTLM image (after converting between hexadecimal and decimal).
Server-side verification of the channel binding data is a parallel of the operations performed on the client side. The server obtains the binding data by a call to the routine QueryContextAttributes to retrieve the SECPKG_ATTR_ENDPOINT_BINDINGS attribute; this data is then used as an input to the AcceptSecurityContext routine. The behaviour of AcceptSecurityContext in the event of missing bindings can be influenced by setting the context request flag ASC_REQ_ALLOW_MISSING_BINDINGS. If a channel binding problem is detected, the status value SEC_E_BAD_BINDINGS or SEC_E_INVALID_TOKEN is returned (depending on the nature of the problem).
The client and server must explicitly perform channel binding – TLS and Kerberos/NTLM can’t collaborate themselves to automatically perform the function. That means that code has to be explicitly added to client and server implementations of LDAP or HTTP or whatever other service that wishes to be protected by channel binding.
Nice post Gary. There's so much superficial garbage about stuff like this on the web so it's refreshing to see your post with some real data, actual identifiers, function names, wireshark captures and decrypted data. There's real meat to your post.
ReplyDeleteThe only tiny nitpick I would mention is that when you say "I interpret this to mean that TLS proxies which use the same certificate and private key as the LDAP server can be used.", I think it is much more likely that someone realized putting IPs in the CBT was a bad idea because 1) it binds to a particular IP and that will obviously not work with anything like a proxy and 2) it's quite pointless since IPs can be spoofed and 3) all that really matters is the TLS certificate because that is what will appear to be different if a MITM presents a bogus cert signed by it's own CA. So, in other words, they just decided to skip the IPs (the 16 bytes of zeros) and put the RFC5929 hash in the "application data" of the gss_channel_bindings_t struct.