CVE-2016-2110: auth/gensec: require spnego mechListMIC exchange for new_spnego backends
authorStefan Metzmacher <metze@samba.org>
Tue, 24 Nov 2015 19:13:24 +0000 (20:13 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 12 Apr 2016 17:25:23 +0000 (19:25 +0200)
This used to work more or less before, but only for krb5 with the
server finishing first.

With NTLMSSP and new_spnego the client will finish first.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11644

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Günther Deschner <gd@samba.org>
auth/gensec/spnego.c

index ea2430804337f9ad51acbceaaab64b4e16d28058..f47221a212829770243976ad37708395f5c920da 100644 (file)
@@ -53,6 +53,11 @@ struct spnego_state {
        const char *neg_oid;
 
        DATA_BLOB mech_types;
+       size_t num_targs;
+       bool mic_requested;
+       bool needs_mic_sign;
+       bool needs_mic_check;
+       bool done_mic_check;
 
        /*
         * The following is used to implement
@@ -416,6 +421,11 @@ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_
                                        spnego_state->neg_oid = all_sec[i].oid;
                                        *unwrapped_out = data_blob_null;
                                        nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+                                       /*
+                                        * Indicate the downgrade and request a
+                                        * mic.
+                                        */
+                                       spnego_state->mic_requested = true;
                                        break;
                                }
 
@@ -674,22 +684,27 @@ static NTSTATUS gensec_spnego_server_negTokenTarg(struct spnego_state *spnego_st
        /* compose reply */
        spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
        spnego_out.negTokenTarg.responseToken = unwrapped_out;
-       spnego_out.negTokenTarg.mechListMIC = null_data_blob;
+       spnego_out.negTokenTarg.mechListMIC = mech_list_mic;
        spnego_out.negTokenTarg.supportedMech = NULL;
 
        if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {   
                spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid;
-               spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
+               if (spnego_state->mic_requested) {
+                       spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC;
+                       spnego_state->mic_requested = false;
+               } else {
+                       spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
+               }
                spnego_state->state_position = SPNEGO_SERVER_TARG;
        } else if (NT_STATUS_IS_OK(nt_status)) {
                if (unwrapped_out.data) {
                        spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid;
                }
                spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
-               spnego_out.negTokenTarg.mechListMIC = mech_list_mic;
                spnego_state->state_position = SPNEGO_DONE;
        } else {
                spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
+               spnego_out.negTokenTarg.mechListMIC = null_data_blob;
                DEBUG(2, ("SPNEGO login failed: %s\n", nt_errstr(nt_status)));
                spnego_state->state_position = SPNEGO_DONE;
        }
@@ -700,6 +715,7 @@ static NTSTATUS gensec_spnego_server_negTokenTarg(struct spnego_state *spnego_st
        }
 
        spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
+       spnego_state->num_targs++;
 
        return nt_status;
 }
@@ -892,18 +908,57 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        return NT_STATUS_INVALID_PARAMETER;
                }
 
+               spnego_state->num_targs++;
+
                if (!spnego_state->sub_sec_security) {
                        DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"));
                        spnego_free_data(&spnego);
                        return NT_STATUS_INVALID_PARAMETER;
                }
 
+               if (spnego_state->needs_mic_check) {
+                       if (spnego.negTokenTarg.responseToken.length != 0) {
+                               DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"));
+                               spnego_free_data(&spnego);
+                               return NT_STATUS_INVALID_PARAMETER;
+                       }
+
+                       nt_status = gensec_check_packet(spnego_state->sub_sec_security,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       &spnego.negTokenTarg.mechListMIC);
+                       if (NT_STATUS_IS_OK(nt_status)) {
+                               spnego_state->needs_mic_check = false;
+                               spnego_state->done_mic_check = true;
+                       } else {
+                               DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
+                                       nt_errstr(nt_status)));
+                       }
+                       goto server_response;
+               }
+
                nt_status = gensec_update_ev(spnego_state->sub_sec_security,
-                                         out_mem_ctx, ev,
-                                         spnego.negTokenTarg.responseToken,
-                                         &unwrapped_out);
-               if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
+                                            out_mem_ctx, ev,
+                                            spnego.negTokenTarg.responseToken,
+                                            &unwrapped_out);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       goto server_response;
+               }
+
+               new_spnego = gensec_have_feature(spnego_state->sub_sec_security,
+                                                GENSEC_FEATURE_NEW_SPNEGO);
+               if (spnego.negTokenTarg.mechListMIC.length > 0) {
                        new_spnego = true;
+               }
+
+               if (new_spnego) {
+                       spnego_state->needs_mic_check = true;
+                       spnego_state->needs_mic_sign = true;
+               }
+
+               if (spnego.negTokenTarg.mechListMIC.length > 0) {
                        nt_status = gensec_check_packet(spnego_state->sub_sec_security,
                                                        spnego_state->mech_types.data,
                                                        spnego_state->mech_types.length,
@@ -913,9 +968,14 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        if (!NT_STATUS_IS_OK(nt_status)) {
                                DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
                                        nt_errstr(nt_status)));
+                               goto server_response;
                        }
