Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue relating to cross-domain authentication on ntlm #125

Closed
ziyiwang opened this issue Jun 12, 2024 · 5 comments · Fixed by #126
Closed

issue relating to cross-domain authentication on ntlm #125

ziyiwang opened this issue Jun 12, 2024 · 5 comments · Fixed by #126

Comments

@ziyiwang
Copy link

heya Sam, i found another issue

when user is connecting to a different domain proxy (i.e, testau domain) and using existing domain (au) credentials, due to the initial negotiate coming bck is testau (the proxy being in that domain ), the subsequent authenticate message sent back to that proxy is always testau , hence failing authenticaitons, this is even with user entering the right domain i.e. au with -d option.

this cross-domain auth is needed as some of our test proxies support both prod and non-prod crednetails and multi domains, many proxies do that, i..e prod, non-prod, dev , staging etc.

hence the feature needs to be added to use the user provided domain for authentication regardless of the domain that comes with the hostname of the target proxy. Curl for example handles this quite seamlessly.

i have hacked a fix with overriding ntlmssp lib which is upstream , but i think a more elegant solution could help

overrideing this method with additional user domain issue seems to solve problem , here domain_uaa is the user entered domain at launch of alpaca.

`
func (m *challengeMessage) UnmarshalBinary(data []byte, domain_uaa string) error {
r := bytes.NewReader(data)
err := binary.Read(r, binary.LittleEndian, &m.challengeMessageFields)
if err != nil {
return err
}
if !m.challengeMessageFields.IsValid() {
return fmt.Errorf("Message is not a valid challenge message: %+v", m.challengeMessageFields.messageHeader)
}

if m.challengeMessageFields.TargetName.Len > 0 {
	//m.TargetName, err = m.challengeMessageFields.TargetName.ReadStringFrom(data, m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE))
	m.TargetName = domain_uaa
//	if err != nil {
//		return err
//	}
}

if m.challengeMessageFields.TargetInfo.Len > 0 {
	d, err := m.challengeMessageFields.TargetInfo.ReadFrom(data)
	m.TargetInfoRaw = d
	if err != nil {
		return err
	}
	m.TargetInfo = make(map[avID][]byte)
	r := bytes.NewReader(d)
	for {
		var id avID
		var l uint16
		err = binary.Read(r, binary.LittleEndian, &id)
		if err != nil {
			return err
		}
		if id == avIDMsvAvEOL {
			break
		}

		err = binary.Read(r, binary.LittleEndian, &l)
		if err != nil {
			return err
		}
		value := make([]byte, l)
		n, err := r.Read(value)
		if err != nil {
			return err
		}
		if n != int(l) {
			return fmt.Errorf("Expected to read %d bytes, got only %d", l, n)
		}
		m.TargetInfo[id] = value
	}
}

return nil

}
`

@samuong
Copy link
Owner

samuong commented Jun 12, 2024

Hi Ziyi, thanks for the bug report! I just wanted to make sure I'm understanding correctly. Here's what happens when Alpaca does the NTLM handshake:

  1. Alpaca sends a type 1 "negotiate" message to the proxy in the request. This message contains an (optional) domain, which Alpaca does send.
  2. The proxy sends back a type 2 "challenge" message in the response. This message contains an optional "target info" object, which contains the domain. Alpaca does not bother to look at this, and just assumes that the domain matches.
  3. Alpaca sends a type 3 "authenticate" message in the next request, which uses the credentials for the domain from the type 1 message.
  4. The proxy makes the request to the origin server and sends the response to Alpaca.

In your situation, it sounds like you've got two domains "au" and "testau". A user runs Alpaca with something like alpaca -d au, so the type negotiate message contains "au" and the challenge message contains "testau", since the proxy is in the "testau" domain. In this case, should the user authenticate using their "au" domain credentials, or their "testau" domain credentials?

I think you're saying that your user needs to use their "au" credentials, but the go-ntlmssp package blindly copies over the TargetName and TargetInfo from the challenge message into the authenticate message, which results in the proxy rejecting the request. And so the fix is to make sure that the authenticate message has the "au" domain in it. Is that correct?

I think you're not saying that Alpaca needs to accept multiple credentials (maybe something like alpaca -d au,testau -u user,testuser) , and have it look up the correct set of credentials depending on the TargetInfo in the challenge message. But please let me know if I've misunderstood.

@ziyiwang
Copy link
Author

hi sam, yes, dot point 3 is the issue, it always uses the domain from the hostname of the target proxy, hence causing auth errors regardless of what domain user puts in via -d option currently

samuong added a commit that referenced this issue Jun 16, 2024
This version sends the user's domain in the type 3 authenticate message,
rather than the server's domain.

I've also exposed the GetNtlmHash() function from go-ntlmssp, so that we
don't have to maintain a copy of the code in the alpaca repo.

Fixes #125
@samuong
Copy link
Owner

samuong commented Jun 16, 2024

Ok, I see what you mean. I've built a small test server that can reproduce the curl behaviour that you're talking about, and I can see that the go-ntlmssp package that we're using doesn't match curl's behaviour. It looks like curl is the one behaving correctly here.

As you mentioned, a fix should be applied in the upstream package, not in Alpaca itself. I think it would be best to set the user's domain while constructing the authenticate message, rather than overwriting the target domain in the challenge message. But I'm not sure how to do this without breaking the public API of that module, so for the moment I'll point Alpaca at a personal fork with the fix.

I'd really appreciate if you could test it once again, before I merge and tag the next release. Any chance you can give #126 a go?

@ziyiwang
Copy link
Author

hi mate , sorry in delay getting back to you, i have tested the build , it didnt inject the right domain still looksl ike, i will take a closer look over weekend, and compare it with my local fix version , i think the UnmarshalBinary needs tobe updated

@ziyiwang
Copy link
Author

Ok, I see what you mean. I've built a small test server that can reproduce the curl behaviour that you're talking about, and I can see that the go-ntlmssp package that we're using doesn't match curl's behaviour. It looks like curl is the one behaving correctly here.

As you mentioned, a fix should be applied in the upstream package, not in Alpaca itself. I think it would be best to set the user's domain while constructing the authenticate message, rather than overwriting the target domain in the challenge message. But I'm not sure how to do this without breaking the public API of that module, so for the moment I'll point Alpaca at a personal fork with the fix.

I'd really appreciate if you could test it once again, before I merge and tag the next release. Any chance you can give #126 a go?

hi mate, its all good ,tested again now, it was working fine, sorry it was credential issue on our side previously. no need for change on UnmarshalBinary. pls release 2.0.5 when you can , keen to get rid of the local fix build version we using now. and just use public 2.0.5 version, thanks mate

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 a pull request may close this issue.

2 participants