]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s4:librpc/rpc: use netr_LogonGetCapabilities query_level=2 to verify the proposed...
authorStefan Metzmacher <metze@samba.org>
Wed, 2 Oct 2024 14:38:53 +0000 (16:38 +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 0b6ac4b082ddec5dae1392537727f3a7123ec279)

source4/librpc/rpc/dcerpc_schannel.c

index a8216c7fde26c56f5949fe549be75a3a79cc1d91..9d96abd2521e13bf9643d30b8cb0fea1dfa337fc 100644 (file)
@@ -40,6 +40,7 @@ struct schannel_key_state {
        bool dcerpc_schannel_auto;
        struct cli_credentials *credentials;
        struct netlogon_creds_CredentialState *creds;
+       uint32_t requested_negotiate_flags;
        uint32_t required_negotiate_flags;
        uint32_t local_negotiate_flags;
        uint32_t remote_negotiate_flags;
@@ -56,7 +57,8 @@ static void continue_secondary_connection(struct composite_context *ctx);
 static void continue_bind_auth_none(struct composite_context *ctx);
 static void continue_srv_challenge(struct tevent_req *subreq);
 static void continue_srv_auth2(struct tevent_req *subreq);
-static void continue_get_capabilities(struct tevent_req *subreq);
+static void continue_get_negotiated_capabilities(struct tevent_req *subreq);
+static void continue_get_client_capabilities(struct tevent_req *subreq);
 
 
 /*
@@ -184,7 +186,7 @@ static void continue_srv_challenge(struct tevent_req *subreq)
        s->a.in.secure_channel_type =
                cli_credentials_get_secure_channel_type(s->credentials);
        s->a.in.computer_name    = cli_credentials_get_workstation(s->credentials);
-       s->a.in.negotiate_flags  = &s->local_negotiate_flags;
+       s->a.in.negotiate_flags  = &s->requested_negotiate_flags;
        s->a.in.credentials      = &s->credentials3;
        s->a.out.negotiate_flags = &s->remote_negotiate_flags;
        s->a.out.return_credentials     = &s->credentials3;
@@ -330,7 +332,22 @@ static void continue_srv_auth2(struct tevent_req *subreq)
                return;
        }
 
-       s->creds->negotiate_flags &= s->remote_negotiate_flags;
+       if (s->requested_negotiate_flags == s->local_negotiate_flags) {
+               /*
+                * Without a downgrade in the crypto we proposed
+                * we can adjust the otherwise downgraded flags
+                * before storing.
+                */
+               s->creds->negotiate_flags &= s->remote_negotiate_flags;
+       } else if (s->local_negotiate_flags != s->remote_negotiate_flags) {
+               /*
+                * We downgraded our crypto once, we should not
+                * allow any additional downgrade!
+                */
+               DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+               composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+               return;
+       }
 
        composite_done(c);
 }
@@ -413,6 +430,8 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
                s->local_negotiate_flags |= NETLOGON_NEG_RODC_PASSTHROUGH;
        }
 
+       s->requested_negotiate_flags = s->local_negotiate_flags;
+
        epm_creds = cli_credentials_init_anon(s);
        if (composite_nomem(epm_creds, c)) return c;
 
