diff --git a/bbot/modules/azure_tenant.py b/bbot/modules/azure_tenant.py
index f17911c86a..4a323fece3 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,90 @@ 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
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):
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"]