diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eb64a9cd2..2cc86d2a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1308,6 +1308,11 @@ if(CURL_USE_GSSAPI) elseif(GSS_VERSION) # MIT set(CURL_KRB5_VERSION "\"${GSS_VERSION}\"") endif() + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gss) + check_function_exists("gss_set_neg_mechs" HAVE_GSS_SET_NEG_MECHS) + cmake_pop_check_state() else() message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.") endif() diff --git a/configure.ac b/configure.ac index 6d2cb5583b..98535dada8 100644 --- a/configure.ac +++ b/configure.ac @@ -1997,6 +1997,7 @@ if test "$want_gss" = "yes"; then AC_MSG_RESULT([no]) AC_MSG_ERROR([--with-gssapi was specified, but a GSS-API library was not found.]) ]) + AC_CHECK_FUNCS([gss_set_neg_mechs]) fi build_libstubgss=no diff --git a/lib/curl_gssapi.c b/lib/curl_gssapi.c index c0eb1c4db1..84e2d68819 100644 --- a/lib/curl_gssapi.c +++ b/lib/curl_gssapi.c @@ -319,7 +319,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags) + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle) { OM_uint32 req_flags = GSS_C_REPLAY_FLAG; @@ -341,7 +342,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #ifdef CURL_GSS_STUB if(getenv("CURL_STUB_GSS_CREDS")) return stub_gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, (struct stub_gss_ctx_id_t_desc **)context, target_name, mech_type, @@ -356,7 +357,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #endif /* CURL_GSS_STUB */ return gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, context, target_name, mech_type, diff --git a/lib/curl_gssapi.h b/lib/curl_gssapi.h index 0d3609715a..82839b4087 100644 --- a/lib/curl_gssapi.h +++ b/lib/curl_gssapi.h @@ -41,7 +41,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags); + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle); OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, gss_ctx_id_t *context_handle, diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c index 962fcd05b2..506db8fbda 100644 --- a/lib/socks_gssapi.c +++ b/lib/socks_gssapi.c @@ -180,7 +180,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_token, &gss_send_token, TRUE, - &gss_ret_flags); + &gss_ret_flags, + GSS_C_NO_CREDENTIAL); if(gss_token != GSS_C_NO_BUFFER) { Curl_safefree(gss_recv_token.value); diff --git a/lib/vauth/krb5_gssapi.c b/lib/vauth/krb5_gssapi.c index d6af18f40b..324bbb075b 100644 --- a/lib/vauth/krb5_gssapi.c +++ b/lib/vauth/krb5_gssapi.c @@ -138,7 +138,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, &input_token, &output_token, mutual_auth, - NULL); + NULL, + GSS_C_NO_CREDENTIAL); if(GSS_ERROR(major_status)) { if(output_token.value) diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c index af8498bad3..c129ddeee7 100644 --- a/lib/vauth/spnego_gssapi.c +++ b/lib/vauth/spnego_gssapi.c @@ -37,6 +37,7 @@ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif + /* * Curl_auth_is_spnego_supported() * @@ -158,6 +159,52 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } #endif +#ifdef HAVE_GSS_SET_NEG_MECHS + /* Acquire explicit credentials and restrict SPNEGO sub-mechanisms to + * exclude NTLM. We enumerate all available mechanisms and filter out + * the NTLMSSP OID, matching SSPI's "!ntlm". */ + if(nego->cred == GSS_C_NO_CREDENTIAL) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + gss_OID_set available_mechs = GSS_C_NO_OID_SET; + gss_OID_set filtered_mechs = GSS_C_NO_OID_SET; + + /* Acquire default credentials for SPNEGO */ + major_status = gss_acquire_cred(&minor_status, GSS_C_NO_NAME, + GSS_C_INDEFINITE, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &nego->cred, NULL, NULL); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_acquire_cred() failed: ", + major_status, minor_status); + Curl_safefree(input_token.value); + return CURLE_AUTH_ERROR; + } + + /* Get all available mechanisms */ + major_status = gss_indicate_mechs(&minor_status, &available_mechs); + if(!GSS_ERROR(major_status)) { + /* Build a set excluding NTLMSSP */ + major_status = gss_create_empty_oid_set(&minor_status, &filtered_mechs); + if(!GSS_ERROR(major_status)) { + size_t i; + for(i = 0; i < available_mechs->count; i++) { + gss_OID oid = &available_mechs->elements[i]; + if(oid->length != ntlmssp_oid.length || + memcmp(oid->elements, ntlmssp_oid.elements, oid->length)) { + gss_add_oid_set_member(&minor_status, oid, &filtered_mechs); + } + } + /* Restrict SPNEGO to only use non-NTLM mechanisms */ + gss_set_neg_mechs(&minor_status, nego->cred, filtered_mechs); + gss_release_oid_set(&minor_status, &filtered_mechs); + } + gss_release_oid_set(&minor_status, &available_mechs); + } + } +#endif /* HAVE_GSS_SET_NEG_MECHS */ + /* Generate our challenge-response message */ major_status = Curl_gss_init_sec_context(data, &minor_status, @@ -168,7 +215,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, &input_token, &output_token, TRUE, - NULL); + NULL, + nego->cred); /* Free the decoded challenge as it is not required anymore */ Curl_safefree(input_token.value); @@ -191,6 +239,29 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_AUTH_ERROR; } + /* Check if NTLM was selected and is disallowed */ + if(nego->context != GSS_C_NO_CONTEXT) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + OM_uint32 inquire_major, inquire_minor; + gss_OID mech_type = GSS_C_NO_OID; + + inquire_major = gss_inquire_context(&inquire_minor, nego->context, + NULL, NULL, NULL, &mech_type, + NULL, NULL, NULL); + if(!GSS_ERROR(inquire_major) && mech_type && + mech_type->length == ntlmssp_oid.length && + !memcmp(mech_type->elements, ntlmssp_oid.elements, + ntlmssp_oid.length)) { + infof(data, "SPNEGO chose NTLM, but NTLM is not allowed"); + gss_release_buffer(&unused_status, &output_token); + Curl_auth_cleanup_spnego(nego); + return CURLE_AUTH_ERROR; + } + } + /* Free previous token */ if(nego->output_token.length && nego->output_token.value) gss_release_buffer(&unused_status, &nego->output_token); @@ -280,6 +351,12 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->spn = GSS_C_NO_NAME; } + /* Free our credentials */ + if(nego->cred != GSS_C_NO_CREDENTIAL) { + gss_release_cred(&minor_status, &nego->cred); + nego->cred = GSS_C_NO_CREDENTIAL; + } + /* Reset any variables */ nego->status = 0; nego->noauthpersist = FALSE; diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h index 10a02321e3..31aeaed266 100644 --- a/lib/vauth/vauth.h +++ b/lib/vauth/vauth.h @@ -297,6 +297,7 @@ struct negotiatedata { OM_uint32 status; gss_ctx_id_t context; gss_name_t spn; + gss_cred_id_t cred; gss_buffer_desc output_token; #ifdef GSS_C_CHANNEL_BOUND_FLAG struct dynbuf channel_binding_data;