Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
404 lines
13 KiB
404 lines
13 KiB
// +build !windows |
|
|
|
package mssql |
|
|
|
import ( |
|
"crypto/des" |
|
"crypto/hmac" |
|
"crypto/md5" |
|
"crypto/rand" |
|
"encoding/binary" |
|
"errors" |
|
"fmt" |
|
"strings" |
|
"time" |
|
"unicode/utf16" |
|
|
|
"golang.org/x/crypto/md4" |
|
) |
|
|
|
const ( |
|
_NEGOTIATE_MESSAGE = 1 |
|
_CHALLENGE_MESSAGE = 2 |
|
_AUTHENTICATE_MESSAGE = 3 |
|
) |
|
|
|
const ( |
|
_NEGOTIATE_UNICODE = 0x00000001 |
|
_NEGOTIATE_OEM = 0x00000002 |
|
_NEGOTIATE_TARGET = 0x00000004 |
|
_NEGOTIATE_SIGN = 0x00000010 |
|
_NEGOTIATE_SEAL = 0x00000020 |
|
_NEGOTIATE_DATAGRAM = 0x00000040 |
|
_NEGOTIATE_LMKEY = 0x00000080 |
|
_NEGOTIATE_NTLM = 0x00000200 |
|
_NEGOTIATE_ANONYMOUS = 0x00000800 |
|
_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000 |
|
_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000 |
|
_NEGOTIATE_ALWAYS_SIGN = 0x00008000 |
|
_NEGOTIATE_TARGET_TYPE_DOMAIN = 0x00010000 |
|
_NEGOTIATE_TARGET_TYPE_SERVER = 0x00020000 |
|
_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000 |
|
_NEGOTIATE_IDENTIFY = 0x00100000 |
|
_REQUEST_NON_NT_SESSION_KEY = 0x00400000 |
|
_NEGOTIATE_TARGET_INFO = 0x00800000 |
|
_NEGOTIATE_VERSION = 0x02000000 |
|
_NEGOTIATE_128 = 0x20000000 |
|
_NEGOTIATE_KEY_EXCH = 0x40000000 |
|
_NEGOTIATE_56 = 0x80000000 |
|
) |
|
|
|
const _NEGOTIATE_FLAGS = _NEGOTIATE_UNICODE | |
|
_NEGOTIATE_NTLM | |
|
_NEGOTIATE_OEM_DOMAIN_SUPPLIED | |
|
_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | |
|
_NEGOTIATE_ALWAYS_SIGN | |
|
_NEGOTIATE_EXTENDED_SESSIONSECURITY |
|
|
|
type ntlmAuth struct { |
|
Domain string |
|
UserName string |
|
Password string |
|
Workstation string |
|
} |
|
|
|
func getAuth(user, password, service, workstation string) (auth, bool) { |
|
if !strings.ContainsRune(user, '\\') { |
|
return nil, false |
|
} |
|
domain_user := strings.SplitN(user, "\\", 2) |
|
return &ntlmAuth{ |
|
Domain: domain_user[0], |
|
UserName: domain_user[1], |
|
Password: password, |
|
Workstation: workstation, |
|
}, true |
|
} |
|
|
|
func utf16le(val string) []byte { |
|
var v []byte |
|
for _, r := range val { |
|
if utf16.IsSurrogate(r) { |
|
r1, r2 := utf16.EncodeRune(r) |
|
v = append(v, byte(r1), byte(r1>>8)) |
|
v = append(v, byte(r2), byte(r2>>8)) |
|
} else { |
|
v = append(v, byte(r), byte(r>>8)) |
|
} |
|
} |
|
return v |
|
} |
|
|
|
func (auth *ntlmAuth) InitialBytes() ([]byte, error) { |
|
domain_len := len(auth.Domain) |
|
workstation_len := len(auth.Workstation) |
|
msg := make([]byte, 40+domain_len+workstation_len) |
|
copy(msg, []byte("NTLMSSP\x00")) |
|
binary.LittleEndian.PutUint32(msg[8:], _NEGOTIATE_MESSAGE) |
|
binary.LittleEndian.PutUint32(msg[12:], _NEGOTIATE_FLAGS) |
|
// Domain Name Fields |
|
binary.LittleEndian.PutUint16(msg[16:], uint16(domain_len)) |
|
binary.LittleEndian.PutUint16(msg[18:], uint16(domain_len)) |
|
binary.LittleEndian.PutUint32(msg[20:], 40) |
|
// Workstation Fields |
|
binary.LittleEndian.PutUint16(msg[24:], uint16(workstation_len)) |
|
binary.LittleEndian.PutUint16(msg[26:], uint16(workstation_len)) |
|
binary.LittleEndian.PutUint32(msg[28:], uint32(40+domain_len)) |
|
// Version |
|
binary.LittleEndian.PutUint32(msg[32:], 0) |
|
binary.LittleEndian.PutUint32(msg[36:], 0) |
|
// Payload |
|
copy(msg[40:], auth.Domain) |
|
copy(msg[40+domain_len:], auth.Workstation) |
|
return msg, nil |
|
} |
|
|
|
var errorNTLM = errors.New("NTLM protocol error") |
|
|
|
func createDesKey(bytes, material []byte) { |
|
material[0] = bytes[0] |
|
material[1] = (byte)(bytes[0]<<7 | (bytes[1]&0xff)>>1) |
|
material[2] = (byte)(bytes[1]<<6 | (bytes[2]&0xff)>>2) |
|
material[3] = (byte)(bytes[2]<<5 | (bytes[3]&0xff)>>3) |
|
material[4] = (byte)(bytes[3]<<4 | (bytes[4]&0xff)>>4) |
|
material[5] = (byte)(bytes[4]<<3 | (bytes[5]&0xff)>>5) |
|
material[6] = (byte)(bytes[5]<<2 | (bytes[6]&0xff)>>6) |
|
material[7] = (byte)(bytes[6] << 1) |
|
} |
|
|
|
func oddParity(bytes []byte) { |
|
for i := 0; i < len(bytes); i++ { |
|
b := bytes[i] |
|
needsParity := (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0 |
|
if needsParity { |
|
bytes[i] = bytes[i] | byte(0x01) |
|
} else { |
|
bytes[i] = bytes[i] & byte(0xfe) |
|
} |
|
} |
|
} |
|
|
|
func encryptDes(key []byte, cleartext []byte, ciphertext []byte) { |
|
var desKey [8]byte |
|
createDesKey(key, desKey[:]) |
|
cipher, err := des.NewCipher(desKey[:]) |
|
if err != nil { |
|
panic(err) |
|
} |
|
cipher.Encrypt(ciphertext, cleartext) |
|
} |
|
|
|
func response(challenge [8]byte, hash [21]byte) (ret [24]byte) { |
|
encryptDes(hash[:7], challenge[:], ret[:8]) |
|
encryptDes(hash[7:14], challenge[:], ret[8:16]) |
|
encryptDes(hash[14:], challenge[:], ret[16:]) |
|
return |
|
} |
|
|
|
func lmHash(password string) (hash [21]byte) { |
|
var lmpass [14]byte |
|
copy(lmpass[:14], []byte(strings.ToUpper(password))) |
|
magic := []byte("KGS!@#$%") |
|
encryptDes(lmpass[:7], magic, hash[:8]) |
|
encryptDes(lmpass[7:], magic, hash[8:]) |
|
return |
|
} |
|
|
|
func lmResponse(challenge [8]byte, password string) [24]byte { |
|
hash := lmHash(password) |
|
return response(challenge, hash) |
|
} |
|
|
|
func ntlmHash(password string) (hash [21]byte) { |
|
h := md4.New() |
|
h.Write(utf16le(password)) |
|
h.Sum(hash[:0]) |
|
return |
|
} |
|
|
|
func ntResponse(challenge [8]byte, password string) [24]byte { |
|
hash := ntlmHash(password) |
|
return response(challenge, hash) |
|
} |
|
|
|
func clientChallenge() (nonce [8]byte) { |
|
_, err := rand.Read(nonce[:]) |
|
if err != nil { |
|
panic(err) |
|
} |
|
return |
|
} |
|
|
|
func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password string) [24]byte { |
|
var sessionHash [16]byte |
|
h := md5.New() |
|
h.Write(serverChallenge[:]) |
|
h.Write(clientNonce[:]) |
|
h.Sum(sessionHash[:0]) |
|
var hash [8]byte |
|
copy(hash[:], sessionHash[:8]) |
|
passwordHash := ntlmHash(password) |
|
return response(hash, passwordHash) |
|
} |
|
|
|
func ntlmHashNoPadding(val string) []byte { |
|
hash := make([]byte, 16) |
|
h := md4.New() |
|
h.Write(utf16le(val)) |
|
h.Sum(hash[:0]) |
|
|
|
return hash |
|
} |
|
|
|
func hmacMD5(passwordHash, data []byte) []byte { |
|
hmacEntity := hmac.New(md5.New, passwordHash) |
|
hmacEntity.Write(data) |
|
|
|
return hmacEntity.Sum(nil) |
|
} |
|
|
|
func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) { |
|
// NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response |
|
|
|
ntlmHash := ntlmHashNoPadding(password) |
|
usernameAndTargetBytes := utf16le(strings.ToUpper(username) + userDomain) |
|
ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes) |
|
targetInfoLength := len(targetInfoFields) |
|
blob := make([]byte, 32+targetInfoLength) |
|
binary.BigEndian.PutUint32(blob[:4], 0x01010000) |
|
binary.BigEndian.PutUint32(blob[4:8], 0x00000000) |
|
binary.BigEndian.PutUint64(blob[8:16], uint64(timestamp.UnixNano())) |
|
copy(blob[16:24], nonce[:]) |
|
binary.BigEndian.PutUint32(blob[24:28], 0x00000000) |
|
copy(blob[28:], targetInfoFields) |
|
binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000) |
|
challengeLength := len(challenge) |
|
blobLength := len(blob) |
|
challengeAndBlob := make([]byte, challengeLength+blobLength) |
|
copy(challengeAndBlob[:challengeLength], challenge[:]) |
|
copy(challengeAndBlob[challengeLength:], blob) |
|
hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob) |
|
ntlmV2Payload = append(hashedChallenge, blob...) |
|
|
|
// LMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theLmv2Response |
|
ntlmV2hash := hmacMD5(ntlmHash, usernameAndTargetBytes) |
|
challengeAndNonce := make([]byte, 16) |
|
copy(challengeAndNonce[:8], challenge[:]) |
|
copy(challengeAndNonce[8:], nonce[:]) |
|
hashedChallenge = hmacMD5(ntlmV2hash, challengeAndNonce) |
|
lmV2Payload = append(hashedChallenge, nonce[:]...) |
|
|
|
return |
|
} |
|
|
|
func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string) (lm, nt []byte, err error) { |
|
nonce := clientChallenge() |
|
|
|
// Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 |
|
// Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html |
|
if (flags & _NEGOTIATE_TARGET_INFO) != 0 { |
|
targetInfoFields, err := getNTLMv2TargetInfoFields(message) |
|
if err != nil { |
|
return lm, nt, err |
|
} |
|
|
|
nt, lm = getNTLMv2AndLMv2ResponsePayloads(userDom, username, password, challenge, nonce, targetInfoFields, time.Now()) |
|
|
|
return lm, nt, nil |
|
} |
|
|
|
var lm_bytes [24]byte |
|
copy(lm_bytes[:8], nonce[:]) |
|
lm = lm_bytes[:] |
|
nt_bytes := ntlmSessionResponse(nonce, challenge, password) |
|
nt = nt_bytes[:] |
|
|
|
return lm, nt, nil |
|
} |
|
|
|
func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) { |
|
type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d" |
|
type2MessageLength := len(type2Message) |
|
if type2MessageLength < 20 { |
|
return nil, fmt.Errorf(type2MessageError, type2MessageLength, 20) |
|
} |
|
|
|
targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16]) |
|
targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20]) |
|
endOfOffset := int(targetNameOffset + uint32(targetNameAllocated)) |
|
if type2MessageLength < endOfOffset { |
|
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) |
|
} |
|
|
|
targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44]) |
|
targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48]) |
|
endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated)) |
|
if type2MessageLength < endOfOffset { |
|
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) |
|
} |
|
|
|
targetInformationBytes := make([]byte, targetInformationAllocated) |
|
copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)]) |
|
|
|
return targetInformationBytes, nil |
|
} |
|
|
|
func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) { |
|
lm_len := len(lm) |
|
nt_len := len(nt) |
|
domain16 := utf16le(domain) |
|
domain_len := len(domain16) |
|
user16 := utf16le(username) |
|
user_len := len(user16) |
|
workstation16 := utf16le(workstation) |
|
workstation_len := len(workstation16) |
|
msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len) |
|
copy(msg, []byte("NTLMSSP\x00")) |
|
binary.LittleEndian.PutUint32(msg[8:], _AUTHENTICATE_MESSAGE) |
|
|
|
// Lm Challenge Response Fields |
|
binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len)) |
|
binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len)) |
|
binary.LittleEndian.PutUint32(msg[16:], 88) |
|
|
|
// Nt Challenge Response Fields |
|
binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len)) |
|
binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len)) |
|
binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len)) |
|
|
|
// Domain Name Fields |
|
binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len)) |
|
binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len)) |
|
binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len)) |
|
|
|
// User Name Fields |
|
binary.LittleEndian.PutUint16(msg[36:], uint16(user_len)) |
|
binary.LittleEndian.PutUint16(msg[38:], uint16(user_len)) |
|
binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len)) |
|
|
|
// Workstation Fields |
|
binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len)) |
|
binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len)) |
|
binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len)) |
|
|
|
// Encrypted Random Session Key Fields |
|
binary.LittleEndian.PutUint16(msg[52:], 0) |
|
binary.LittleEndian.PutUint16(msg[54:], 0) |
|
binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len)) |
|
|
|
// Negotiate Flags |
|
binary.LittleEndian.PutUint32(msg[60:], flags) |
|
|
|
// Version |
|
binary.LittleEndian.PutUint32(msg[64:], 0) |
|
binary.LittleEndian.PutUint32(msg[68:], 0) |
|
|
|
// MIC |
|
binary.LittleEndian.PutUint32(msg[72:], 0) |
|
binary.LittleEndian.PutUint32(msg[76:], 0) |
|
binary.LittleEndian.PutUint32(msg[88:], 0) |
|
binary.LittleEndian.PutUint32(msg[84:], 0) |
|
|
|
// Payload |
|
copy(msg[88:], lm) |
|
copy(msg[88+lm_len:], nt) |
|
copy(msg[88+lm_len+nt_len:], domain16) |
|
copy(msg[88+lm_len+nt_len+domain_len:], user16) |
|
copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16) |
|
|
|
return msg, nil |
|
} |
|
|
|
func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) { |
|
signature := string(bytes[0:8]) |
|
if signature != "NTLMSSP\x00" { |
|
return nil, errorNTLM |
|
} |
|
|
|
messageTypeIndicator := binary.LittleEndian.Uint32(bytes[8:12]) |
|
if messageTypeIndicator != _CHALLENGE_MESSAGE { |
|
return nil, errorNTLM |
|
} |
|
|
|
var challenge [8]byte |
|
copy(challenge[:], bytes[24:32]) |
|
flags := binary.LittleEndian.Uint32(bytes[20:24]) |
|
if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { |
|
lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) |
|
} |
|
|
|
lm_bytes := lmResponse(challenge, auth.Password) |
|
lm := lm_bytes[:] |
|
nt_bytes := ntResponse(challenge, auth.Password) |
|
nt := nt_bytes[:] |
|
|
|
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) |
|
} |
|
|
|
func (auth *ntlmAuth) Free() { |
|
}
|
|
|