]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
libcli/auth: use netr_LogonGetCapabilities query_level=2 to verify the proposed capab...
authorStefan Metzmacher <metze@samba.org>
Wed, 2 Oct 2024 11:43:36 +0000 (13:43 +0200)
committerJule Anger <janger@samba.org>
Wed, 13 Nov 2024 10:39:11 +0000 (10:39 +0000)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15425

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit 25a2105ca7816c47a9c4a7fded88a922e4ccf88b)

libcli/auth/netlogon_creds_cli.c

index 935697be0096f7f7ed0c28fdfadf12fcc4682291..40a40cb99aede4353054d3d5db149bef4cdfa0e6 100644 (file)
@@ -1215,6 +1215,7 @@ struct netlogon_creds_cli_auth_state {
        struct netlogon_creds_CredentialState *creds;
        struct netr_Credential client_credential;
        struct netr_Credential server_credential;
+       uint32_t negotiate_flags;
        uint32_t rid;
        bool try_auth3;
        bool try_auth2;
@@ -1360,6 +1361,17 @@ static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq)
        }
 
        if (state->try_auth3) {
+               /*
+                * We always need to send our proposed flags,
+                * even if state->current_flags we passed to
+                * netlogon_creds_client_init() is already downgraded,
+                *
+                * An old server will just ignore the bits it doesn't
+                * know about, but LogonGetCapabilities(level=2) will
+                * report what we proposed.
+                */
+               state->negotiate_flags = state->context->client.proposed_flags;
+
                subreq = dcerpc_netr_ServerAuthenticate3_send(state, state->ev,
                                                state->binding_handle,
                                                state->srv_name_slash,
@@ -1368,12 +1380,22 @@ static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq)
                                                state->context->client.computer,
                                                &state->client_credential,
                                                &state->server_credential,
-                                               &state->creds->negotiate_flags,
+                                               &state->negotiate_flags,
                                                &state->rid);
                if (tevent_req_nomem(subreq, req)) {
                        return;
                }
        } else if (state->try_auth2) {
+               /*
+                * We always need to send our proposed flags,
+                * even if state->current_flags we passed to
+                * netlogon_creds_client_init() is already downgraded,
+                *
+                * An old server will just ignore the bits it doesn't
+                * know about, but LogonGetCapabilities(level=2) will
+                * report what we proposed.
+                */
+               state->negotiate_flags = state->context->client.proposed_flags;
                state->rid = 0;
 
                subreq = dcerpc_netr_ServerAuthenticate2_send(state, state->ev,
@@ -1384,11 +1406,12 @@ static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq)
                                                state->context->client.computer,
                                                &state->client_credential,
                                                &state->server_credential,
-                                               &state->creds->negotiate_flags);
+                                               &state->negotiate_flags);
                if (tevent_req_nomem(subreq, req)) {
                        return;
                }
        } else {
+               state->negotiate_flags = 0;
                state->rid = 0;
 
                subreq = dcerpc_netr_ServerAuthenticate_send(state, state->ev,
@@ -1467,7 +1490,7 @@ static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq)
        }
 
        downgraded = netlogon_creds_cli_downgraded(
-                       state->creds->negotiate_flags,
+                       state->negotiate_flags,
                        state->context->client.proposed_flags,
                        state->context->client.required_flags);
        if (downgraded) {
@@ -1482,7 +1505,7 @@ static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq)
        if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
                uint32_t prop_f = state->context->client.proposed_flags;
                uint32_t cli_f = state->current_flags;
-               uint32_t srv_f = state->creds->negotiate_flags;
+               uint32_t srv_f = state->negotiate_flags;
                uint32_t nego_f = cli_f & srv_f;
 
                if (cli_f == prop_f && nego_f != prop_f) {
@@ -1519,6 +1542,22 @@ static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq)
                return;
        }
 
