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
This commit is contained in:
felix h 2026-08-10 23:24:18 +02:00 committed by Jay Satiro
parent 67221a7882
commit 3ca86602ef
2 changed files with 42 additions and 28 deletions

View file

@ -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);

View file

@ -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);