diff --git a/negotiate_flags.go b/negotiate_flags.go index 6c3ce7b..5905c02 100644 --- a/negotiate_flags.go +++ b/negotiate_flags.go @@ -7,30 +7,38 @@ const ( /*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1 /*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2 - /*D*/ negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4 + /*D*/ + negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4 /*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5 /*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6 /*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7 - /*H*/ negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9 + /*H*/ + negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9 - /*J*/ negotiateFlagANONYMOUS = 1 << 11 + /*J*/ + negotiateFlagANONYMOUS = 1 << 11 /*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12 /*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13 - /*M*/ negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15 + /*M*/ + negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15 /*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16 /*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17 - /*P*/ negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19 + /*P*/ + negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19 /*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20 - /*R*/ negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22 + /*R*/ + negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22 /*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23 - /*T*/ negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25 + /*T*/ + negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25 - /*U*/ negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29 + /*U*/ + negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29 /*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30 /*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31 ) diff --git a/negotiate_message.go b/negotiate_message.go index 97aa07e..28f0537 100644 --- a/negotiate_message.go +++ b/negotiate_message.go @@ -3,29 +3,61 @@ package ntlmssp import ( "bytes" "encoding/binary" + "errors" + "strings" ) +const expMsgBodyLen = 40 + type negotiateMessageFields struct { messageHeader NegotiateFlags negotiateFlags + + Domain varField + Workstation varField + + Version } +var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO | + negotiateFlagNTLMSSPNEGOTIATE56 | + negotiateFlagNTLMSSPNEGOTIATE128 | + negotiateFlagNTLMSSPNEGOTIATEUNICODE + //NewNegotiateMessage creates a new NEGOTIATE message with the //flags that this package supports. -func NewNegotiateMessage() []byte { - m := negotiateMessageFields{ - messageHeader: newMessageHeader(1), +func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) { + payloadOffset := expMsgBodyLen + flags := defaultFlags + + if domainName != "" { + flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED } - m.NegotiateFlags = negotiateFlagNTLMSSPREQUESTTARGET | - negotiateFlagNTLMSSPNEGOTIATENTLM | - negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN | - negotiateFlagNTLMSSPNEGOTIATEUNICODE + if workstationName != "" { + flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED + } + + msg := negotiateMessageFields{ + messageHeader: newMessageHeader(1), + NegotiateFlags: flags, + Domain: newVarField(&payloadOffset, len(domainName)), + Workstation: newVarField(&payloadOffset, len(workstationName)), + Version: DefaultVersion(), + } b := bytes.Buffer{} - err := binary.Write(&b, binary.LittleEndian, &m) - if err != nil { - panic(err) + if err := binary.Write(&b, binary.LittleEndian, &msg); err != nil { + return nil, err } - return b.Bytes() + if b.Len() != expMsgBodyLen { + return nil, errors.New("incorrect body length") + } + + payload := strings.ToUpper(domainName + workstationName) + if _, err := b.WriteString(payload); err != nil { + return nil, err + } + + return b.Bytes(), nil } diff --git a/negotiator.go b/negotiator.go index 9ae4586..6e30454 100644 --- a/negotiator.go +++ b/negotiator.go @@ -6,8 +6,21 @@ import ( "io" "io/ioutil" "net/http" + "strings" ) +// GetDomain : parse domain name from based on slashes in the input +func GetDomain(user string) (string, string) { + domain := "" + + if strings.Contains(user, "\\") { + ucomponents := strings.SplitN(user, "\\", 2) + domain = ucomponents[0] + user = ucomponents[1] + } + return user, domain +} + //Negotiator is a http.Roundtripper decorator that automatically //converts basic authentication to NTLM/Negotiate authentication when appropriate. type Negotiator struct{ http.RoundTripper } @@ -76,8 +89,15 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error) return nil, err } + // get domain from username + domain := "" + u, domain = GetDomain(u) + // send negotiate - negotiateMessage := NewNegotiateMessage() + negotiateMessage, err := NewNegotiateMessage(domain, "") + if err != nil { + return nil, err + } if resauth.IsNTLM() { req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage)) } else { diff --git a/nlmp_test.go b/nlmp_test.go index dea5aaf..c0ece95 100644 --- a/nlmp_test.go +++ b/nlmp_test.go @@ -10,8 +10,62 @@ import ( var username = "user" var password = "SecREt01" var target = "DOMAIN" +var domain = "MYDOMAIN" +var workstation = "MYPC" var challenge = []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef} +func TestUsernameDomainWorkstation(t *testing.T) { + // taking a username and workstation as input, check that the username, domain, workstation + // and negotiate message bytes all match their expected values + tables := []struct { + u string + w string + xu string + xd string + xb []byte + }{ + {username, "", username, "", []byte{ + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x80, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f}}, + {domain + "\\" + username, "", username, domain, []byte{ + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x80, 0xa0, 0x08, 0x00, 0x08, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, + 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e}}, + {domain + "\\" + username, workstation, username, domain, []byte{ + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x30, + 0x80, 0xa0, 0x08, 0x00, 0x08, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, + 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e, 0x4d, 0x59, 0x50, 0x43}}, + {username, workstation, username, "", []byte{ + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x80, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, + 0x50, 0x43}}, + } + + for _, table := range tables { + tuser, tdomain := GetDomain(table.u) + if tuser != table.xu { + t.Fatalf("username not correct, expected %v got %v", tuser, table.xu) + } + if tdomain != table.xd { + t.Fatalf("domain not correct, expected %v got %v", tdomain, table.xd) + } + + tb, err := NewNegotiateMessage(tdomain, table.w) + if err != nil { + t.Fatalf("error creating new negotiate message with domain '%v' and workstation '%v'", tdomain, table.w) + } + + if !bytes.Equal(tb, table.xb) { + t.Fatalf("negotiate message bytes not correct, expected %v got %v", tb, table.xb) + } + } + +} + func TestCalculateNTLMv2Response(t *testing.T) { NTLMv2Hash := getNtlmV2Hash(password, username, target) ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44} diff --git a/version.go b/version.go new file mode 100644 index 0000000..6d84892 --- /dev/null +++ b/version.go @@ -0,0 +1,20 @@ +package ntlmssp + +// Version is a struct representing https://msdn.microsoft.com/en-us/library/cc236654.aspx +type Version struct { + ProductMajorVersion uint8 + ProductMinorVersion uint8 + ProductBuild uint16 + _ [3]byte + NTLMRevisionCurrent uint8 +} + +// DefaultVersion returns a Version with "sensible" defaults (Windows 7) +func DefaultVersion() Version { + return Version{ + ProductMajorVersion: 6, + ProductMinorVersion: 1, + ProductBuild: 7601, + NTLMRevisionCurrent: 15, + } +}