+
+                       spnego_state->needs_mic_check = false;
+                       spnego_state->done_mic_check = true;
                }
-               if (NT_STATUS_IS_OK(nt_status) && new_spnego) {
+
+               if (spnego_state->needs_mic_sign) {
                        nt_status = gensec_sign_packet(spnego_state->sub_sec_security,
                                                       out_mem_ctx,
                                                       spnego_state->mech_types.data,
@@ -926,9 +986,16 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        if (!NT_STATUS_IS_OK(nt_status)) {
                                DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n",
                                        nt_errstr(nt_status)));
+                               goto server_response;
                        }
+                       spnego_state->needs_mic_sign = false;
                }
 
+               if (spnego_state->needs_mic_check) {
+                       nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+               }
+
+ server_response:
                nt_status = gensec_spnego_server_negTokenTarg(spnego_state,
                                                              out_mem_ctx, 
                                                              nt_status,
@@ -942,7 +1009,8 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
        }
        case SPNEGO_CLIENT_TARG:
        {
-               NTSTATUS nt_status;
+               NTSTATUS nt_status = NT_STATUS_INTERNAL_ERROR;
+
                if (!in.length) {
                        return NT_STATUS_INVALID_PARAMETER;
                }
@@ -964,11 +1032,17 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                        return NT_STATUS_INVALID_PARAMETER;
                }
 
+               spnego_state->num_targs++;
+
                if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
                        spnego_free_data(&spnego);
                        return NT_STATUS_LOGON_FAILURE;
                }
 
+               if (spnego.negTokenTarg.negResult == SPNEGO_REQUEST_MIC) {
+                       spnego_state->mic_requested = true;
+               }
+
                /* Server didn't like our choice of mech, and chose something else */
                if (((spnego.negTokenTarg.negResult == SPNEGO_ACCEPT_INCOMPLETE) ||
                     (spnego.negTokenTarg.negResult == SPNEGO_REQUEST_MIC)) &&
@@ -995,64 +1069,143 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                                return nt_status;
                        }
 