@@ -438,7 +457,8 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
  */
 static NTSTATUS dcerpc_schannel_key_recv(struct composite_context *c,
                                TALLOC_CTX *mem_ctx,
-                               struct netlogon_creds_CredentialState **creds)
+                               struct netlogon_creds_CredentialState **creds,
+                               uint32_t *requested_negotiate_flags)
 {
        NTSTATUS status = composite_wait(c);
 
@@ -447,6 +467,7 @@ static NTSTATUS dcerpc_schannel_key_recv(struct composite_context *c,
                        talloc_get_type_abort(c->private_data,
                        struct schannel_key_state);
                *creds = talloc_move(mem_ctx, &s->creds);
+               *requested_negotiate_flags = s->requested_negotiate_flags;
        }
 
        talloc_free(c);
@@ -457,6 +478,7 @@ static NTSTATUS dcerpc_schannel_key_recv(struct composite_context *c,
 struct auth_schannel_state {
        struct dcerpc_pipe *pipe;
        struct cli_credentials *credentials;
+       uint32_t requested_negotiate_flags;
        const struct ndr_interface_table *table;
        struct loadparm_context *lp_ctx;
        uint8_t auth_level;
@@ -465,6 +487,7 @@ struct auth_schannel_state {
        struct netr_Authenticator auth;
        struct netr_Authenticator return_auth;
        union netr_Capabilities capabilities;
+       union netr_Capabilities client_caps;
        struct netr_LogonGetCapabilities c;
        union netr_CONTROL_QUERY_INFORMATION ctrl_info;
 };
@@ -487,7 +510,11 @@ static void continue_schannel_key(struct composite_context *ctx)
        NTSTATUS status;
 
        /* receive schannel key */
-       status = c->status = dcerpc_schannel_key_recv(ctx, s, &s->creds_state);
+       c->status = dcerpc_schannel_key_recv(ctx,
+                                            s,
+                                            &s->creds_state,
+                                            &s->requested_negotiate_flags);
+       status = c->status;
        if (!composite_is_ok(c)) {
                DEBUG(1, ("Failed to setup credentials: %s\n", nt_errstr(status)));
                return;
@@ -556,7 +583,9 @@ static void continue_bind_auth(struct composite_context *ctx)
                                                                 &s->c);
                if (composite_nomem(subreq, c)) return;
 
-               tevent_req_set_callback(subreq, continue_get_capabilities, c);
+               tevent_req_set_callback(subreq,
+                                       continue_get_negotiated_capabilities,
+                                       c);
                return;
        }
 
@@ -568,10 +597,11 @@ static void continue_logon_control_do(struct composite_context *c);
 /*
   Stage 4 of auth_schannel: Get the Logon Capabilities and verify them.
 */
-static void continue_get_capabilities(struct tevent_req *subreq)
+static void continue_get_negotiated_capabilities(struct tevent_req *subreq)
 {
        struct composite_context *c;
        struct auth_schannel_state *s;
+       NTSTATUS status;
 
        c = tevent_req_callback_data(subreq, struct composite_context);
        s = talloc_get_type(c->private_data, struct auth_schannel_state);
@@ -635,7 +665,106 @@ static void continue_get_capabilities(struct tevent_req *subreq)
                return;
        }
 
-       /* TODO: Add downgrade detection. */
+       if ((s->requested_negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) &&
+           (!(s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES)))
+       {
+               DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+               composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+               return;
+       }
+
+       ZERO_STRUCT(s->return_auth);
+
+       s->save_creds_state = *s->creds_state;
+       status = netlogon_creds_client_authenticator(&s->save_creds_state,
+                                                    &s->auth);
+       if (!NT_STATUS_IS_OK(status)) {
+               composite_error(c, status);
+               return;
+       }
+
+       s->c.in.credential            = &s->auth;
+       s->c.in.return_authenticator  = &s->return_auth;
+       s->c.in.query_level           = 2;
+
+       s->c.out.capabilities         = &s->client_caps;
+       s->c.out.return_authenticator = &s->return_auth;
+
+       subreq = dcerpc_netr_LogonGetCapabilities_r_send(s,
+                                                        c->event_ctx,
+                                                        s->pipe->binding_handle,
+                                                        &s->c);
+       if (composite_nomem(subreq, c)) return;
+
+       tevent_req_set_callback(subreq, continue_get_client_capabilities, c);
+       return;
+}
+
+static void continue_get_client_capabilities(struct tevent_req *subreq)
+{
+       struct composite_context *c;
+       struct auth_schannel_state *s;
+
+       c = tevent_req_callback_data(subreq, struct composite_context);
+       s = talloc_get_type(c->private_data, struct auth_schannel_state);
+
+       /* receive rpc request result */
+       c->status = dcerpc_netr_LogonGetCapabilities_r_recv(subreq, s);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_BAD_STUB_DATA)) {
+               /*
+                * unpatched Samba server, see
+                * https://bugzilla.samba.org/show_bug.cgi?id=15418
+                */
+               c->status = NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE;
+       }
+       if (NT_STATUS_EQUAL(c->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...
+                */
+               continue_logon_control_do(c);
+               return;
+       }
+       if (!composite_is_ok(c)) {
+               return;
+       }
+
+       /* verify credentials */
+       if (!netlogon_creds_client_check(&s->save_creds_state,
+                                        &s->c.out.return_authenticator->cred)) {
+               composite_error(c, NT_STATUS_UNSUCCESSFUL);
+               return;
+       }
+
+       if (!NT_STATUS_IS_OK(s->c.out.result)) {
+               composite_error(c, s->c.out.result);
+               return;
+       }
+
+       /* compare capabilities */
+       if (s->requested_negotiate_flags != s->client_caps.requested_flags) {
+               DBG_ERR("The client requested capabilities did not reach"
+                       "the server! local[0x%08X] remote[0x%08X]\n",
+                       s->requested_negotiate_flags,
+                       s->client_caps.requested_flags);
+               composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+               return;
+       }
+
+       *s->creds_state = s->save_creds_state;
+       cli_credentials_set_netlogon_creds(s->credentials, s->creds_state);
 
        composite_done(c);
 }