From f46022137fef5e44b88e62c12e374c9e52bbbe17 Mon Sep 17 00:00:00 2001 From: Ronnie Flathers Date: Sun, 7 Jun 2020 17:46:57 -0500 Subject: [PATCH 1/3] added ntlm bind --- v3/bind.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/v3/bind.go b/v3/bind.go index 1f429369..ab413d1c 100644 --- a/v3/bind.go +++ b/v3/bind.go @@ -6,6 +6,7 @@ import ( enchex "encoding/hex" "errors" "fmt" + "github.com/Azure/go-ntlmssp" "io/ioutil" "math/rand" "strings" @@ -387,3 +388,118 @@ func (l *Conn) ExternalBind() error { return GetLDAPError(packet) } + +// NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp + +type NTLMBindRequest struct { + Domain string + Username string + Password string + Controls []Control +} + +func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + + negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "") + if err != nil { + return fmt.Errorf("err creating negmessage: %s", err) + } + auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication") + request.AppendChild(auth) + envelope.AppendChild(request) + if len(req.Controls) > 0 { + envelope.AppendChild(encodeControls(req.Controls)) + } + return nil +} + +type NTLMBindResult struct { + Controls []Control +} + +func (l *Conn) NTLMBind(domain, username, password string) error { + req := &NTLMBindRequest{ + Domain: domain, + Username: username, + Password: password, + } + _, err := l.NTLMChallengeBind(req) + return err +} + +func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) { + if ntlmBindRequest.Password == "" { + return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) + } + + msgCtx, err := l.doRequest(ntlmBindRequest) + if err != nil { + return nil, err + } + defer l.finishMessage(msgCtx) + packet, err := l.readPacket(msgCtx) + if err != nil { + return nil, err + } + l.Debug.Printf("%d: got response %p", msgCtx.id, packet) + if l.Debug { + if err = addLDAPDescriptions(packet); err != nil { + return nil, err + } + ber.PrintPacket(packet) + } + result := &NTLMBindResult{ + Controls: make([]Control, 0), + } + var ntlmsspChallenge []byte + + // now find the NTLM Response Message + if len(packet.Children) == 2 { + if len(packet.Children[1].Children) == 3 { + child := packet.Children[1].Children[1] + ntlmsspChallenge = child.ByteValue + if !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) { + return result, GetLDAPError(packet) + } + l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id) + } + } + if ntlmsspChallenge != nil { + responseMessage, err := ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password) + if err != nil { + return result, fmt.Errorf("parsing ntlm-challenge: %s", err) + } + packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) + + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + + auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication") + + request.AppendChild(auth) + packet.AppendChild(request) + msgCtx, err = l.sendMessage(packet) + if err != nil { + return nil, fmt.Errorf("send message: %s", err) + } + defer l.finishMessage(msgCtx) + packetResponse, ok := <-msgCtx.responses + if !ok { + return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) + } + packet, err = packetResponse.ReadPacket() + l.Debug.Printf("%d: got response %p", msgCtx.id, packet) + if err != nil { + return nil, fmt.Errorf("read packet: %s", err) + } + + } + + err = GetLDAPError(packet) + return result, err +} From 580834d3e9f9a563063c17bacfdf34f0334f4fd2 Mon Sep 17 00:00:00 2001 From: Ronnie Flathers Date: Tue, 16 Jun 2020 20:40:12 -0500 Subject: [PATCH 2/3] adding doc comments --- v3/bind.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/v3/bind.go b/v3/bind.go index ab413d1c..09edba06 100644 --- a/v3/bind.go +++ b/v3/bind.go @@ -6,12 +6,13 @@ import ( enchex "encoding/hex" "errors" "fmt" - "github.com/Azure/go-ntlmssp" "io/ioutil" "math/rand" "strings" ber "github.com/go-asn1-ber/asn1-ber" + "github.com/Azure/go-ntlmssp" + ) // SimpleBindRequest represents a username/password bind operation @@ -391,10 +392,15 @@ func (l *Conn) ExternalBind() error { // NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp +// NTLMBindRequest represents an NTLMSSP bind operation type NTLMBindRequest struct { + // Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge Domain string + // Username is the name of the Directory object that the client wishes to bind as Username string + // Password is the credentials to bind with Password string + // Controls are optional controls to send with the bind request Controls []Control } @@ -403,10 +409,13 @@ func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + // generate an NTLMSSP Negotiation message for the specified domain (it can be blank) negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "") if err != nil { return fmt.Errorf("err creating negmessage: %s", err) } + + // append the generated NTLMSSP message as a TagEnumerated BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication") request.AppendChild(auth) envelope.AppendChild(request) @@ -416,10 +425,12 @@ func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { return nil } +// NTLMBindResult contains the response from the server type NTLMBindResult struct { Controls []Control } +// NTLMBind performs an NTLMSSP Bind with the given domain, username and password func (l *Conn) NTLMBind(domain, username, password string) error { req := &NTLMBindRequest{ Domain: domain, @@ -430,6 +441,7 @@ func (l *Conn) NTLMBind(domain, username, password string) error { return err } +// NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) { if ntlmBindRequest.Password == "" { return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) @@ -461,6 +473,7 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes if len(packet.Children[1].Children) == 3 { child := packet.Children[1].Children[1] ntlmsspChallenge = child.ByteValue + // Check to make sure we got the right message. It will always start with NTLMSSP if !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) { return result, GetLDAPError(packet) } @@ -468,6 +481,7 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes } } if ntlmsspChallenge != nil { + // generate a response message to the challenge with the given Username/Password responseMessage, err := ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password) if err != nil { return result, fmt.Errorf("parsing ntlm-challenge: %s", err) @@ -479,6 +493,7 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + // append the challenge response message as a TagEmbeddedPDV BER value auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication") request.AppendChild(auth) From 12b30500ebdc6708636f9c3d7c6bb5da203ec50d Mon Sep 17 00:00:00 2001 From: Ronnie Flathers Date: Tue, 16 Jun 2020 20:41:59 -0500 Subject: [PATCH 3/3] copy changes to older version --- bind.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/bind.go b/bind.go index 1f429369..09edba06 100644 --- a/bind.go +++ b/bind.go @@ -11,6 +11,8 @@ import ( "strings" ber "github.com/go-asn1-ber/asn1-ber" + "github.com/Azure/go-ntlmssp" + ) // SimpleBindRequest represents a username/password bind operation @@ -387,3 +389,132 @@ func (l *Conn) ExternalBind() error { return GetLDAPError(packet) } + +// NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp + +// NTLMBindRequest represents an NTLMSSP bind operation +type NTLMBindRequest struct { + // Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge + Domain string + // Username is the name of the Directory object that the client wishes to bind as + Username string + // Password is the credentials to bind with + Password string + // Controls are optional controls to send with the bind request + Controls []Control +} + +func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + + // generate an NTLMSSP Negotiation message for the specified domain (it can be blank) + negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "") + if err != nil { + return fmt.Errorf("err creating negmessage: %s", err) + } + + // append the generated NTLMSSP message as a TagEnumerated BER value + auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication") + request.AppendChild(auth) + envelope.AppendChild(request) + if len(req.Controls) > 0 { + envelope.AppendChild(encodeControls(req.Controls)) + } + return nil +} + +// NTLMBindResult contains the response from the server +type NTLMBindResult struct { + Controls []Control +} + +// NTLMBind performs an NTLMSSP Bind with the given domain, username and password +func (l *Conn) NTLMBind(domain, username, password string) error { + req := &NTLMBindRequest{ + Domain: domain, + Username: username, + Password: password, + } + _, err := l.NTLMChallengeBind(req) + return err +} + +// NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request +func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) { + if ntlmBindRequest.Password == "" { + return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client")) + } + + msgCtx, err := l.doRequest(ntlmBindRequest) + if err != nil { + return nil, err + } + defer l.finishMessage(msgCtx) + packet, err := l.readPacket(msgCtx) + if err != nil { + return nil, err + } + l.Debug.Printf("%d: got response %p", msgCtx.id, packet) + if l.Debug { + if err = addLDAPDescriptions(packet); err != nil { + return nil, err + } + ber.PrintPacket(packet) + } + result := &NTLMBindResult{ + Controls: make([]Control, 0), + } + var ntlmsspChallenge []byte + + // now find the NTLM Response Message + if len(packet.Children) == 2 { + if len(packet.Children[1].Children) == 3 { + child := packet.Children[1].Children[1] + ntlmsspChallenge = child.ByteValue + // Check to make sure we got the right message. It will always start with NTLMSSP + if !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) { + return result, GetLDAPError(packet) + } + l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id) + } + } + if ntlmsspChallenge != nil { + // generate a response message to the challenge with the given Username/Password + responseMessage, err := ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password) + if err != nil { + return result, fmt.Errorf("parsing ntlm-challenge: %s", err) + } + packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) + + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name")) + + // append the challenge response message as a TagEmbeddedPDV BER value + auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication") + + request.AppendChild(auth) + packet.AppendChild(request) + msgCtx, err = l.sendMessage(packet) + if err != nil { + return nil, fmt.Errorf("send message: %s", err) + } + defer l.finishMessage(msgCtx) + packetResponse, ok := <-msgCtx.responses + if !ok { + return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed")) + } + packet, err = packetResponse.ReadPacket() + l.Debug.Printf("%d: got response %p", msgCtx.id, packet) + if err != nil { + return nil, fmt.Errorf("read packet: %s", err) + } + + } + + err = GetLDAPError(packet) + return result, err +}