Created
February 4, 2026 20:28
-
-
Save shakquraa/7b86b0245971e0315836c8c9fdf8b971 to your computer and use it in GitHub Desktop.
Microsoft tenant–based domain discovery for bug bounty recon.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import argparse | |
| import sys | |
| import time | |
| import random | |
| import os | |
| import requests | |
| UA = ( | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " | |
| "AppleWebKit/537.36 (KHTML, like Gecko) " | |
| "Chrome/139.0.0.0 Safari/537.36" | |
| ) | |
| HEADERS = {"User-Agent": UA} | |
| def log(msg, silent): | |
| if not silent: | |
| print(msg, file=sys.stderr) | |
| def fetch_tenant_id(domain): | |
| url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration" | |
| try: | |
| r = requests.get(url, headers=HEADERS, timeout=10) | |
| if r.status_code != 200: | |
| return None | |
| data = r.json() | |
| token_endpoint = data.get("token_endpoint") | |
| if not token_endpoint: | |
| return None | |
| # https://login.microsoftonline.com/{tenant_id}/oauth2/... | |
| return token_endpoint.split("/")[3] | |
| except Exception: | |
| return None | |
| def query_domains(tenant_id): | |
| # random delay to avoid hammering | |
| time.sleep(random.uniform(1, 10)) | |
| url = "https://tenant-api.micahvandeusen.com/search" | |
| try: | |
| r = requests.get(url, params={"tenant_id": tenant_id}, headers=HEADERS, timeout=15) | |
| if r.status_code != 200: | |
| return [] | |
| data = r.json() | |
| return data.get("domains", []) | |
| except Exception: | |
| return [] | |
| def process_domain(domain, silent): | |
| log(f"[*] Processing domain: {domain}", silent) | |
| tenant_id = fetch_tenant_id(domain) | |
| if not tenant_id: | |
| return [] | |
| return query_domains(tenant_id) | |
| def read_stdin_domains(): | |
| return [line.strip() for line in sys.stdin if line.strip()] | |
| def write_output(results, path): | |
| os.makedirs(os.path.dirname(path) or ".", exist_ok=True) | |
| with open(path, "w") as f: | |
| f.write("\n".join(results)) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="Discover Microsoft tenant-linked domains" | |
| ) | |
| parser.add_argument( | |
| "-d", "--domain", | |
| help="Single domain to query (example.com)" | |
| ) | |
| parser.add_argument( | |
| "-o", "--output", | |
| help="Write results to file" | |
| ) | |
| parser.add_argument( | |
| "-s", "--silent", | |
| action="store_true", | |
| help="Suppress status output" | |
| ) | |
| args = parser.parse_args() | |
| domains = [] | |
| if args.domain: | |
| domains.append(args.domain) | |
| if not sys.stdin.isatty(): | |
| domains.extend(read_stdin_domains()) | |
| if not domains: | |
| parser.print_help() | |
| sys.exit(0) | |
| found = [] | |
| for d in domains: | |
| found.extend(process_domain(d, args.silent)) | |
| if not found: | |
| log("[-] No domains discovered.", args.silent) | |
| sys.exit(1) | |
| unique = sorted(set(found)) | |
| if args.output: | |
| write_output(unique, args.output) | |
| log(f"[+] {len(unique)} unique domains saved to {args.output}", args.silent) | |
| else: | |
| print("\n".join(unique)) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment