Thursday 30 January 2020

LDAP Channel Binding


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.

1 comment:

  1. 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.

    The 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.

    ReplyDelete