sspi-rs icon indicating copy to clipboard operation
sspi-rs copied to clipboard

Negotiate package tries Kerberos even when disabled and unsupported

Open afiffon opened this issue 6 months ago • 9 comments

Hello,

I've been using the crate for authenticating a client against a remote server. I have started using the Negotiate SSP package, and I have noticed some issues with its implementations, which I'd like to resolve. Most of those issues are relevant for non-domain environments, and it might be a misconfiguration of the provider by me.

The initialization of the provided may be seen here in my crate's source code.

Kerberos is disabled in the options of the SSP

Providing a package_list argument to NegotiateConfig with the value "ntlm,!kerberos,!pku2u", I am still seeing logs of the crate that indicate 3 attempts to perform a KDC detection using DNS(?):

[2025-05-24T14:57:07Z DEBUG smb::session] Setting up session for user LocalAdmin.
[smb/src/session/authenticator.rs:41:18] Self::get_available_ssp_pkgs(&conn_info.config.auth_methods) = "ntlm,!kerberos,!pku2u"
[2025-05-24T14:57:07Z INFO  sspi::negotiate] acquire_credentials_handle_impl; protocol="NTLM"
[2025-05-24T14:57:07Z INFO  sspi::negotiate] negotiate_protocol; username="LocalAdmin" domain="" protocol="NTLM"
[2025-05-24T14:57:07Z INFO  sspi::kdc] detect_kdc_hosts; domain=""
[2025-05-24T14:57:07Z DEBUG sspi::kdc] detect_kdc_hosts_from_system; domain=""
[2025-05-24T14:57:07Z DEBUG sspi::kdc] return=[]
[2025-05-24T14:57:07Z DEBUG sspi::dns] detect_kdc_hosts_from_dns; domain=""
[2025-05-24T14:57:08Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:09Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:09Z DEBUG sspi::dns] return=[]
[2025-05-24T14:57:09Z INFO  sspi::kdc] return=[]
[2025-05-24T14:57:09Z INFO  sspi::negotiate] return=Ok(())
[2025-05-24T14:57:09Z INFO  sspi::negotiate] return=Ok(AcquireCredentialsHandleResult { credentials_handle: Some(AuthIdentity(AuthIdentityBuffers { user: 0x..., domain: 0x, password: Secret })), expiry: None })
[2025-05-24T14:57:09Z INFO  sspi::negotiate] initialize_security_context_impl; protocol="NTLM"
[2025-05-24T14:57:09Z INFO  sspi::negotiate] negotiate_protocol; username="LocalAdmin" domain="" protocol="NTLM"
[2025-05-24T14:57:09Z INFO  sspi::kdc] detect_kdc_hosts; domain=""
[2025-05-24T14:57:09Z DEBUG sspi::kdc] detect_kdc_hosts_from_system; domain=""
[2025-05-24T14:57:09Z DEBUG sspi::kdc] return=[]
[2025-05-24T14:57:09Z DEBUG sspi::dns] detect_kdc_hosts_from_dns; domain=""
[2025-05-24T14:57:10Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:11Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:11Z DEBUG sspi::dns] return=[]
[2025-05-24T14:57:11Z INFO  sspi::kdc] return=[]
[2025-05-24T14:57:11Z INFO  sspi::negotiate] return=Ok(())
[2025-05-24T14:57:11Z INFO  sspi::negotiate] return=Ok(InitializeSecurityContextResult { status: ContinueNeeded, flags: ClientResponseFlags(0x0), expiry: None })
[2025-05-24T14:57:11Z INFO  sspi::negotiate] initialize_security_context_impl; protocol="NTLM"
[2025-05-24T14:57:11Z INFO  sspi::negotiate] negotiate_protocol; username="LocalAdmin" domain="" protocol="NTLM"
[2025-05-24T14:57:11Z INFO  sspi::kdc] detect_kdc_hosts; domain=""
[2025-05-24T14:57:11Z DEBUG sspi::kdc] detect_kdc_hosts_from_system; domain=""
[2025-05-24T14:57:11Z DEBUG sspi::kdc] return=[]
[2025-05-24T14:57:11Z DEBUG sspi::dns] detect_kdc_hosts_from_dns; domain=""
[2025-05-24T14:57:12Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:13Z ERROR sspi::dns] Timeout when reading DNS query error=deadline has elapsed
[2025-05-24T14:57:13Z DEBUG sspi::dns] return=[]
[2025-05-24T14:57:13Z INFO  sspi::kdc] return=[]
[2025-05-24T14:57:13Z INFO  sspi::negotiate] return=Ok(())
[2025-05-24T14:57:13Z INFO  sspi::negotiate] return=Ok(InitializeSecurityContextResult { status: Ok, flags: ClientResponseFlags(0x0), expiry: None })
[2025-05-24T14:57:13Z INFO  sspi::negotiate] query_context_session_key; protocol="NTLM"
[2025-05-24T14:57:13Z DEBUG sspi::ntlm] query_context_session_key; state=Final
[2025-05-24T14:57:13Z DEBUG smb::session::signer] Signature verification passed (signature=...).
[2025-05-24T14:57:13Z DEBUG smb::session::state] Session 30786392686641 flags set: SessionFlags { is_guest: false, is_null_session: false, encrypt_data: false }
[2025-05-24T14:57:13Z INFO  smb::session] Session setup complete.

Since I have disabled kerberos & pku2u completely, I do not expect any kerberos-related logic to be triggered. The most important symptom of this behavior is a major delay in the Negotiation process - using the Ntlm SSP it takes almost no-time, but using Negotiate this time has increased.

Kerberos is not even supported by the client

