From 3ca86602ef9a2ce8a2e22813736a96ba544471cf Mon Sep 17 00:00:00 2001 From: felix h Date: Mon, 10 Aug 2026 23:24:18 +0200 Subject: [PATCH] spnego_sspi: pass channel bindings on initial context HTTP Negotiate can create and send a Kerberos token before receiving a server challenge. The SSPI backend only supplied Schannel endpoint bindings when a challenge token was present, causing IIS with EPA enabled to reject preemptive Kerberos authentication with STATUS_BAD_BINDINGS. Build the SSPI input descriptor independently of the challenge token so Schannel endpoint bindings are included on both initial and subsequent InitializeSecurityContext calls. Assisted-by: martin-fzi@users.noreply.github.com Fixes https://github.com/curl/curl/issues/22466 Closes https://github.com/curl/curl/pull/22537 --- lib/vauth/ntlm_sspi.c | 12 ++++++--- lib/vauth/spnego_sspi.c | 58 +++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/vauth/ntlm_sspi.c b/lib/vauth/ntlm_sspi.c index 4f977865c9..17fb1d2922 100644 --- a/lib/vauth/ntlm_sspi.c +++ b/lib/vauth/ntlm_sspi.c @@ -242,6 +242,9 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, SecBufferDesc type_3_desc; SECURITY_STATUS status; unsigned long attrs; +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + SecPkgContext_Bindings pkgBindings = { 0, NULL }; +#endif (void)creds; @@ -262,9 +265,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 */ if(ntlm->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; status = Curl_pSecFn->QueryContextAttributes( ntlm->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, @@ -296,6 +296,12 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 0, ntlm->context, &type_3_desc, &attrs, NULL); + +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); +#endif + if(status != SEC_E_OK) { infof(data, "NTLM handshake failure (type-3 message): Status=0x%08lx", (unsigned long)status); diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c index 3a140ad84e..7d3c9ce2d8 100644 --- a/lib/vauth/spnego_sspi.c +++ b/lib/vauth/spnego_sspi.c @@ -93,6 +93,9 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, SecBufferDesc chlg_desc; SecBufferDesc resp_desc; unsigned long attrs; +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + SecPkgContext_Bindings pkgBindings = { 0, NULL }; +#endif if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server @@ -194,6 +197,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 0; + chlg_desc.pBuffers = chlg_buf; + if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { @@ -209,38 +216,33 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } /* Setup the challenge "input" security buffer */ - chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; - chlg_desc.pBuffers = &chlg_buf[0]; chlg_buf[0].BufferType = SECBUFFER_TOKEN; chlg_buf[0].pvBuffer = chlg; chlg_buf[0].cbBuffer = curlx_uztoul(chlglen); + } #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS - /* SSL context comes from Schannel. - * When extended protection is used in IIS server, - * we have to pass a second SecBuffer to the SecBufferDesc - * otherwise IIS does not pass the authentication (401 response). - * Minimum supported version is Windows 7. - * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 - */ - if(nego->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; - nego->status = Curl_pSecFn->QueryContextAttributes( - nego->sslContext, - SECPKG_ATTR_ENDPOINT_BINDINGS, - &pkgBindings); - if(nego->status == SEC_E_OK) { - chlg_desc.cBuffers++; - chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; - chlg_buf[1].cbBuffer = pkgBindings.BindingsLength; - chlg_buf[1].pvBuffer = pkgBindings.Bindings; - } + /* SSL context comes from Schannel. + * When extended protection is used in IIS server, pass its channel + * bindings on the initial call too. HTTP Negotiate can create and send a + * Kerberos token before receiving a challenge from the server. + * Minimum supported version is Windows 7. + * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 + */ + if(nego->sslContext) { + nego->status = Curl_pSecFn->QueryContextAttributes( + nego->sslContext, + SECPKG_ATTR_ENDPOINT_BINDINGS, + &pkgBindings); + if(nego->status == SEC_E_OK) { + SecBuffer *binding_buf = &chlg_buf[chlg_desc.cBuffers++]; + binding_buf->BufferType = SECBUFFER_CHANNEL_BINDINGS; + binding_buf->cbBuffer = pkgBindings.BindingsLength; + binding_buf->pvBuffer = pkgBindings.Bindings; } -#endif } +#endif /* Setup the response "output" security buffer */ resp_desc.ulVersion = SECBUFFER_VERSION; @@ -261,11 +263,17 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, nego->spn, sspi_flags, 0, SECURITY_NATIVE_DREP, - chlg ? &chlg_desc : NULL, + chlg_desc.cBuffers ? + &chlg_desc : NULL, 0, nego->context, &resp_desc, &attrs, NULL); } +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); +#endif + /* Free the decoded challenge as it is not required anymore */ curlx_free(chlg);