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

Allow NTLM authentication without a password #371

Merged
merged 6 commits into from
Apr 21, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update v3
nodauf committed Apr 20, 2022
commit 5b1f9e555619cdaa6ed5f6aaa0607b5d94bb9cf4
29 changes: 25 additions & 4 deletions v3/bind.go
Original file line number Diff line number Diff line change
@@ -399,6 +399,9 @@ type NTLMBindRequest struct {
Username string
// Password is the credentials to bind with
Password string
// AllowEmptyPassword sets whether the client allows binding with an empty password
// (normally used for unauthenticated bind).
AllowEmptyPassword bool
// Hash is the hex NTLM hash to bind with. Password or hash must be provided
Hash string
// Controls are optional controls to send with the bind request
@@ -442,6 +445,24 @@ func (l *Conn) NTLMBind(domain, username, password string) error {
return err
}

// NTLMUnauthenticatedBind performs an unauthenticated bind.
//
// A username may be provided for trace (e.g. logging) purpose only, but it is normally not
Copy link
Member

@cpuschma cpuschma Apr 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NLMP/%5BMS-NLMP%5D.pdf

[...] If the user name and response are empty, the server authenticates the client as the ANONYMOUS user...

In the same document, see section "3.3.2 NTLM v2 Authentication" the pseudo code for the ServerChallenge:

If (User is set to "" && Passwd is set to "") <--
    -- Special case for anonymous authentication
    Set NtChallengeResponseLen to 0
    Set NtChallengeResponseMaxLen to 0
    Set NtChallengeResponseBufferOffset to 0
    Set LmChallengeResponse to Z(1)
[...]

I interpret that for a anonymous NTLM bind the username must be empty. I don't have an Active Directory server at hand as I'm on vacation, so I can't confirm the behaviour :/

Can someone confirm this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not notice that paper. The results of my tests are below:

  • A username with flag Password Not Required and an empty password (or the hash for empty password): A successful logon and I was able to use my ldap connection to retrieve information
  • A blank username and password: The error parsing ntlm-challenge: Anonymous authentication not supported is returned

Copy link
Member

@cpuschma cpuschma Apr 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your tests, @nodauf !
It seems anonymous NTLM authentications aren't supported by the Go NTLM library:

//ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message
//that was received from the server
func ProcessChallenge(challengeMessageData []byte, user, password string) ([]byte, error) {
	if user == "" && password == "" {
		return nil, errors.New("Anonymous authentication not supported")
	}

May you update the comment of the function and remove these lines, as atleast a username is required for the NTLM challenge to succeed?

// A username may be provided for trace (e.g. logging) purpose only, but it is normally not
// authenticated or otherwise validated by the LDAP server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking the time to look. The comments have been updated (in both root folder and v3 😅 )

// authenticated or otherwise validated by the LDAP server.
//
// See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
// See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May you please update/add links for the NTLM anonymous request? Maybe refer to this PDF from Microsoft, for example section "3.2.5.1.2 Server Receives an AUTHENTICATE_MESSAGE from the Client"

https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NLMP/%5BMS-NLMP%5D.pdf

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to update the comment for v3 😅, @nodauf

func (l *Conn) NTLMUnauthenticatedBind(domain, username string) error {
req := &NTLMBindRequest{
Domain: domain,
Username: username,
Password: "",
AllowEmptyPassword: true,
}
_, err := l.NTLMChallengeBind(req)
return err
}

// NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash)
func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {
req := &NTLMBindRequest{
@@ -455,7 +476,7 @@ func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {

// NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request
func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) {
if ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
if !ntlmBindRequest.AllowEmptyPassword && ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
}

@@ -496,10 +517,10 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes
var err error
var responseMessage []byte
// generate a response message to the challenge with the given Username/Password if password is provided
if ntlmBindRequest.Password != "" {
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password)
} else if ntlmBindRequest.Hash != "" {
if ntlmBindRequest.Hash != "" {
responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash)
} else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword {
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password)
} else {
err = fmt.Errorf("need a password or hash to generate reply")
}