Skip to content

Fix NTLM DomainName case mismatch breaking FQDN auth (#576)#577

Open
p0dalirius wants to merge 1 commit into
mainfrom
bugfix-ntlm-domain-case
Open

Fix NTLM DomainName case mismatch breaking FQDN auth (#576)#577
p0dalirius wants to merge 1 commit into
mainfrom
bugfix-ntlm-domain-case

Conversation

@p0dalirius

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #576

Root Cause

In CreateAuthenticateMessage (crypto/spnego/ntlm/message/authenticate/authenticate.go), the AUTHENTICATE DomainName field was built with strings.ToUpper(domain), but the NTLMv2 response a few lines below computes NTOWFv2 from the original-case domain (ntlmv2.NewNTLMv2CtxWithPassword(domain, …)). Per MS-NLMP 3.3.2, NTOWFv2 upper-cases only the username, never the domain. The server validates by recomputing NTProofStr using the domain as carried in the DomainName field; because that field was upper-cased while the client's NTProofStr was built from the original case, the two NTProofStr values differed and the server returned STATUS_LOGON_FAILURE (0xc000006d). It only worked when the supplied domain was already all upper-case or empty (where ToUpper is a no-op).

Fix Description

Carry the DomainName field with its original case (drop strings.ToUpper on the domain in both the Unicode and OEM branches) so it matches the domain folded into NTOWFv2. The username field was already case-preserving, and NTOWFv2 still upper-cases the username internally, so the change is limited to the domain. The workstation field is left untouched. The fix lives in shared crypto/spnego, so it benefits both the SMB1 (smb_v10) and SMB2 (smb_v20) clients.

How Verified

  • Runtime (live, Windows Server 2016): before the fix, domain="TMP-W-2016.local" (lower-case FQDN) → LOGON_FAILURE; domain="TMP-W-2016.LOCAL" (upper) and "" → success. After the fix, all of TMP-W-2016.local, tmp-w-2016.local, TMP-W-2016.LOCAL, "", and . authenticate successfully with SMB signing active.
  • Static: the DomainName field now equals utf16.EncodeUTF16LE(domain), identical to the value used to build NTOWFv2, so client and server compute the same NTProofStr.
  • Tests: go build ./..., plus go test ./crypto/... ./network/smb/smb_v10/client/... ./network/smb/smb_v20/client/... all pass.

Test Coverage

  • Added: TestDomainNamePreservesCase in crypto/spnego/ntlm/message/authenticate/authenticate_test.go — asserts the DomainName field of a generated AUTHENTICATE message equals the UTF-16LE encoding of the supplied mixed-case domain (fails against the pre-fix upper-casing).

Scope of Change

  • Files changed: crypto/spnego/ntlm/message/authenticate/authenticate.go, crypto/spnego/ntlm/message/authenticate/authenticate_test.go.
  • Submodule pointer updated: no.
  • Behavioral changes outside the bug fix: none.

Risk and Rollout

Low blast radius: the change only stops upper-casing the NTLM domain. Domains that previously worked were already upper-case or empty, for which the behavior is unchanged; mixed/lower-case domains that previously failed now work. Safe to merge without staged rollout.

CreateAuthenticateMessage upper-cased the AUTHENTICATE DomainName field while
NTOWFv2 folded the domain in with its original case. NTLMv2 upper-cases only the
username (MS-NLMP 3.3.2), so a mixed/lower-case domain (e.g. an FQDN like
host.example.local) produced a DomainName field that disagreed with the domain
used to build NTProofStr, and the server rejected it with STATUS_LOGON_FAILURE.
It only worked when the domain was already upper-case or empty.

Carry DomainName with its original case so it matches the NTOWFv2 input. Shared
by smb_v10 and smb_v20.

Fixes #576
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant