List [CTL]
TL; DR
为掌握细节并加深理解,参考MSDN实现一个NTLMSSP库。repo:https://github.com/eddieivan01/ntlmssp
本文主要篇幅介绍NTLM认证协议的细节,对于互联网上关于NTLM常见的知识不作赘述
文末是使用该库实现的两个Example:SSPI本地令牌协商和NTLM over HTTP
NTLM Message
三种message中Signature
, MessageType
, NegotiateFlags
, Payload
字段是共有的
Signature
是8-byte字节数组,{ 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' }
MessageType
是32-bit unsigned integer,在type1/type2/type3
中分别为1/2/3
NegotiateFlags
是32-bit的Negotiate structure,这个结构体比较重要,记录了NTLM认证中一些协商配置项,后文会详细介绍
Payload
字段是一个变长的字节数组,message中的变长字段都是通过定长的Len/BufferOffset
来存储在Payload
中
除此之外还有一个8-byte的Version structure,仅作debug用,当NegotiateFlags
中的NEGOTIATE_VERSION
被设置时才会填充该字段
Negotiate Message
type NegotiateMsg struct {
Signature [8]byte
MessageType uint32
NegotiateFlags uint32
DomainNameLen uint16
DomainNameMaxLen uint16
DomainNameBufferOffset uint32
WorkstationLen uint16
WorkstationMaxLen uint16
WorkstationBufferOffset uint32
// Version is variable
// Version [8]byte
Payload []byte
}
Negotiage Message
中可以设置Domain和Workstation,是否设置了这两个字段由NegotiateFlags
的NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
和NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
指示。从这两个flag的名称也可以看出,Domain和Workstation必须是OEM编码(这里为什么单独提一下,因为后面的字段需要根据协商结果来决定是OEM还是UNICODE)
以DomainName
举例如何设置变长字段:
当Client设置DomainName
时,需要设置DomainNameLen
为DomainName
的长度,DomainNameMaxLen
必须等于DomainNameLen
,然后将OEM编码的DomainName
写入Payload
字段中,然后将DomainName
距离Negotiate Message
开头的偏移保存在DomainNameBufferOffset
中,最后设置NegotiateFlags
的NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
Challenge Message
type ChallengeMsg struct {
Signature [8]byte
MessageType uint32
TargetNameLen uint16
TargetNameMaxLen uint16
TargetNameBufferOffset uint32
NegotiateFlags uint32
ServerChallenge [8]byte
Reserved [8]byte
TargetInfoLen uint16
TargetInfoMaxLen uint16
TargetInfoBufferOffset uint32
// Version is variable
// Version [8]byte
Payload []byte
}
TargetName
:
当Client发送的Type1的NegotiateFlags
设置了NTLMSSP_REQUEST_TARGET
时,需要设置该字段
该字段的值是Server Name还是Domain Name由Type2的NegotiateFlags
的NTLMSSP_TARGET_TYPE_SERVER / NTLMSSP_TARGET_TYPE_DOMAIN
决定
该字段由协商的字符集编码(OEM or UNICODE),也就是NegotiateFlags
的NTLM_NEGOTIATE_OEM / NTLMSSP_NEGOTIATE_UNICODE
ServerChallenge
很明显,Server生成的8-byte challenge
Reserved
是保留字段,全0
TargetInfo
:
如果Server设置该字段,需要同时设置Type2的NegotiateFlags
的NTLMSSP_NEGOTIATE_TARGET_INFO
该变长字段是一个AV_PAIR structure,其中包括:
MsvAvEOL
* MsvAvNbComputerName
* MsvAvNbDomainName
* MsvAvDnsComputerName
* MsvAvDnsDomainName
* MsvAvDnsTreeName
MsvAvFlags
MsvAvTimestamp
MsvAvSingleHost
* MsvAvTargetName
MsvAvChannelBindings
带星号的textual value必须是UNICODE编码
AV_PAIR
还会在NTLMv2 Response中的NTLMv2_CLIENT_CHALLENGE
中出现
Authenticate Message
type AuthenticateMsg struct {
Signature [8]byte
MessageType uint32
LmChallengeResponseLen uint16
LmChallengeResponseMaxLen uint16
LmChallengeResponseBufferOffset uint32
NtChallengeResponseLen uint16
NtChallengeResponseMaxLen uint16
NtChallengeResponseBufferOffset uint32
DomainNameLen uint16
DomainNameMaxLen uint16
DomainNameBufferOffset uint32
UserNameLen uint16
UserNameMaxLen uint16
UserNameBufferOffset uint32
WorkstationLen uint16
WorkstationMaxLen uint16
WorkstationBufferOffset uint32
EncryptedRandomSessionKeyLen uint16
EncryptedRandomSessionKeyMaxLen uint16
EncryptedRandomSessionKeyBufferOffset uint32
NegotiateFlags uint32
// Version is variable
// Version [8]byte
// The MIC field is omitted in Windows NT, Windows 2000, Windows XP, and Windows Server 2003.
// MIC [16]byte
Payload []byte
}
LmChallengeResponse / NtChallengeResponse
在后面计算响应时介绍
DomainName / UserName / Workstaion
是认证的用户名/域名/工作组名,必须是协商的编码
EncryptedRandomSessionKey
是Client生成的一个Session Key,用于安全会话通信
还有一个MIC(message integrity code)字段,这个字段和VERSION一样,都有可能不填充,但并没有flag来指示是否填充,微软只丢了一句话:The MIC field is omitted in Windows NT, Windows 2000, Windows XP, and Windows Server 2003.
。所以在解析时只能根据Offset探测是否有MIC字段
( 其实在AV_PAIR
的MsvAvFlags
字段有这样一个标注:0x00000002: Indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.
,然而Type3中并没有AV_PAIR
Negotiate Flags
const (
NEGOTIATE_56BIT_ENCRYPTION = 0x80000000
NEGOTIATE_EXPLICIT_KEY_EXCHANGE = 0x40000000
NEGOTIATE_128BIT_SESSION_KEY = 0x20000000
NEGOTIATE_R1_UNUSED = 0x10000000
NEGOTIATE_R2_UNUSED = 0x8000000
NEGOTIATE_R3_UNUSED = 0x4000000
NEGOTIATE_VERSION = 0x2000000
NEGOTIATE_R4_UNUSED = 0x1000000
NEGOTIATE_TARGET_INFO = 0x800000
NEGOTIATE_REQUEST_NON_NT_SESSION_KEY = 0x400000
NEGOTIATE_R5_UNUSED = 0x200000
NEGOTIATE_IDENTITY_LEVEL_TOKEN = 0x100000
NEGOTIATE_EXTENDED_SESSION_SECURITY = 0x80000
NEGOTIATE_R6_UNUSED = 0x40000
NEGOTIATE_TARGET_TYPE_SERVER = 0x20000
NEGOTIATE_TARGET_TYPE_DOMAIN = 0x10000
NEGOTIATE_ALWAYS_SIGN = 0x8000
NEGOTIATE_R7_UNUSED = 0x4000
NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x2000
NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x1000
NEGOTIATE_ANONYMOUS = 0x800
NEGOTIATE_R8_UNUSED = 0x400
NEGOTIATE_NTLM = 0x200
NEGOTIATE_R9_UNUSED = 0x100
NEGOTIATE_LM_SESSION_KEY = 0x80
NEGOTIATE_DATAGRAM_CONNECTIONLESS = 0x40
NEGOTIATE_SEAL = 0x20
NEGOTIATE_SIGN = 0x10
NEGOTIATE_R10_UNUSED = 0x8
NEGOTIATE_REQUEST_TARGET_NAME = 0x4
NEGOTIATE_OEM_CHARSET = 0x2
NEGOTIATE_UNICODE_CHARSET = 0x1
)
一些常见的flag:
NEGOTIATE_56BIT_ENCRYPTION / NEGOTIATE_128BIT_SESSION_KEY
:56-bit or 128-bit session key
NEGOTIATE_VERSION
:是否填充VERSION字段
NEGOTIATE_TARGET_INFO
:在Type2中设置,是否有TargetInfo
字段
NEGOTIATE_EXTENDED_SESSION_SECURITY
:扩展会话安全,后文计算响应中会用到
NEGOTIATE_TARGET_TYPE_SERVER / NEGOTIATE_TARGET_TYPE_DOMAIN
:Type2中设置,TargetName
值的类型
NEGOTIATE_OEM_WORKSTATION_SUPPLIED / NEGOTIATE_OEM_DOMAIN_SUPPLIED
: Type1中是否有DOMAIN / WORKSTATION字段
NEGOTIATE_ANONYMOUS
:匿名认证
NEGOTIATE_DATAGRAM_CONNECTIONLESS
:使用connectionless(UDP)
NEGOTIATE_REQUEST_TARGET_NAME
:Type1中设置,请求TargetName
NEGOTIATE_OEM_CHARSET / NEGOTIATE_UNICODE_CHARSET
:编码协商
Response Compute
Note The LM and NTLM authentication versions are not negotiated by the protocol. It MUST be configured on both the client and the server prior to authentication.
所以NegotiateFlags
中的NEGOTIATE_REQUEST_NON_NT_SESSION_KEY / NEGOTIATE_NTLM / NEGOTIATE_LM_SESSION_KEY
和认证过程都没什么关系(它们和后续的会话安全有关),唯一和认证过程响应计算有关的是NEGOTIATE_EXTENDED_SESSION_SECURITY
Server在验证时需要通过Response长度(是否大于24)和NEGOTIATE_EXTENDED_SESSION_SECURITY
来决定使用哪种计算方法
认证有以下三种,后两种都有Client生成的nonce,缓解了彩虹表攻击:
- NTLMv1 Authentication:使用NTLMv1且
NEGOTIATE_EXTENDED_SESSION_SECURITY
未设置 - NTLMv1 With Client Challenge:使用NTLMv1且
NEGOTIATE_EXTENDED_SESSION_SECURITY
设置 - NTLMv2 Authentication:使用NTLMv2
具体计算方法不赘述了,直接看代码吧
NTLMv1 Authentication
分别计算LmChallengeResponse
和NtChallengeResponse
然后设置到Type3(实际情况里可能只发送其中一种)
这两个Response的计算过程基本相同(除了一个需要NT hash,一个需要LM hash)
func ComputeLMv1Response(challenge []byte, lmhash []byte) []byte {
lmhash = append(lmhash, []byte{0, 0, 0, 0, 0}...)
output := append(desEnc(padding(lmhash[:7]), challenge), desEnc(padding(lmhash[7:14]), challenge)...)
output = append(output, desEnc(padding(lmhash[14:]), challenge)...)
return output
}
func ComputeNTLMv1Response(challenge []byte, nthash []byte) []byte {
return ComputeLMv1Response(challenge, nthash)
}
NTLMv1 With Client Challenge
If NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY set, requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2.
当设置了NEGOTIATE_EXTENDED_SESSION_SECURITY
,需要先生成8-byte的nonce,然后填充16个'\0'
,设置在Type3的LmChallengeResponse
字段
接着计算NTLMv2SessionResponse
并设置在Type3的NtChallengeResponse
func ComputeNTLMv2SessionResponse(challenge []byte, clientNonce []byte, nthash []byte) []byte {
if clientNonce == nil {
clientNonce = make([]byte, 8)
rand.Read(clientNonce)
}
sessionHash := md5Hash(append(challenge, clientNonce...))[:8]
nthash = append(nthash, []byte{0, 0, 0, 0, 0}...)
output := append(desEnc(padding(nthash[:7]), sessionHash), desEnc(padding(nthash[7:14]), sessionHash)...)
return append(output, desEnc(padding(nthash[14:21]), sessionHash)...)
}
func (am *AuthenticateMsg) SetNTLMResponse(version int, challenge []byte, pwd []byte) {
if version == 1 && am.NegotiateFlags&NEGOTIATE_EXTENDED_SESSION_SECURITY != 0 {
nonce := [24]byte{}
rand.Read(nonce[:8])
am.LmChallengeResponseLen = 24
am.LmChallengeResponseMaxLen = 24
am.LmChallengeResponseBufferOffset = am.ptr
am.Payload = append(am.Payload, nonce[:]...)
am.ptr += uint32(am.LmChallengeResponseLen)
ntsresp := ComputeNTLMv2SessionResponse(challenge, nonce[:8], NtHash([]byte(pwd)))
am.NtChallengeResponseLen = uint16(len(ntsresp))
am.NtChallengeResponseMaxLen = am.NtChallengeResponseLen
am.NtChallengeResponseBufferOffset = am.ptr
am.Payload = append(am.Payload, ntsresp...)
am.ptr += uint32(am.NtChallengeResponseLen)
} else {
am.setLmResponse(version, challenge, pwd)
am.setNtResponse(version, challenge, pwd)
}
}
NTLMv2 Authentication
LMv2Response
的计算,值得注意的是,LMv2 Response的计算用的是NT Hash
func ComputeLMv2Response(challenge []byte, usernameWithDomainOrServer []byte, nthash []byte, clientNonce []byte) []byte {
if clientNonce == nil {
clientNonce = make([]byte, 8)
rand.Read(clientNonce)
}
hsh := hmacMd5(nthash, bytes.ToUpper(usernameWithDomainOrServer))
return append(hmacMd5(hsh, append(challenge, clientNonce...)), clientNonce...)
}
NTLMv2Response
的结构:
type NTLMv2Response struct {
Response [16]byte
ClientChallenge NTLMv2ClientChallenge
}
type NTLMv2ClientChallenge struct {
RespType byte
HiRespType byte
Reserved1 uint16
Reserved2 uint32
TimeStamp uint64
ChallengeFromClient [8]byte
Reserved3 uint32
AVPair map[string]interface{}
}
NTLMv2Response.Response
也就是常说的NTProofStr
,后面附加的blob是NTLMv2ClientChallenge
结构
计算过程相比于LMv2Response
,只是把一个8-byte的nonce替换成了NTLMv2ClientChallenge
结构,所以一样可以复用LMv2的计算函数
func ComputeNTLMv2Response(challenge []byte, usernameWithDomainOrServer []byte, nthash []byte, clientChallenge []byte) []byte {
if clientChallenge == nil {
nonce := [8]byte{}
rand.Read(nonce[:])
cc := NTLMv2ClientChallenge{
RespType: 1,
HiRespType: 1,
Reserved1: 0,
Reserved2: 0,
TimeStamp: (uint64(time.Now().UnixNano()) / 100) + 116444736000000000,
ChallengeFromClient: nonce,
Reserved3: 0,
AVPair: nil,
}
clientChallenge = cc.Marshal()
}
return ComputeLMv2Response(challenge, usernameWithDomainOrServer, nthash, clientChallenge)
}
一些细节
字节序
因为message中有integer类型的字段,所以序列化时涉及到字节序的问题。测试后得出,不论上层协议是什么(SMB, HTTP, RPC…),NTLMSSP都是小端序,也就意味着在主流架构上直接强转integer指针就行了:
bs = append(bs, (*(*[4]byte)(unsafe.Pointer(&nm.NegotiateFlags)))[:]...)
Negotiate Flags的协商
协商意味着Client发送的Type1的NegotiateFlags
和Server回复的Type2的NegotiateFlags
中相应的flag相同代表协商成功
例如Type1设置了NEGOTIATE_EXTENDED_SESSION_SECURITY
,Type2也设置了NEGOTIATE_EXTENDED_SESSION_SECURITY
,代表EXTENDED_SESSION_SECURITY
协商成功。接着在Type3中无论设置NEGOTIATE_EXTENDED_SESSION_SECURITY
与否,都会使用EXTENDED_SESSION_SECURITY
响应计算方式
Example
使用该库写了两个Example,分别是NTLM本地令牌协商和NTLM over HTTP
完整代码见https://github.com/eddieivan01/ntlmssp/tree/master/example
Local NTLM Negotiate
通过Go加载security.dll
,然后调用SSPI的AcceptSecurityContext
进行令牌协商
func localNegotiate() {
lpPackageName := []byte("Negotiate")
hCredential := credHandle{}
time := timeStamp{}
ret, _, err := acquireCredentialsHandleA.Call(
0,
(uintptr)(unsafe.Pointer(&lpPackageName[0])),
SECPKG_CRED_INBOUND,
0, 0, 0, 0,
(uintptr)(unsafe.Pointer(&hCredential)),
(uintptr)(unsafe.Pointer(&time)),
)
if ret != SEC_E_OK {
fmt.Println(err)
return
}
hContext := ctxtHandle{}
secBufClient := secBuffer{}
secBufDescClient := secBufferDesc{}
initTokenContextBuffer(&secBufDescClient, &secBufClient)
type1 := ntlmssp.NewNegotiateMsg(nil)
type1.NegotiateFlags |= ntlmssp.NEGOTIATE_128BIT_SESSION_KEY |
ntlmssp.NEGOTIATE_56BIT_ENCRYPTION |
ntlmssp.NEGOTIATE_UNICODE_CHARSET |
ntlmssp.NEGOTIATE_EXTENDED_SESSION_SECURITY
bs := type1.Marshal('<')
secBufClient.cbBuffer = uint32(len(bs))
secBufClient.pvBuffer = (uintptr)(unsafe.Pointer(&bs[0]))
secBufServer := secBuffer{}
secBufDescServer := secBufferDesc{}
initTokenContextBuffer(&secBufDescServer, &secBufServer)
var fContextAttr uint32
var tsExpiry timeStamp
ret, _, err = acceptSecurityContext.Call(
(uintptr)(unsafe.Pointer(&hCredential)),
0,
(uintptr)(unsafe.Pointer(&secBufDescClient)),
ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_CONNECTION,
SECURITY_NATIVE_DREP,
(uintptr)(unsafe.Pointer(&hContext)),
(uintptr)(unsafe.Pointer(&secBufDescServer)),
(uintptr)(unsafe.Pointer(&fContextAttr)),
(uintptr)(unsafe.Pointer(&tsExpiry)),
)
if ret != SEC_I_CONTINUE_NEEDED {
fmt.Println(err)
return
}
type2 := ntlmssp.NewChallengeMsg(loadByteArray(secBufServer.pvBuffer, secBufServer.cbBuffer))
type2.Display()
type3 := ntlmssp.NewAuthenticateMsg(nil)
type3.NegotiateFlags = type2.NegotiateFlags
// type3.NegotiateFlags &^= ntlmssp.NEGOTIATE_EXTENDED_SESSION_SECURITY
type3.SetUserName(username)
type3.SetWorkstation(servername)
type3.SetNTLMResponse(1, type2.ServerChallenge[:], password)
type3.Display()
bs = type3.Marshal('<')
initTokenContextBuffer(&secBufDescClient, &secBufClient)
secBufClient.pvBuffer = (uintptr)(unsafe.Pointer(&bs[0]))
secBufClient.cbBuffer = uint32(len(bs))
initTokenContextBuffer(&secBufDescServer, &secBufServer)
ret, _, err = acceptSecurityContext.Call(
(uintptr)(unsafe.Pointer(&hCredential)),
(uintptr)(unsafe.Pointer(&hContext)),
(uintptr)(unsafe.Pointer(&secBufDescClient)),
ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_CONNECTION,
SECURITY_NATIVE_DREP,
(uintptr)(unsafe.Pointer(&hContext)),
(uintptr)(unsafe.Pointer(&secBufDescServer)),
(uintptr)(unsafe.Pointer(&fContextAttr)),
(uintptr)(unsafe.Pointer(&tsExpiry)),
)
if ret == SEC_E_INVALID_TOKEN {
fmt.Println("NTLM auth error,", err)
}
if ret == SEC_E_LOGON_DENIED {
fmt.Println("Username or password wrong,", err)
return
}
if ret == SEC_E_OK {
fmt.Println("Auth ok,", err)
}
}
这里只能使用NTLMv1和NTLMv1 With Client Challenge认证,使用NTLMv2会返回SEC_E_INVALID_TOKEN
我认为应该不是响应计算有误,因为密码错误会返回SEC_E_LOGON_DENIED
(猜测不支持NTLMv2,但没有找到相关文档的说明)
NTLM over HTTP
在WireShark中可以看到协议解析无误
Server端:仅仅是一个Demo,实际情况下需要保存协商结果,做一些更复杂的校验
func handler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
w.Header().Set("WWW-Authenticate", "NTLM")
w.WriteHeader(401)
return
}
bs, err := base64.StdEncoding.DecodeString(auth[5:])
if err != nil {
w.Header().Set("WWW-Authenticate", "NTLM")
w.WriteHeader(401)
w.Write([]byte("Malformed base64"))
return
}
switch bs[8] {
case 1:
type1 := ntlmssp.NewNegotiateMsg(bs)
type2 := ntlmssp.NewChallengeMsg(nil)
type2.NegotiateFlags = type1.NegotiateFlags
type2.NegotiateFlags &^= ntlmssp.NEGOTIATE_VERSION
type2.NegotiateFlags |= ntlmssp.NEGOTIATE_EXTENDED_SESSION_SECURITY | ntlmssp.NEGOTIATE_TARGET_TYPE_DOMAIN
type2.SetServerChallenge(challenge)
type2.SetTargetName([]byte("XYZ.LAB"))
type2.SetTargetInfo(map[string]interface{}{
"MsvAvNbComputerName": "WIN-123456",
"MsvAvNbDomainName": "XYZ.LAB",
"MsvAvDnsComputerName": "DC$",
"MsvAvDnsDomainName": "XYZ.LAB",
})
w.Header().Set("WWW-Authenticate", "NTLM "+base64.StdEncoding.EncodeToString(type2.Marshal('<')))
w.WriteHeader(401)
case 3:
type3 := ntlmssp.NewAuthenticateMsg(bs)
ok := false
if type3.NtChallengeResponseLen <= 24 {
// NTLMv2 session
if type3.NegotiateFlags&ntlmssp.NEGOTIATE_EXTENDED_SESSION_SECURITY != 0 {
ntsResp := ntlmssp.ComputeNTLMv2SessionResponse(
challenge,
type3.LmChallengeResponse()[:8],
ntlmssp.NtHash(pwd))
if bytes.Equal(ntsResp, type3.NtChallengeResponseBytes()) {
ok = true
}
} else {
// NTLM
ntResp := ntlmssp.ComputeNTLMv1Response(challenge, ntlmssp.NtHash(pwd))
if bytes.Equal(ntResp, type3.NtChallengeResponseBytes()) {
ok = true
}
}
} else {
// NTLMv2
userNameWithDomainOrServer := type3.UserNameBytes()
if type3.DomainNameLen != 0 {
userNameWithDomainOrServer = append(userNameWithDomainOrServer, type3.DomainNameBytes()...)
} else if type3.WorkstationLen != 0 {
userNameWithDomainOrServer = append(userNameWithDomainOrServer, type3.WorkstationBytes()...)
}
ntResp := ntlmssp.ComputeNTLMv2Response(
challenge,
userNameWithDomainOrServer,
ntlmssp.NtHash(pwd),
type3.NtChallengeResponseBytes()[16:],
)
if bytes.Equal(ntResp, type3.NtChallengeResponseBytes()) {
ok = true
}
}
if ok {
w.Write([]byte("OK"))
} else {
w.Write([]byte("Auth fail"))
}
default:
w.Header().Set("WWW-Authenticate", "NTLM")
w.WriteHeader(401)
w.Write([]byte("Malformed NTLMSSP"))
return
}
}
Client端
func client() {
resp, err := nic.Post(url, nil)
if err != nil || resp.StatusCode != 401 || resp.Header.Get("WWW-Authenticate") != "NTLM" {
fmt.Println("type1 error")
return
}
type1 := ntlmssp.NewNegotiateMsg(nil)
type1.NegotiateFlags |= ntlmssp.NEGOTIATE_OEM_DOMAIN_SUPPLIED |
ntlmssp.NEGOTIATE_OEM_WORKSTATION_SUPPLIED |
ntlmssp.NEGOTIATE_128BIT_SESSION_KEY |
ntlmssp.NEGOTIATE_56BIT_ENCRYPTION |
ntlmssp.NEGOTIATE_UNICODE_CHARSET |
ntlmssp.NEGOTIATE_REQUEST_TARGET_NAME
type1.SetDomainName([]byte("CC.LAB"))
type1.SetWorkstation([]byte("WIN-123456"))
type1.Display()
resp, err = nic.Post(url, nic.H{
Headers: nic.KV{
"Authorization": "NTLM " + base64.StdEncoding.EncodeToString(type1.Marshal('<')),
},
})
if err != nil {
fmt.Println(err)
return
}
// trip "NTLM "
bs, err := base64.StdEncoding.DecodeString(resp.Header.Get("WWW-Authenticate")[5:])
if err != nil {
fmt.Println("type2 error")
return
}
type2 := ntlmssp.NewChallengeMsg(bs)
type2.Display()
type3 := ntlmssp.NewAuthenticateMsg(nil)
type3.NegotiateFlags = type2.NegotiateFlags
// type3.NegotiateFlags &^= ntlmssp.NEGOTIATE_EXTENDED_SESSION_SECURITY
type3.SetUserName([]byte("admin"))
type3.SetDomainName([]byte("LAB"))
type3.SetNTLMResponse(2, type2.ServerChallenge[:], pwd)
type3.Display()
resp, err = nic.Post(url, nic.H{
Headers: nic.KV{
"Authorization": "NTLM " + base64.StdEncoding.EncodeToString(type3.Marshal('<')),
},
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.Text)
}