Negotiate package tries Kerberos even when disabled and unsupported
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:
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!
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!
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.
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.
@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):
- Fix the
negotiate_protocolmethod. I believe that @AvivNaaman can do it (if he still wants to contribute). - 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
What should we do now? There are two parts (steps):
Fix the
negotiate_protocolmethod. I believe that @AvivNaaman can do it (if he still wants to contribute).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?
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!
@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
how long do you think it would take to go through this?
@CBenoit I think around 2 days + code review.
@AvivNaaman Sorry for the late answer. I had a day off yesterday. I am going to review your PR today 🙏
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 👍