+       if (state->current_flags == state->context->client.proposed_flags) {
+               /*
+                * Without a downgrade in the crypto we proposed
+                * we can adjust the otherwise downgraded flags
+                * before storing.
+                */
+               state->creds->negotiate_flags &= state->negotiate_flags;
+       } else if (state->current_flags != state->negotiate_flags) {
+               /*
+                * We downgraded our crypto once, we should not
+                * allow any additional downgrade!
+                */
+               tevent_req_nterror(req, NT_STATUS_DOWNGRADE_DETECTED);
+               return;
+       }
+
        status = netlogon_creds_cli_store_internal(state->context,
                                                   state->creds);
        if (tevent_req_nterror(req, status)) {
@@ -1587,6 +1626,7 @@ struct netlogon_creds_cli_check_state {
        char *srv_name_slash;
 
        union netr_Capabilities caps;
+       union netr_Capabilities client_caps;
 
        struct netlogon_creds_CredentialState *creds;
        struct netr_Authenticator req_auth;
@@ -1597,7 +1637,8 @@ struct netlogon_creds_cli_check_state {
 
 static void netlogon_creds_cli_check_cleanup(struct tevent_req *req,
                                             NTSTATUS status);
-static void netlogon_creds_cli_check_caps(struct tevent_req *subreq);
+static void netlogon_creds_cli_check_negotiate_caps(struct tevent_req *subreq);
+static void netlogon_creds_cli_check_client_caps(struct tevent_req *subreq);
 
 struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx,
                                struct tevent_context *ev,
@@ -1681,7 +1722,7 @@ struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx,
        }
 
        tevent_req_set_callback(subreq,
-                               netlogon_creds_cli_check_caps,
+                               netlogon_creds_cli_check_negotiate_caps,
                                req);
 
        return req;
@@ -1713,7 +1754,7 @@ static void netlogon_creds_cli_check_cleanup(struct tevent_req *req,
 
 static void netlogon_creds_cli_check_control_do(struct tevent_req *req);
 
-static void netlogon_creds_cli_check_caps(struct tevent_req *subreq)
+static void netlogon_creds_cli_check_negotiate_caps(struct tevent_req *subreq)
 {
        struct tevent_req *req =
                tevent_req_callback_data(subreq,
@@ -1856,6 +1897,110 @@ static void netlogon_creds_cli_check_caps(struct tevent_req *subreq)
                return;
        }
 
+       /*
+        * Now try to verify our client proposed flags
+        * arrived at the server, using query_level = 2
+        */
+
+       status = netlogon_creds_client_authenticator(state->creds,
+                                                    &state->req_auth);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       ZERO_STRUCT(state->rep_auth);
+
+       subreq = dcerpc_netr_LogonGetCapabilities_send(state, state->ev,
+                                               state->binding_handle,
+                                               state->srv_name_slash,
+                                               state->context->client.computer,
+                                               &state->req_auth,
+                                               &state->rep_auth,
+                                               2,
+                                               &state->client_caps);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+
+       tevent_req_set_callback(subreq,
+                               netlogon_creds_cli_check_client_caps,
+                               req);
+       return;
+}
+
+static void netlogon_creds_cli_check_client_caps(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct netlogon_creds_cli_check_state *state =
+               tevent_req_data(req,
+               struct netlogon_creds_cli_check_state);
+       NTSTATUS status;
+       NTSTATUS result;
+       bool ok;
+
+       status = dcerpc_netr_LogonGetCapabilities_recv(subreq, state,
+                                                      &result);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_BAD_STUB_DATA)) {
+               /*
+                * unpatched Samba server, see
+                * https://bugzilla.samba.org/show_bug.cgi?id=15418
+                */
+               status = NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE;
+       }
+       if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
+               /*
+                * Here we know the negotiated flags were already
+                * verified with query_level=1, which means
+                * the server supported NETLOGON_NEG_SUPPORTS_AES
+                * and also NETLOGON_NEG_AUTHENTICATED_RPC
+                *
+                * As we're using DCERPC_AUTH_TYPE_SCHANNEL with
+                * DCERPC_AUTH_LEVEL_INTEGRITY or DCERPC_AUTH_LEVEL_PRIVACY
+                * we should detect a faked
+                * NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE
+                * with the next request as the sequence number processing
+                * gets out of sync.
+                *
+                * So we'll do a LogonControl message to check that...
+                */
+               netlogon_creds_cli_check_control_do(req);
+               return;
+       }
+       if (tevent_req_nterror(req, status)) {
+               netlogon_creds_cli_check_cleanup(req, status);
+               return;
+       }
+
+       ok = netlogon_creds_client_check(state->creds,
+                                        &state->rep_auth.cred);
+       if (!ok) {
+               status = NT_STATUS_ACCESS_DENIED;
+               tevent_req_nterror(req, status);
+               netlogon_creds_cli_check_cleanup(req, status);
+               return;
+       }
+       if (tevent_req_nterror(req, result)) {
+               netlogon_creds_cli_check_cleanup(req, result);
+               return;
+       }
+
+       if (state->client_caps.requested_flags !=
+           state->context->client.proposed_flags)
+       {
+               status = NT_STATUS_DOWNGRADE_DETECTED;
+               tevent_req_nterror(req, status);
+               netlogon_creds_cli_check_cleanup(req, status);
+               return;
+       }
+
+       status = netlogon_creds_cli_store_internal(state->context,
+                                                  state->creds);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
        tevent_req_done(req);
 }