-                       nt_status = gensec_update_ev(spnego_state->sub_sec_security,
-                                                 out_mem_ctx, ev,
-                                                 spnego.negTokenTarg.responseToken,
-                                                 &unwrapped_out);
-                       spnego_state->neg_oid = talloc_strdup(spnego_state, spnego.negTokenTarg.supportedMech);
-               } else if (spnego_state->no_response_expected) {
-                       if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) {
-                               DEBUG(3,("GENSEC SPNEGO: client GENSEC accepted, but server rejected (bad password?)\n"));
-                               nt_status = NT_STATUS_INVALID_PARAMETER;
-                       } else if (spnego.negTokenTarg.responseToken.length) {
-                               DEBUG(2,("GENSEC SPNEGO: client GENSEC accepted, but server continued negotiation!\n"));
-                               nt_status = NT_STATUS_INVALID_PARAMETER;
-                       } else {
-                               nt_status = NT_STATUS_OK;
+                       spnego_state->neg_oid = talloc_strdup(spnego_state,
+                                               spnego.negTokenTarg.supportedMech);
+                       if (spnego_state->neg_oid == NULL) {
+                               spnego_free_data(&spnego);
+                               return NT_STATUS_NO_MEMORY;
+                       };
+               }
+
+               if (spnego.negTokenTarg.mechListMIC.length > 0) {
+                       if (spnego_state->no_response_expected) {
+                               spnego_state->needs_mic_check = true;
                        }
-                       if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
-                               nt_status = gensec_check_packet(spnego_state->sub_sec_security,
-                                                               spnego_state->mech_types.data,
-                                                               spnego_state->mech_types.length,
-                                                               spnego_state->mech_types.data,
-                                                               spnego_state->mech_types.length,
-                                                               &spnego.negTokenTarg.mechListMIC);
-                               if (!NT_STATUS_IS_OK(nt_status)) {
-                                       DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
-                                               nt_errstr(nt_status)));
-                               }
+               }
+
+               if (spnego_state->needs_mic_check) {
+                       if (spnego.negTokenTarg.responseToken.length != 0) {
+                               DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"));
+                               spnego_free_data(&spnego);
+                               return NT_STATUS_INVALID_PARAMETER;
                        }
-               } else {
-                       bool new_spnego = false;
 
+                       nt_status = gensec_check_packet(spnego_state->sub_sec_security,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       &spnego.negTokenTarg.mechListMIC);
+                       if (!NT_STATUS_IS_OK(nt_status)) {
+                               DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
+                                       nt_errstr(nt_status)));
+                               spnego_free_data(&spnego);
+                               return nt_status;
+                       }
+                       spnego_state->needs_mic_check = false;
+                       spnego_state->done_mic_check = true;
+                       goto client_response;
+               }
+
+               if (!spnego_state->no_response_expected) {
                        nt_status = gensec_update_ev(spnego_state->sub_sec_security,
                                                  out_mem_ctx, ev,
                                                  spnego.negTokenTarg.responseToken, 
                                                  &unwrapped_out);
+                       if (!NT_STATUS_IS_OK(nt_status)) {
+                               goto client_response;
+                       }
+
+                       spnego_state->no_response_expected = true;
+               } else {
+                       nt_status = NT_STATUS_OK;
+               }
+
+               if (spnego_state->no_response_expected &&
+                   !spnego_state->done_mic_check)
+               {
+                       bool new_spnego = false;
+
+                       new_spnego = gensec_have_feature(spnego_state->sub_sec_security,
+                                                        GENSEC_FEATURE_NEW_SPNEGO);
+
+                       switch (spnego.negTokenTarg.negResult) {
+                       case SPNEGO_ACCEPT_COMPLETED:
+                       case SPNEGO_NONE_RESULT:
+                               if (spnego_state->num_targs == 1) {
+                                       /*
+                                        * the first exchange doesn't require
+                                        * verification
+                                        */
+                                       new_spnego = false;
+                               }
+                               break;
 
-                       if (NT_STATUS_IS_OK(nt_status)
-                           && spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) {
-                               new_spnego = gensec_have_feature(spnego_state->sub_sec_security,
-                                                                GENSEC_FEATURE_NEW_SPNEGO);
+                       case SPNEGO_ACCEPT_INCOMPLETE:
+                       case SPNEGO_REQUEST_MIC:
+                               if (spnego.negTokenTarg.mechListMIC.length > 0) {
+                                       new_spnego = true;
+                               }
+                               break;
+                       default:
+                               break;
                        }
-                       if (NT_STATUS_IS_OK(nt_status) && new_spnego) {
-                               nt_status = gensec_sign_packet(spnego_state->sub_sec_security,
-                                                              out_mem_ctx,
-                                                              spnego_state->mech_types.data,
-                                                              spnego_state->mech_types.length,
-                                                              spnego_state->mech_types.data,
-                                                              spnego_state->mech_types.length,
-                                                              &mech_list_mic);
-                               if (!NT_STATUS_IS_OK(nt_status)) {
-                                       DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n",
-                                               nt_errstr(nt_status)));
+
+                       if (spnego_state->mic_requested) {
+                               bool sign;
+
+                               sign = gensec_have_feature(spnego_state->sub_sec_security,
+                                                          GENSEC_FEATURE_SIGN);
+                               if (sign) {
+                                       new_spnego = true;
                                }
                        }
-                       if (NT_STATUS_IS_OK(nt_status)) {
-                               spnego_state->no_response_expected = true;
+
+                       if (new_spnego) {
+                               spnego_state->needs_mic_check = true;
+                               spnego_state->needs_mic_sign = true;
+                       }
+               }
+
+               if (spnego.negTokenTarg.mechListMIC.length > 0) {
+                       nt_status = gensec_check_packet(spnego_state->sub_sec_security,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       spnego_state->mech_types.data,
+                                                       spnego_state->mech_types.length,
+                                                       &spnego.negTokenTarg.mechListMIC);
+                       if (!NT_STATUS_IS_OK(nt_status)) {
+                               DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n",
+                                       nt_errstr(nt_status)));
+                               spnego_free_data(&spnego);
+                               return nt_status;
+                       }
+                       spnego_state->needs_mic_check = false;
+                       spnego_state->done_mic_check = true;
+               }
+
+               if (spnego_state->needs_mic_sign) {
+                       nt_status = gensec_sign_packet(spnego_state->sub_sec_security,
+                                                      out_mem_ctx,
+                                                      spnego_state->mech_types.data,
+                                                      spnego_state->mech_types.length,
+                                                      spnego_state->mech_types.data,
+                                                      spnego_state->mech_types.length,
+                                                      &mech_list_mic);
+                       if (!NT_STATUS_IS_OK(nt_status)) {
+                               DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n",
+                                       nt_errstr(nt_status)));
+                               spnego_free_data(&spnego);
+                               return nt_status;
                        }
-               } 
+                       spnego_state->needs_mic_sign = false;
+               }
+
+               if (spnego_state->needs_mic_check) {
+                       nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+               }
 
+ client_response:
                spnego_free_data(&spnego);
 
                if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
@@ -1076,6 +1229,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
                                return NT_STATUS_INVALID_PARAMETER;
                        }
 
+                       spnego_state->num_targs++;
                        spnego_state->state_position = SPNEGO_CLIENT_TARG;
                        nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
                } else {