From ac18801ce5350030f069a50b6debffc6ab884263 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Thu, 29 Jan 2026 11:00:05 -0500 Subject: [PATCH 1/4] fix azure tenant enum --- bbot/modules/azure_tenant.py | 121 +++++++++++++---------------------- 1 file changed, 45 insertions(+), 76 deletions(-) diff --git a/bbot/modules/azure_tenant.py b/bbot/modules/azure_tenant.py index f17911c86a..fa3d737283 100644 --- a/bbot/modules/azure_tenant.py +++ b/bbot/modules/azure_tenant.py @@ -1,6 +1,3 @@ -import regex as re -from contextlib import suppress - from bbot.modules.base import BaseModule @@ -9,116 +6,88 @@ class azure_tenant(BaseModule): produced_events = ["DNS_NAME"] flags = ["affiliates", "subdomain-enum", "cloud-enum", "passive", "safe"] meta = { - "description": "Query Azure for tenant sister domains", + "description": "Query Azure via azmap.dev for tenant sister domains", "created_date": "2024-07-04", "author": "@TheTechromancer", } - base_url = "https://autodiscover-s.outlook.com" + base_url = "https://azmap.dev/api/tenant" in_scope_only = True per_domain_only = True async def setup(self): self.processed = set() - self.d_xml_regex = re.compile(r"([^<>/]*)", re.I) return True async def handle_event(self, event): _, query = self.helpers.split_domain(event.data) - domains, openid_config = await self.query(query) - - tenant_id = None - authorization_endpoint = openid_config.get("authorization_endpoint", "") - matches = await self.helpers.re.findall(self.helpers.regexes.uuid_regex, authorization_endpoint) - if matches: - tenant_id = matches[0] - - tenant_names = set() - if domains: - self.verbose(f'Found {len(domains):,} domains under tenant for "{query}": {", ".join(sorted(domains))}') - for domain in domains: + tenant_data = await self.query(query) + + if not tenant_data: + return + + tenant_id = tenant_data.get("tenant_id") + tenant_name = tenant_data.get("tenant_name") + email_domains = tenant_data.get("email_domains", []) + + if email_domains: + self.verbose(f'Found {len(email_domains):,} domains under tenant for "{query}": {", ".join(sorted(email_domains))}') + for domain in email_domains: if domain != query: await self.emit_event( domain, "DNS_NAME", parent=event, tags=["affiliate", "azure-tenant"], - context=f'{{module}} queried Outlook autodiscover for "{query}" and found {{event.type}}: {{event.data}}', + context=f'{{module}} queried azmap.dev for "{query}" and found {{event.type}}: {{event.data}}', ) - # tenant names - if domain.lower().endswith(".onmicrosoft.com"): - tenantname = domain.split(".")[0].lower() - if tenantname: - tenant_names.add(tenantname) - - tenant_names = sorted(tenant_names) - event_data = {"tenant-names": tenant_names, "domains": sorted(domains)} + + # Build tenant names list (include the tenant name from the API) + tenant_names = [] + if tenant_name: + tenant_names.append(tenant_name) + + # Also extract tenant names from .onmicrosoft.com domains + for domain in email_domains: + if domain.lower().endswith(".onmicrosoft.com"): + tenantname = domain.split(".")[0].lower() + if tenantname and tenantname not in tenant_names: + tenant_names.append(tenantname) + + event_data = {"tenant-names": tenant_names, "domains": sorted(email_domains)} tenant_names_str = ",".join(tenant_names) - if tenant_id is not None: + if tenant_id: event_data["tenant-id"] = tenant_id await self.emit_event( event_data, "AZURE_TENANT", parent=event, - context=f'{{module}} queried Outlook autodiscover for "{query}" and found {{event.type}}: {tenant_names_str}', + context=f'{{module}} queried azmap.dev for "{query}" and found {{event.type}}: {tenant_names_str}', ) async def query(self, domain): - url = f"{self.base_url}/autodiscover/autodiscover.svc" - data = f""" - - - http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation - https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc - - http://www.w3.org/2005/08/addressing/anonymous - - - - - - {domain} - - - -""" - - headers = { - "Content-Type": "text/xml; charset=utf-8", - "SOAPAction": '"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"', - "User-Agent": "AutodiscoverClient", - "Accept-Encoding": "identity", - } + url = f"{self.base_url}?domain={domain}&extract=true" self.debug(f"Retrieving tenant domains at {url}") - autodiscover_task = self.helpers.create_task( - self.helpers.request(url, method="POST", headers=headers, content=data) - ) - openid_url = f"https://login.windows.net/{domain}/.well-known/openid-configuration" - openid_task = self.helpers.create_task(self.helpers.request(openid_url)) - - r = await autodiscover_task + r = await self.helpers.request(url) status_code = getattr(r, "status_code", 0) - if status_code not in (200, 421): + if status_code != 200: self.verbose(f'Error retrieving azure_tenant domains for "{domain}" (status code: {status_code})') - return set(), {} - found_domains = list(set(await self.helpers.re.findall(self.d_xml_regex, r.text))) - domains = set() + return {} - for d in found_domains: - # make sure we don't make any unnecessary api calls + try: + tenant_data = r.json() + except Exception as e: + self.warning(f'Error parsing JSON response for "{domain}": {e}') + return {} + + # Absorb domains into word cloud + email_domains = tenant_data.get("email_domains", []) + for d in email_domains: d = str(d).lower() _, query = self.helpers.split_domain(d) self.processed.add(hash(query)) - domains.add(d) - # absorb into word cloud self.scan.word_cloud.absorb_word(d) - r = await openid_task - openid_config = {} - with suppress(Exception): - openid_config = r.json() - - domains = sorted(domains) - return domains, openid_config + return tenant_data From 7f73f02d119f28336324d1026e6a2d9dd4640502 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Thu, 29 Jan 2026 11:00:12 -0500 Subject: [PATCH 2/4] ruffed --- bbot/modules/azure_tenant.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bbot/modules/azure_tenant.py b/bbot/modules/azure_tenant.py index fa3d737283..4a323fece3 100644 --- a/bbot/modules/azure_tenant.py +++ b/bbot/modules/azure_tenant.py @@ -31,7 +31,9 @@ async def handle_event(self, event): email_domains = tenant_data.get("email_domains", []) if email_domains: - self.verbose(f'Found {len(email_domains):,} domains under tenant for "{query}": {", ".join(sorted(email_domains))}') + self.verbose( + f'Found {len(email_domains):,} domains under tenant for "{query}": {", ".join(sorted(email_domains))}' + ) for domain in email_domains: if domain != query: await self.emit_event( From b843736ce7f0fc477030b2672ffe8ba4d86d8866 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Thu, 29 Jan 2026 11:04:54 -0500 Subject: [PATCH 3/4] update tests --- .../module_tests/test_module_azure_tenant.py | 93 +++---------------- 1 file changed, 11 insertions(+), 82 deletions(-) diff --git a/bbot/test/test_step_2/module_tests/test_module_azure_tenant.py b/bbot/test/test_step_2/module_tests/test_module_azure_tenant.py index b7986d3a11..ee15d1a6dd 100644 --- a/bbot/test/test_step_2/module_tests/test_module_azure_tenant.py +++ b/bbot/test/test_step_2/module_tests/test_module_azure_tenant.py @@ -2,93 +2,22 @@ class TestAzure_Tenant(ModuleTestBase): - tenant_response = """ - - - - http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformationResponse - - 15 - 20 - 6411 - 14 - Exchange2015 - - - - - - NoError - - outlook.com - - blacklanternsecurity.onmicrosoft.com - - - - https://login.microsoftonline.com/extSTS.srf - urn:federation:MicrosoftOnline - - - - - -""" - - openid_config_azure = { - "token_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/oauth2/token", - "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], - "jwks_uri": "https://login.windows.net/common/discovery/keys", - "response_modes_supported": ["query", "fragment", "form_post"], - "subject_types_supported": ["pairwise"], - "id_token_signing_alg_values_supported": ["RS256"], - "response_types_supported": ["code", "id_token", "code id_token", "token id_token", "token"], - "scopes_supported": ["openid"], - "issuer": "https://sts.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/", - "microsoft_multi_refresh_token": True, - "authorization_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/oauth2/authorize", - "device_authorization_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/oauth2/devicecode", - "http_logout_supported": True, - "frontchannel_logout_supported": True, - "end_session_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/oauth2/logout", - "claims_supported": [ - "sub", - "iss", - "cloud_instance_name", - "cloud_instance_host_name", - "cloud_graph_host_name", - "msgraph_host", - "aud", - "exp", - "iat", - "auth_time", - "acr", - "amr", - "nonce", - "email", - "given_name", - "family_name", - "nickname", + tenant_response = { + "tenant_id": "cc74fc12-4142-400e-a653-f98bdeadbeef", + "tenant_name": "blacklanternsecurity", + "domain": "blacklanternsecurity.com", + "email_domains": [ + "blacklanternsecurity.com", + "blacklanternsecurity.onmicrosoft.com", + "blsgvt.com", + "o365.blacklanternsecurity.com", ], - "check_session_iframe": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/oauth2/checksession", - "userinfo_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/openid/userinfo", - "kerberos_endpoint": "https://login.windows.net/cc74fc12-4142-400e-a653-f98bdeadbeef/kerberos", - "tenant_region_scope": "NA", - "cloud_instance_name": "microsoftonline.com", - "cloud_graph_host_name": "graph.windows.net", - "msgraph_host": "graph.microsoft.com", - "rbac_url": "https://pas.windows.net", } async def setup_after_prep(self, module_test): module_test.httpx_mock.add_response( - method="POST", - url="https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc", - text=self.tenant_response, - ) - module_test.httpx_mock.add_response( - url="https://login.windows.net/blacklanternsecurity.com/.well-known/openid-configuration", - json=self.openid_config_azure, + url="https://azmap.dev/api/tenant?domain=blacklanternsecurity.com&extract=true", + json=self.tenant_response, ) def check(self, module_test, events): From 147d8df9e35d63b8a6ec54fbc8cfbcc6240b5088 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Thu, 29 Jan 2026 12:54:09 -0500 Subject: [PATCH 4/4] bump version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 91e82c91c9..c826ae1aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bbot" -version = "2.8.0" +version = "2.8.1" description = "OSINT automation for hackers." authors = [ "TheTechromancer", @@ -121,7 +121,7 @@ lint.ignore = ["E402", "E711", "E713", "E721", "E741", "F403", "F405", "E501"] [tool.poetry-dynamic-versioning] enable = true metadata = false -format-jinja = 'v2.8.0{% if branch == "dev" %}.{{ distance }}rc{% endif %}' +format-jinja = 'v2.8.1{% if branch == "dev" %}.{{ distance }}rc{% endif %}' [tool.poetry-dynamic-versioning.substitution] files = ["*/__init__.py"]