Merge pull request #13 from justdan96/master

add version, domain and workstation fields
This commit is contained in:
Paul Meyer
2018-04-16 10:50:57 -07:00
committed by GitHub
5 changed files with 154 additions and 20 deletions

View File

@@ -7,30 +7,38 @@ const (
/*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1 /*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1
/*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2 /*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2
/*D*/ negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4 /*D*/
negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4
/*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5 /*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5
/*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6 /*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6
/*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7 /*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7
/*H*/ negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9 /*H*/
negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9
/*J*/ negotiateFlagANONYMOUS = 1 << 11 /*J*/
negotiateFlagANONYMOUS = 1 << 11
/*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12 /*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12
/*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13 /*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13
/*M*/ negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15 /*M*/
negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15
/*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16 /*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16
/*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17 /*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17
/*P*/ negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19 /*P*/
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19
/*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20 /*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20
/*R*/ negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22 /*R*/
negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22
/*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23 /*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23
/*T*/ negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25 /*T*/
negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25
/*U*/ negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29 /*U*/
negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29
/*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30 /*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30
/*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31 /*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31
) )

View File

@@ -3,29 +3,61 @@ package ntlmssp
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"strings"
) )
const expMsgBodyLen = 40
type negotiateMessageFields struct { type negotiateMessageFields struct {
messageHeader messageHeader
NegotiateFlags negotiateFlags NegotiateFlags negotiateFlags
Domain varField
Workstation varField
Version
} }
var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO |
negotiateFlagNTLMSSPNEGOTIATE56 |
negotiateFlagNTLMSSPNEGOTIATE128 |
negotiateFlagNTLMSSPNEGOTIATEUNICODE
//NewNegotiateMessage creates a new NEGOTIATE message with the //NewNegotiateMessage creates a new NEGOTIATE message with the
//flags that this package supports. //flags that this package supports.
func NewNegotiateMessage() []byte { func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) {
m := negotiateMessageFields{ payloadOffset := expMsgBodyLen
messageHeader: newMessageHeader(1), flags := defaultFlags
if domainName != "" {
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED
} }
m.NegotiateFlags = negotiateFlagNTLMSSPREQUESTTARGET | if workstationName != "" {
negotiateFlagNTLMSSPNEGOTIATENTLM | flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED
negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN | }
negotiateFlagNTLMSSPNEGOTIATEUNICODE
msg := negotiateMessageFields{
messageHeader: newMessageHeader(1),
NegotiateFlags: flags,
Domain: newVarField(&payloadOffset, len(domainName)),
Workstation: newVarField(&payloadOffset, len(workstationName)),
Version: DefaultVersion(),
}
b := bytes.Buffer{} b := bytes.Buffer{}
err := binary.Write(&b, binary.LittleEndian, &m) if err := binary.Write(&b, binary.LittleEndian, &msg); err != nil {
if err != nil { return nil, err
panic(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
} }

View File

@@ -6,8 +6,21 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "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 //Negotiator is a http.Roundtripper decorator that automatically
//converts basic authentication to NTLM/Negotiate authentication when appropriate. //converts basic authentication to NTLM/Negotiate authentication when appropriate.
type Negotiator struct{ http.RoundTripper } type Negotiator struct{ http.RoundTripper }
@@ -76,8 +89,15 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
return nil, err return nil, err
} }
// get domain from username
domain := ""
u, domain = GetDomain(u)
// send negotiate // send negotiate
negotiateMessage := NewNegotiateMessage() negotiateMessage, err := NewNegotiateMessage(domain, "")
if err != nil {
return nil, err
}
if resauth.IsNTLM() { if resauth.IsNTLM() {
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage)) req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage))
} else { } else {

View File

@@ -10,8 +10,62 @@ import (
var username = "user" var username = "user"
var password = "SecREt01" var password = "SecREt01"
var target = "DOMAIN" var target = "DOMAIN"
var domain = "MYDOMAIN"
var workstation = "MYPC"
var challenge = []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef} 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) { func TestCalculateNTLMv2Response(t *testing.T) {
NTLMv2Hash := getNtlmV2Hash(password, username, target) NTLMv2Hash := getNtlmV2Hash(password, username, target)
ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44} ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44}

20
version.go Normal file
View File

@@ -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,
}
}