Skip to content

Pick the mechanism from the server's preference, not the client's - #309

Open
Pushpenderrathore wants to merge 1 commit into
rapid7:masterfrom
Pushpenderrathore:fix/multi-provider-server-mech-preference
Open

Pushpenderrathore wants to merge 1 commit into
rapid7:masterfrom
Pushpenderrathore:fix/multi-provider-server-mech-preference

Conversation

@Pushpenderrathore

Copy link
Copy Markdown
Contributor

Description

Addresses #304. Provider::Multi::Authenticator#process routed each NegTokenInit by reading only the client's first-listed mechanism, so a client (or an on-path attacker rewriting the mechTypeList before signing was in effect) that reordered the list could steer the server to a weaker sub-provider without removing any mechanism. On a server built with Multi.new([kerberos_provider, ntlm_provider]), a client-asserted [NTLM, Kerberos] was routed to NTLM even though both sides supported Kerberos.

Walk the full mechTypeList, pick the server's most-preferred advertised mechanism that the client also offers, and route to that. When that choice differs from the mechanism the client's optimistic mechToken was built for, reply with a NegTokenResp carrying accept-incomplete and supportedMech per RFC 4178 section 4.2.2 so the client resends a token for the server-selected mechanism.

What this is and is not

This is server-side routing hardening that prevents client ordering from forcing a weaker common mechanism. It is not a replacement for a mechListMIC verifier: an on-path attacker who rewrites the client's mechTypeList before signing is in effect can still drop mechanisms the client would otherwise have offered. Full integrity for the negotiated mechanism list requires computing and verifying the mechListMIC (RFC 4178 §5.1), which is tracked separately on #304 and left for a follow-up.

Wire-level demonstration

A localhost MitM harness sends [Kerberos, NTLM] from the client to a transparent TCP proxy, which rewrites the mechTypeList to [NTLM, Kerberos] and replaces the Kerberos optimistic mechToken with an NTLM Type 1 message, then forwards the resulting SMB2 SessionSetup to a Multi(Kerberos, NTLM) ruby_smb server on the other side.

Same bytes on the wire in both runs. Only lib/ruby_smb/gss/provider/multi.rb is swapped between runs:

Before (upstream/master) After (this branch)
MitM-rewritten SessionSetup 105 bytes 105 bytes
Server-selected mechanism NTLMSSP 1.3.6.1.4.1.311.2.2.10 Kerberos 1.2.840.113554.1.2.2
Reply buffer 217 B NTLM Type 2 challenge 22 B NegTokenResp accept-incomplete, supportedMech=Kerberos
nt_status STATUS_MORE_PROCESSING_REQUIRED (0xC0000016) STATUS_MORE_PROCESSING_REQUIRED (0xC0000016)
Routing outcome downgrade succeeds downgrade refused

Before (upstream):

[INFO] mitm: rewriting [Kerberos, NTLM] -> [NTLM, Kerberos], replacing mechToken with NTLM Type 1
[INFO] target server: SessionSetup security_blob 105 bytes
[INFO] target server: authenticator result nt_status=0xc0000016 buffer=217B
[INFO] target server: supportedMech=1.3.6.1.4.1.311.2.2.10   # NTLMSSP

After (this branch):

[INFO] mitm: rewriting [Kerberos, NTLM] -> [NTLM, Kerberos], replacing mechToken with NTLM Type 1
[INFO] target server: SessionSetup security_blob 105 bytes
[INFO] SPNEGO: client listed 1.3.6.1.4.1.311.2.2.10 first; server prefers 1.2.840.113554.1.2.2, requesting a token for it
[INFO] target server: authenticator result nt_status=0xc0000016 buffer=22B
[INFO] target server: supportedMech=1.2.840.113554.1.2.2   # Kerberos

Routing matrix

With server advertisement [Kerberos, NTLM]:

Client mechTypeList Server chooses Response
[Kerberos, NTLM] Kerberos Routes mechToken straight to Kerberos sub-provider
[NTLM, Kerberos] Kerberos accept-incomplete + supportedMech = Kerberos, awaits new token
[NTLM] NTLM Routes mechToken to NTLM sub-provider
[Kerberos] Kerberos Routes mechToken to Kerberos sub-provider
[NEGOEX] (no overlap) none Returns nil (same as prior behaviour for unsupported)
[] (empty) none Returns nil, logged

Continuation after accept-incomplete

Covered by a new spec: client lists [NTLM, Kerberos], server responds accept-incomplete naming Kerberos, client re-sends a NegTokenResp carrying a Kerberos token, which is dispatched to the Kerberos sub-provider's on_mech_token as if it had been named first.

Verification Steps

  • bundle exec rspec spec/lib/ruby_smb/gss/provider/multi_spec.rb passes with the matrix + continuation specs added
  • Offline ASN.1 PoC confirms client_mech_oids returns the exact OID strings in client-listed order for [Kerberos, NTLM], [NTLM, Kerberos], [NTLM], [Kerberos]
  • Localhost MitM harness: swapping only multi.rb between runs reproduces the downgrade on upstream/master and refuses it on this branch (output above)
  • Existing single-mechanism tests continue to pass (the helper gss_init now delegates to gss_init_list so previous call sites are byte-identical)

Test Evidence

New describe blocks in spec/lib/ruby_smb/gss/provider/multi_spec.rb:

  • #process when a client offers more than one mechanism covers the six matrix rows above.
  • #process after replying accept-incomplete to a reordered client exercises the two-leg continuation.

Related

Provider::Multi::Authenticator#process read only the client's first-listed
mechanism when routing a NegTokenInit. That handed routing to the client:
reordering a mechTypeList, without removing any mechanism, was enough to
steer the server to a weaker sub-provider (NTLM when both sides supported
Kerberos). The gap was tracked in rapid7#304.

Walk the client's full mechTypeList, pick the server's most-preferred
advertised mechanism that the client also offers, and route to that. When
the server's choice is not the mechanism the client's optimistic
mechToken is for, reply with a NegTokenResp carrying accept-incomplete
and supportedMech, per RFC 4178 section 4.2.2, so the client resends a
token for the server-selected mechanism.

This is server-side routing hardening, not a replacement for a mechListMIC
verifier. An on-path attacker who rewrites the mechTypeList before signing
is in effect is still able to drop mechanisms the client offered; stopping
that requires computing and verifying the mechListMIC, which is tracked
separately on the issue.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant