Skip to content

Validate the SPNEGO NegTokenResp before parsing an NTLM Type 3 - #306

Open
Pushpenderrathore wants to merge 1 commit into
rapid7:masterfrom
Pushpenderrathore:fix/ntlm-provider-malformed-neg-token-resp
Open

Pushpenderrathore wants to merge 1 commit into
rapid7:masterfrom
Pushpenderrathore:fix/ntlm-provider-malformed-neg-token-resp

Conversation

@Pushpenderrathore

@Pushpenderrathore Pushpenderrathore commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

Gss::Provider::NTLM::Authenticator#process_gss_type3 assumed every SPNEGO NegTokenResp carries a tag-[2] response_token and that each tagged element wraps exactly one child. RFC 4178 section 4.2.2 marks every NegTokenResp field OPTIONAL, so this is not true. A legitimate NegTokenResp carrying only neg_result or only mech_list_mic, or one with an empty-constructed tag [2], raised a NoMethodError that escaped the server-client thread and dropped the connection with no SMB reply.

Walk the inner sequence with a nil-safe accessor, bail out when the response_token is absent, and wrap the Net::NTLM parse so bytes that are not a valid Type 3 message produce a logged error and a nil return instead of killing the thread.

Lab PoC

Reproducer handed to RubySMB::Gss::Provider::NTLM::Authenticator#process with four DER-valid NegTokenResp shapes.

Before (upstream/master at b303067):

[CRASH] empty inner sequence:              NoMethodError: undefined method 'size'  for nil
[CRASH] only mech_list_mic [3]:            NoMethodError: undefined method 'size'  for nil
[CRASH] response_token [2] with garbage:   ArgumentError: unknown type: 1954112045
[CRASH] empty-constructed response_token:  NoMethodError: undefined method 'value' for nil

After (this branch):

[OK]    empty inner sequence:              nil
[OK]    only mech_list_mic [3]:            nil
[OK]    response_token [2] with garbage:   nil
[OK]    empty-constructed response_token:  nil

Verification Steps

  • bundle exec rspec spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb passes locally (22 examples in the Authenticator spec, 0 failures)
  • bundle exec rspec spec/lib/ruby_smb/gss/ passes locally (103 examples, 0 failures)
  • Reproducer against the live Authenticator#process returns nil for all four malformed-NegTokenResp shapes shown in Lab PoC (upstream crashed on each)

Test Evidence

103 examples in spec/lib/ruby_smb/gss/, 0 failures. New context #process when the NegTokenResp is malformed covers five shapes: empty inner sequence, response_token absent, mech_list_mic only, empty response_token, non-NTLM response_token.

RFC 4178 section 4.2.2 marks every NegTokenResp field OPTIONAL, so a
response may legitimately omit the response_token or wrap it with an
empty value. The previous flow assumed a tag-[2] field was always
present and that every tagged element wrapped exactly one child, so
a token missing the response_token, a token carrying only a
mech_list_mic, or a tag [2] with no value raised a NoMethodError that
escaped the server-client thread and dropped the connection with no
reply.

Walk the inner sequence with a nil-safe accessor and bail out when
the response_token is absent. Wrap the Net::NTLM parse so bytes that
are not a valid Type 3 message produce a logged error and a nil
return rather than killing the thread.
@Pushpenderrathore

Copy link
Copy Markdown
Contributor Author

The red CI run here is a pre-existing flake, not a regression from this change.

Only one job actually failed: ubuntu-latest - Ruby 3.2 - bundle exec rspec. The other seven reds are the fail-fast: true matrix cancelling the rest after the leader failed.

The failing test is RubySMB::Client#initialize sets the pid to a random value (spec/lib/ruby_smb/client_spec.rb:150-156), which loops 100 times asserting two consecutive rand(1..0xFFFF) draws never collide:

Failure/Error: expect(client.pid).to_not eq(previous_pid)
  expected: value != 59731
       got: 59731

Across the 16-bit PID space and 100 iterations, this collides occasionally; the collision is in rand, not in Client. The spec file is unchanged on this branch, confirmed by git log upstream/master -- spec/lib/ruby_smb/client_spec.rb matching the branch's log for that path. The ubuntu-latest - Ruby 3.3 job on the same commit passed.

Could someone re-trigger the failed jobs when convenient? I do not have the admin bit to rerun from my side. Happy to open a separate PR to either widen the PID space in that spec or to seed its random source, if that would help.

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