Merge pull request #13 from justdan96/master
add version, domain and workstation fields
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
54
nlmp_test.go
54
nlmp_test.go
@@ -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
20
version.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user