Skip to content

NTLM-only authentication broken in 0.18.8+ when USE_SESSION_KEY is requested without Kerberos #640

@adrabkin

Description

@adrabkin

NTLM-only authentication broken in sspi 0.18.8+ when USE_SESSION_KEY is requested without Kerberos

Summary

Starting in sspi 0.18.8 (PR #600), consumers that request ClientRequestFlags::USE_SESSION_KEY without providing a target SPN get a hard error during initialize_security_context. In 0.18.7, this worked fine — NTLM negotiation proceeded without requiring an SPN. This is a breaking change that affects any SMB client using NTLM-only authentication.

Additionally, the NTLM-on-IP-address fallback added in PR #637 (sspi 0.18.9) does not fully work — the Kerberos TGT-REQ token is still generated even after the protocol is downgraded to NTLM, causing STATUS_LOGON_FAILURE on non-domain-joined servers.

Environment

  • sspi versions tested: 0.18.7 (works), 0.18.8 (broken), 0.18.9 (broken)
  • SMB client: smb-rs v0.11.1, built without kerberos feature
  • Server: Dell PowerScale (Isilon) OneFS — local user authentication, no Active Directory/Kerberos domain
  • Auth method: NTLM only (auth_methods: { ntlm: true, kerberos: false })
  • Connection target: IP address (DNS resolved before SMB connect, e.g., //10.120.4.12/share)

Reproduction

Minimal setup

Any SMB client using sspi's Negotiate SSP with:

  1. ClientRequestFlags::USE_SESSION_KEY in context requirements
  2. No target_name provided (or target_name provided as an IP-based SPN like cifs/10.120.4.12)
  3. NTLM-only package list (no Kerberos)
  4. Server is a non-domain-joined NAS (OneFS, Samba, etc.) with local user accounts

Test matrix (verified)

All tests: same smb-rs 0.11.1, same server, same credentials, same dataset (53 files), fresh SMB sessions each time.

Test sspi version SPN provided Result
1 0.18.7 No 53/53 success
2 0.18.8 No ❌ 0/53 — NoCredentials: Service target name (service principal name) is not provided
3 0.18.9 No ❌ 0/53 — Same NoCredentials error
4 0.18.9 Yes (cifs/10.120.4.12) ❌ 0/53 — Logon Failure (0xc000006d)

Bug 1: USE_SESSION_KEY requires SPN even for NTLM (0.18.8+)

Location

src/negotiate/client.rs:70-82 (introduced in PR #600):

match negotiate.state {
    NegotiateState::Initial => {
        let sname = if builder
            .context_requirements
            .contains(ClientRequestFlags::USE_SESSION_KEY)
        {
            let (service_name, service_principal_name) =
                parse_target_name(builder.target_name.ok_or_else(|| {
                    Error::new(
                        ErrorKind::NoCredentials,
                        "Service target name (service principal name) is not provided",
                    )
                })?)?;
            Some([service_name, service_principal_name])
        } else {
            None
        };

Problem

USE_SESSION_KEY unconditionally requires target_name to be Some. This is incorrect for NTLM — NTLM always derives a session key during authentication regardless of whether USE_SESSION_KEY is requested. The flag is only meaningful for Kerberos, where the session key comes from the service ticket.

Previous behavior (0.18.7)

In src/negotiate.rs:600+, the initialize_security_context function did not check for USE_SESSION_KEY at all. It processed target_name only for NTLM downgrade detection (IP address check) and proceeded directly to protocol negotiation.

Impact

Any consumer that:

  • Requests USE_SESSION_KEY (common — smb-rs always does)
  • Doesn't provide a target_name (because Kerberos isn't being used)
  • Uses NTLM-only authentication

...gets a hard NoCredentials error on every session setup.

Bug 2: NTLM-on-IP downgrade still generates Kerberos TGT-REQ token (0.18.9)

Location

src/negotiate/client.rs:68-100 and src/negotiate/generators.rs:48-80

Problem

When a consumer provides an IP-based SPN (e.g., cifs/10.120.4.12), the flow is:

  1. check_target_name_for_ntlm_downgrade (called before NegotiateState::Initial) detects IP → sets negotiate.protocol = NegotiatedProtocol::Ntlm(...)
  2. NegotiateState::InitialUSE_SESSION_KEY check → parses SPN into sname = Some(["cifs", "10.120.4.12"])
  3. generate_mech_type_list(matches!(protocol, Kerberos(_)), ntlm) → correctly uses NTLM mech types (because protocol was downgraded) ✅
  4. generate_neg_token_init(sname, mech_types)generates a Kerberos TGT-REQ token because sname is Some

In generators.rs:52-72, when sname is Some, a full Kerberos TgtReq message is constructed:

let krb5_neg_token_init = ApplicationTag0(KrbMessage {
    krb5_oid: ObjectIdentifierAsn1::from(oids::krb5_user_to_user()),
    krb5_token_id: TGT_REQ_TOKEN_ID,
    krb_msg: TgtReq {
        server_name: PrincipalName { name_string: sname },
    },
});

This Kerberos token is embedded in the SPNego NegTokenInit.mech_token field. The server receives an SPNego init that says "I support NTLM" in mech_types but also contains a Kerberos TGT-REQ in mech_token. Non-domain-joined servers (OneFS, Samba with local users) attempt to process the Kerberos token, fail, and return STATUS_LOGON_FAILURE (0xc000006d).

Expected behavior

When the protocol has been downgraded to NTLM (by the IP address check or any other reason), sname should be set to None so that generate_neg_token_init produces a clean NTLM-only SPNego init without a Kerberos mech_token.

Suggested fix

In src/negotiate/client.rs, after the USE_SESSION_KEYsname extraction, check if the protocol is NTLM and clear the sname:

let sname = if builder
    .context_requirements
    .contains(ClientRequestFlags::USE_SESSION_KEY)
{
    // ... existing SPN parsing ...
    Some([service_name, service_principal_name])
} else {
    None
};

// Don't include Kerberos TGT-REQ when protocol has been downgraded to NTLM
let sname = if matches!(&negotiate.protocol, NegotiatedProtocol::Ntlm(_)) {
    None
} else {
    sname
};

Or alternatively, don't require target_name when the negotiated protocol is NTLM — since NTLM doesn't use the SPN for session key derivation, USE_SESSION_KEY should not gate on target_name for NTLM.

Workaround

Pin sspi = "=0.18.7" in Cargo.toml. This is the last version where NTLM-only authentication works without providing an SPN.

Related

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions