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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions