def test_pretty_xml_handler(self): # Test that a normal, non-XML log record is passed through unchanged stream = io.StringIO() stream.isatty = lambda: True h = PrettyXmlHandler(stream=stream) self.assertTrue(h.is_tty()) r = logging.LogRecord( name='baz', level=logging.INFO, pathname='/foo/bar', lineno=1, msg='hello', args=(), exc_info=None ) h.emit(r) h.stream.seek(0) self.assertEqual(h.stream.read(), 'hello\n') # Test formatting of an XML record. It should contain newlines and color codes. stream = io.StringIO() stream.isatty = lambda: True h = PrettyXmlHandler(stream=stream) r = logging.LogRecord( name='baz', level=logging.DEBUG, pathname='/foo/bar', lineno=1, msg='hello %(xml_foo)s', args=({'xml_foo': b'<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>'},), exc_info=None) h.emit(r) h.stream.seek(0) self.assertEqual( h.stream.read(), "hello \x1b[36m<?xml version='1.0' encoding='utf-8'?>\x1b[39;49;00m\n\x1b[94m" "<foo\x1b[39;49;00m\x1b[94m>\x1b[39;49;00mbar\x1b[94m</foo>\x1b[39;49;00m\n\n" )
def test_wrap(self): # Test payload wrapper with both delegation, impersonation and timezones MockTZ = namedtuple('EWSTimeZone', ['ms_id']) MockAccount = namedtuple('Account', ['access_type', 'primary_smtp_address', 'default_timezone']) content = create_element('AAA') api_version = 'BBB' account = MockAccount(DELEGATE, '*****@*****.**', MockTZ('XXX')) wrapped = wrap(content=content, api_version=api_version, account=account) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), b'''<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> ''') account = MockAccount(IMPERSONATION, '*****@*****.**', MockTZ('XXX')) wrapped = wrap(content=content, api_version=api_version, account=account) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), b'''<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:ExchangeImpersonation> <t:ConnectingSID> <t:PrimarySmtpAddress>[email protected]</t:PrimarySmtpAddress> </t:ConnectingSID> </t:ExchangeImpersonation> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> ''')
def test_push_message_responses(self): # Test SendNotification ws = SendNotification(protocol=None) with self.assertRaises(ValueError): # Invalid status ws.get_payload(status="XXX") self.assertEqual( PrettyXmlHandler.prettify_xml(ws.ok_payload()), b"""\ <?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Body> <m:SendNotificationResult> <m:SubscriptionStatus>OK</m:SubscriptionStatus> </m:SendNotificationResult> </s:Body> </s:Envelope> """, ) self.assertEqual( PrettyXmlHandler.prettify_xml(ws.unsubscribe_payload()), b"""\ <?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Body> <m:SendNotificationResult> <m:SubscriptionStatus>Unsubscribe</m:SubscriptionStatus> </m:SendNotificationResult> </s:Body> </s:Envelope> """, )
def parse_configs() -> Configs: """Parse command line arguments and return configs""" # process command line args args = docopt(__doc__, version='calcatime {}'.format(__version__)) # extended debug? if args.get('--debug'): import logging from exchangelib.util import PrettyXmlHandler logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) # determine calendar provider calprovider = get_provider(args.get('-c', None)) # determine credentials username = args.get('-u', None) password = args.get('-p', None) if not username or not password: raise Exception('Calendar access credentials are required.') # get domain if provided domain = args.get('-d', None) # determine grouping attribute, set defaults if not provided grouping_attr = args.get('--by', None) if not grouping_attr: if calprovider.supports_categories: grouping_attr = 'category' else: grouping_attr = 'title' # determine if zeros need to be included include_zero = args.get('--include-zero', False) # determine output type, defaults to csv json_out = args.get('--json', False) # determine requested time span start, end = parse_timerange_tokens(args.get('<timespan>', [])) return Configs(calendar_provider=calprovider, username=username, password=password, range_start=start, range_end=end, domain=domain, grouping_attr=grouping_attr, include_zero=include_zero, output_type='json' if json_out else 'csv')
def gal(dump, search, verbose): """ Dump GAL using EWS. The slower technique used by https://github.com/dafthack/MailSniper default searches from "aa" to "zz" and prints them all. EWS only returns batches of 100 There will be doubles, so uniq after. """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) credentials = Credentials(tbestate.username, tbestate.password) username = tbestate.username if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) account = Account(username, config=config, autodiscover=False, access_type=DELEGATE) else: account = Account(username, credentials=credentials, autodiscover=True, access_type=DELEGATE) atoz = [ ''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2) ] if search: for names in ResolveNames( account.protocol).call(unresolved_entries=(search, )): click.secho(f'{names}') else: atoz = [ ''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2) ] for entry in atoz: for names in ResolveNames( account.protocol).call(unresolved_entries=(entry, )): click.secho(f'{names}') click.secho(f'-------------------------------------\n', dim=True)
def brute(verbose, userfile, password): """ Do a brute force. Made for horrizontal brute forcing mostly. Unless an exchange host is provided it will try autodiscover for each. Provide an exchange host to be faster. """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) if not tbestate.exch_host: click.secho( f'[*] Set an exchange host for a faster bruting experience', fg='yellow') usernames = open(userfile, "r") for username in usernames: username = username.strip() # config = Configuration() credentials = Credentials(username=username, password=password) try: if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) # pylint: disable=unused-variable account = Account(username, config=config, autodiscover=False) else: # pylint: disable=unused-variable account = Account(username, credentials=credentials, autodiscover=True) click.secho(f'[+] Success {username}:{password}', fg='green') except exchangelib.errors.UnauthorizedError: click.secho( f'[-] Failure {username}:{password} - exchangelib.errors.UnauthorizedError', dim=True, fg='red') except exchangelib.errors.TransportError: click.secho( f'[-] Failure {username}:{password} - exchangelib.errors.TransportError', dim=True, fg='red') click.secho(f'-------------------------------------\n', dim=True)
def __init__(self): # Set Timezone to local Timezone self._tz = EWSTimeZone.localzone() # set start and end time of Calendarentry self.init_time() # Logger logging.basicConfig(level=logging.WARNING, handlers=[PrettyXmlHandler()]) # Config Parser config = configparser.ConfigParser() config.read('config.txt') try: LoginData = config["Credentials"] except KeyError as error: print('The key ' + str(error) + ' were not found in the config file') exit() _Login = { "user": LoginData['user'], "password": LoginData['password'], "Primary_SMTP_Adress": LoginData['Primary SMTP Adress'] } # Credentials and account self._credentials = Credentials(username=_Login["user"], password=_Login["password"]) # Autodiscover fails w/o mail_config. See issue #337 on github exchangelib self._mailConfig = Configuration(server='outlook.office365.com', credentials=self._credentials) self._account = Account( default_timezone=self._tz, primary_smtp_address=_Login["Primary_SMTP_Adress"], config=self._mailConfig, credentials=self._credentials, autodiscover=False, access_type=DELEGATE) # Init Database self._db = exchange_database.exchange_database()
def oof(user_name: str, pwd: str, account: str, from_addr: str): """ Various out of office shenanigans :param user_name: :param pwd: :param account: :param from_addr: :return: """ logging.basicConfig(level=logging.INFO, handlers=[PrettyXmlHandler()]) creds = Credentials(user_name, pwd) config = Configuration(server=account, credentials=creds) account = Account(primary_smtp_address=from_addr, autodiscover=False, access_type=DELEGATE, config=config) current_settings = account.oof_settings print(current_settings)
def cli(config, username, password, dump_config, verbose, user_agent, outlook_agent, table_width, exch_host): """ \b thumsc-ews for Exchange Web Services by @_cablethief from @sensepost of @orangecyberdef """ # logging.basicConfig(level=logging.WARNING) if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) BaseProtocol.USERAGENT = "thumbscr-ews/" + \ __version__ + " (" + BaseProtocol.USERAGENT + ")" if outlook_agent and user_agent: click.secho(f'CANNOT USE TWO USERAGENTS AT ONCE!!!', fg='red') click.secho(f'Please use only --user-agent or --outlook-agent.', fg='red') quit() if outlook_agent: user_agent = "Microsoft Office/14.0 (Windows NT 6.1; Microsoft Outlook 14.0.7145; Pro)" if user_agent: BaseProtocol.USERAGENT = user_agent # set the mq configuration based on the configuration file if config is not None: with open(config) as f: config_data = yamllib.load(f, Loader=yamllib.FullLoader) tbestate.dictionary_updater(config_data) # set configuration based on the flags this command got tbestate.dictionary_updater(locals()) # If we should be dumping configuration, do that. if dump_config: click.secho('Effective configuration for this run:', dim=True) click.secho('-------------------------------------', dim=True) click.secho(f'Username: {tbestate.username}', dim=True) click.secho(f'Password: {tbestate.password}', dim=True) click.secho(f'User-Agent: {tbestate.user_agent}', dim=True) click.secho('-------------------------------------\n', dim=True)
def autodiscover(verbose): """ Authenticate and go through autodiscover. """ try: tbestate.validate(['username', 'password']) credentials = Credentials(tbestate.username, tbestate.password) if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) primary_address, protocol = discover(tbestate.username, credentials=credentials) click.secho(f'Autodiscover results:', bold=True, fg='yellow') click.secho(f'{primary_address.user}', fg='bright_green') click.secho(f'{protocol}', fg='bright_green') except exchangelib.errors.AutoDiscoverFailed: click.secho(f'Autodiscover failed, try with -v for more information:', bold=True, fg='red')
def get_exchange_email(from_addr: str, account: str, pwd: str, user_name: str, num_emails: int): """ Gets email with Exchange :param from_addr: :param account: :param pwd: :param user_name: :param num_emails: :return: """ logging.basicConfig(level=logging.INFO, handlers=[PrettyXmlHandler()]) if not num_emails: num_emails = 10 else: pass creds = Credentials(user_name, pwd) config = Configuration(server=account, credentials=creds) account = Account(primary_smtp_address=from_addr, autodiscover=True, access_type=DELEGATE, config=config) for item in account.inbox.all().order_by('-datetime_received')[:int(num_emails)]: print(item.subject, item.sender, item.datetime_received)
def cli(): parser = ArgumentParser() parser.add_argument("-N", "--offset", help="offset day integer.", type=int, default=1) parser.add_argument("--date", help="Date formats. (YYYY-MM-DD)", type=valid_date) parser.add_argument("-d", "--debug", help="debug mode", action="store_true") args = parser.parse_args() if args.debug: basicConfig( format="%(asctime)s:%(name)s:%(levelname)s:%(message)s", level=DEBUG, handlers=[PrettyXmlHandler()], ) else: basicConfig(level=INFO) main(date=args.date, n=args.offset)
def test_wrap(self): # Test payload wrapper with both delegation, impersonation and timezones MockTZ = namedtuple("EWSTimeZone", ["ms_id"]) MockAccount = namedtuple( "Account", ["access_type", "identity", "default_timezone"]) content = create_element("AAA") api_version = "BBB" account = MockAccount(access_type=DELEGATE, identity=None, default_timezone=MockTZ("XXX")) wrapped = wrap(content=content, api_version=api_version, timezone=account.default_timezone) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), b"""<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> """, ) for attr, tag in ( ("primary_smtp_address", "PrimarySmtpAddress"), ("upn", "PrincipalName"), ("sid", "SID"), ("smtp_address", "SmtpAddress"), ): val = f"{attr}@example.com" account = MockAccount(access_type=DELEGATE, identity=Identity(**{attr: val}), default_timezone=MockTZ("XXX")) wrapped = wrap( content=content, api_version=api_version, account_to_impersonate=account.identity, timezone=account.default_timezone, ) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), f"""<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:ExchangeImpersonation> <t:ConnectingSID> <t:{tag}>{val}</t:{tag}> </t:ConnectingSID> </t:ExchangeImpersonation> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> """.encode(), )
import logging import os import random import unittest.util from unittest import TestLoader, TestSuite from exchangelib.util import PrettyXmlHandler class RandomTestSuite(TestSuite): def __iter__(self): tests = list(super().__iter__()) random.shuffle(tests) return iter(tests) # Execute test classes in random order TestLoader.suiteClass = RandomTestSuite # Execute test methods in random order within each test class TestLoader.sortTestMethodsUsing = lambda _, x, y: random.choice((1, -1)) # Make sure we're also random in multiprocess test runners random.seed() # Always show full repr() output for object instances in unittest error messages unittest.util._MAX_LENGTH = 2000 if os.environ.get("DEBUG", "").lower() in ("1", "yes", "true"): logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) else: logging.basicConfig(level=logging.CRITICAL)
def delegatecheck(email_list, verbose, full_tree, folder): """ Check if the current user has access to the provided mailboxes By default will check if access to inbox or not. Can check for other access with --full-tree """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) credentials = Credentials(tbestate.username, tbestate.password) if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) account = Account(tbestate.username, config=config, autodiscover=False) else: account = Account(tbestate.username, credentials=credentials, autodiscover=True) ews_url = account.protocol.service_endpoint ews_auth_type = account.protocol.auth_type # primary_smtp_address = account.primary_smtp_address # This one is optional. It is used as a hint to the initial connection and avoids one or more roundtrips # to guess the correct Exchange server version. version = account.version config = Configuration(service_endpoint=ews_url, credentials=credentials, auth_type=ews_auth_type, version=version) emails = open(email_list, "r") for email in emails: email = email.strip() try: delegate_account = Account(primary_smtp_address=email, config=config, autodiscover=False, access_type=DELEGATE) # We could also print the full file structure, but you get all the public folders for users. if full_tree: # pylint: disable=maybe-no-member click.secho( f'[+] Success {email} - Access to some folders.\n{delegate_account.root.tree()}', fg='green') elif folder: folders = delegate_account.root.glob(folder) if len(folders.folders) == 0: click.secho(f'[-] Failure {email} - No folder found', dim=True, fg='red') else: for current_folder in delegate_account.root.glob(folder): pl = [] for p in current_folder.permission_set.permissions: if p.permission_level != "None": pl.append(p.permission_level) click.secho( f'[+] Success {email} - Could access {current_folder} - Permissions: {pl}', fg='green') else: #delegate_account.inbox pl = [] for p in delegate_account.inbox.permission_set.permissions: if p.permission_level != "None": pl.append(p.permission_level) click.secho( f'[+] Success {email} - Could access inbox - Permissions: {pl}', fg='green') except exchangelib.errors.ErrorItemNotFound: click.secho(f'[-] {email} - Failure inbox not accessible', dim=True, fg='red') except exchangelib.errors.AutoDiscoverFailed: click.secho(f'[-] {email} - Failure AutoDiscoverFailed', dim=True, fg='red') except exchangelib.errors.ErrorNonExistentMailbox: click.secho(f'[-] {email} - Failure ErrorNonExistentMailbox', dim=True, fg='red') except exchangelib.errors.ErrorAccessDenied: click.secho(f'[-] {email} - Failure ErrorAccessDenied', dim=True, fg='red') except exchangelib.errors.ErrorImpersonateUserDenied: click.secho(f'[-] {email} - Failure ErrorImpersonateUserDenied', dim=True, fg='red') click.secho(f'-------------------------------------\n', dim=True)
def test_wrap(self): # Test payload wrapper with both delegation, impersonation and timezones MockTZ = namedtuple('EWSTimeZone', ['ms_id']) MockAccount = namedtuple( 'Account', ['access_type', 'identity', 'default_timezone']) content = create_element('AAA') api_version = 'BBB' account = MockAccount(access_type=DELEGATE, identity=None, default_timezone=MockTZ('XXX')) wrapped = wrap(content=content, api_version=api_version, timezone=account.default_timezone) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), b'''<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> ''') for attr, tag in ( ('primary_smtp_address', 'PrimarySmtpAddress'), ('upn', 'PrincipalName'), ('sid', 'SID'), ('smtp_address', 'SmtpAddress'), ): val = '*****@*****.**' % attr account = MockAccount(access_type=DELEGATE, identity=Identity(**{attr: val}), default_timezone=MockTZ('XXX')) wrapped = wrap( content=content, api_version=api_version, account_to_impersonate=account.identity, timezone=account.default_timezone, ) self.assertEqual( PrettyXmlHandler.prettify_xml(wrapped), '''<?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <s:Header> <t:RequestServerVersion Version="BBB"/> <t:ExchangeImpersonation> <t:ConnectingSID> <t:{tag}>{val}</t:{tag}> </t:ConnectingSID> </t:ExchangeImpersonation> <t:TimeZoneContext> <t:TimeZoneDefinition Id="XXX"/> </t:TimeZoneContext> </s:Header> <s:Body> <AAA/> </s:Body> </s:Envelope> '''.format(tag=tag, val=val).encode())
def gal(dump, search, verbose, full, output): """ Dump GAL using EWS. The slower technique used by https://github.com/dafthack/MailSniper default searches from "aa" to "zz" and prints them all. EWS only returns batches of 100 There will be doubles, so uniq after. """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) credentials = Credentials(tbestate.username, tbestate.password) username = tbestate.username try: if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) account = Account(username, config=config, autodiscover=False, access_type=DELEGATE) else: account = Account(username, credentials=credentials, autodiscover=True, access_type=DELEGATE) except exchangelib.errors.ErrorNonExistentMailbox as neb: print(f'[!] Mailbox does not exist for user: {username}') exit() except exchangelib.errors.UnauthorizedError as err: print(f'[!] Invalid credentials for user: {username}') exit() except exchangelib.errors.TransportError: print(f'[!] Can not reach target Exchange server: {tbestate.exch_host}') exit() except Exception as err: print(f'[!] Something went wrong: {err}') atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] if search: for names in ResolveNames(account.protocol).call(unresolved_entries=(search,)): if output: click.secho(f'{names}') output.write(f'{names}\n') else: click.secho(f'{names}') elif full: atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] for entry in atoz: for names in ResolveNames(account.protocol).call(unresolved_entries=(entry,)): stringed = str(names) found = re.findall(r'[\w.+-]+@[\w-]+\.[\w.-]+', stringed) for i in found: if output: click.secho(f'{i}') output.write(f'{i}\n') else: click.secho(f'{i}') else: atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] for entry in atoz: for names in ResolveNames(account.protocol).call(unresolved_entries=(entry,)): if output: click.secho(f'{names}') output.write(f'{names}\n') else: click.secho(f'{names}') click.secho(f'-------------------------------------\n', dim=True)