The entire scenario above is even weirder, looking at the security blob processed by SSPI. The very first buffer passed to SSPI, which is received in the SMB2 Negotiate Protocol, is as follows: Image The buffer is immediately passed to my Authenticator module, which calls SSPI, and it takes a little more than 4 seconds for the next buffer (that the client sends to the server) to be produced by SSPI, and between the next server and client buffers, there's another 2-second gap! Image

Perhaps I'm missing something, but to me, it seems like the correct behavior when the only supported package by the remote is NTLM, and the only enabled package is also NTLM, to avoid using kerberos all along!

I'd love to have your ideas regarding this issue, and again - as in my previous issues, I'd love to contribute to this crate and improve it's behavior, under your guidance, of course.

Thanks!

afiffon avatar May 24 '25 15:05 afiffon

Hi,

Since I have disabled kerberos & pku2u completely, I do not expect any kerberos-related logic to be triggered.

It's weird. The package_list parameter should work. I'll look into it.

The very first buffer passed to SSPI, which is received in the SMB2 Negotiate Protocol, is as follows:

I'm not sure that the sspi-rs pays any attention to them 😅

Perhaps I'm missing something, but to me, it seems like the correct behavior when the only supported package by the remote is NTLM, and the only enabled package is also NTLM, to avoid using kerberos all along!

Yeah, the sspi-rs should not even try to use Kerberos and fall back to NTLM immediately.

I'd love to contribute to this crate and improve it's behavior, under your guidance, of course.

Glad to hear it 😊 I'll investigate your issue a bit and then come back with guidance.

TheBestTvarynka avatar May 24 '25 19:05 TheBestTvarynka

Thanks!

I think it makes some sort of sense to pay attention with which mechanisms are actually enabled (if that's possible), since if the server does not support a mechanism, it makes no sense to try using it (the obvious example is having a non-domain SMB server -- that will never support kerberos)

Let me know if I can help.

afiffon avatar May 31 '25 11:05 afiffon

@AvivNaaman Sorry for the late response. I finally have some time to investigate it.

I think I understood what we missed. SPNEGO messages generation is implemented, but only in the context of the Kerberos protocol. When we use Kerberos, the generated messages look like this: an SPNEGO message with the Kerberos message inside. When we use NTLM, the generated messages are plain NTLM messages.

It means that it doesn't matter what OIDs are listed in in incoming SPNEGO message.

Currently, there is no way to wrap NTLM message into SPNEGO message in sspi-rs 😕 I noticed it some time ago, but didn't have time to refactor it and, previously, it did not cause problems (until you faced it, I guess 🙃).

there's another 2-second gap!

I believe it is the Negotiate package tries to resolve the KDC URL over the DNS.

Since I have disabled kerberos & pku2u completely, I do not expect any kerberos-related logic to be triggered.

This is definitely should be fixed. The problem in the negotiate_protocol method: https://github.com/Devolutions/sspi-rs/blob/99eeaec5582d1661c3e8f62736560515d86ffa36/src/negotiate.rs#L134-L165

It filters the negotiated protocol after trying to resolve the KDC URL. But instead, we should try to resolve the KDC host exclusively after checking the package list.


What should we do now? There are two parts (steps):

  1. Fix the negotiate_protocol method. I believe that @AvivNaaman can do it (if he still wants to contribute).
  2. Refactor SPNEGO and Kerberos messages generation. I was thinking about it for some time. The best way to fix it is to move the SPNEGO messages generation to the Negotiate security package. Thus, the Kerberos package will return exclusively Kerberos messages (TgtReq, ApReq, etc) and the Negotiate package will wrap them into SPNEGO messages. After that, we will be able to implement a proper authentication protocol negotiation based on the incoming OIDs.

cc @CBenoit

TheBestTvarynka avatar Jun 05 '25 10:06 TheBestTvarynka

What should we do now? There are two parts (steps):

  1. Fix the negotiate_protocol method. I believe that @AvivNaaman can do it (if he still wants to contribute).

  2. Refactor SPNEGO and Kerberos messages generation. I was thinking about it for some time. The best way to fix it is to move the SPNEGO messages generation to the Negotiate security package. Thus, the Kerberos package will return exclusively Kerberos messages (TgtReq, ApReq, etc) and the Negotiate package will wrap them into SPNEGO messages. After that, we will be able to implement a proper authentication protocol negotiation based on the incoming OIDs.

I’ve read a bit through this. It seems to me you know what you are doing, and this refactor would be welcomed to avoid some bugs and help future evolution of sspi-rs. Maybe you could do this along the work for system-provided smartcards? I’m mentioning this to @awakecoding on Slack. Just for information, how long do you think it would take to go through this?

CBenoit avatar Jun 06 '25 05:06 CBenoit

Hi @TheBestTvarynka,

I'll be happy to help by fixing the bug in the negotiation evaluation (basically the one that makes Kerberos execute despite being disabled).

Thanks for yours and @CBenoit's help - it is much appreciated!

afiffon avatar Jun 08 '25 17:06 afiffon

@TheBestTvarynka I have PRd a simple fix for the issue of having kerberos disabled and KDC detection still being attempted https://github.com/Devolutions/sspi-rs/pull/447

afiffon avatar Jun 10 '25 05:06 afiffon

how long do you think it would take to go through this?

@CBenoit I think around 2 days + code review.

TheBestTvarynka avatar Jun 10 '25 07:06 TheBestTvarynka

@AvivNaaman Sorry for the late answer. I had a day off yesterday. I am going to review your PR today 🙏

TheBestTvarynka avatar Jun 10 '25 07:06 TheBestTvarynka

how long do you think it would take to go through this?

I think around 2 days + code review.

Sounds good! You can go ahead with this 👍

CBenoit avatar Jun 10 '25 09:06 CBenoit