diff --git a/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py b/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py
new file mode 100644
index 0000000..4b17c6d
--- /dev/null
+++ b/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py
@@ -0,0 +1,525 @@
+#!/usr/bin/env python3
+"""
+Domain TLD Enumerator
+======================
+Example:
+ python3 domain_tld_enum.py --name contohbrand
+ python3 domain_tld_enum.py --name contohbrand --tlds com,id,su,st,cx
+ python3 domain_tld_enum.py --name contohbrand --brute --brute-max-len 3
+ python3 domain_tld_enum.py --name contohbrand --brute --no-common --brute-max-len 2
+"""
+import argparse
+import concurrent.futures
+import itertools
+import json
+import re
+import socket
+import string
+import sys
+import time
+import requests
+import dns.resolver
+try:
+ from shutil import get_terminal_size
+except ImportError:
+ get_terminal_size = None
+requests.packages.urllib3.disable_warnings()
+DEFAULT_TIMEOUT = 6
+USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
+class Colors:
+ GREEN = "\033[92m"
+ RED = "\033[91m"
+ YELLOW = "\033[93m"
+ BOLD = "\033[1m"
+ RESET = "\033[0m"
+ @staticmethod
+ def disable():
+ Colors.GREEN = Colors.RED = Colors.YELLOW = Colors.BOLD = Colors.RESET = ""
+def green(text):
+ return f"{Colors.GREEN}{text}{Colors.RESET}"
+# Wordlist (TLD + ccTLD + gTLD + vanity)
+COMMON_TLDS = [
+
+ # Core
+ "com","net","org","info","biz","name","pro",
+
+ # Startup
+ "io","ai","app","dev","tech","cloud",
+ "software","systems","digital","network",
+ "security","email","tools",
+
+ # Business
+ "company","business","finance","capital",
+ "ventures","partners","consulting",
+ "solutions","services","support",
+
+ # Content
+ "blog","news","media","press",
+ "wiki","community","forum",
+
+ # Commerce
+ "shop","store","market","shopping",
+ "sale","deals",
+
+ # Branding
+ "xyz","online","site","website",
+ "space","world","live","today",
+ "agency","studio","design",
+ "group","life","center","zone",
+
+ # Indonesia
+ "id","co.id","web.id","or.id",
+ "ac.id","sch.id","go.id","my.id",
+
+ # Asia
+ "sg","my","th","vn","ph","in",
+ "jp","kr","cn","tw","hk",
+
+ # Europe
+ "uk","co.uk","de","fr","nl",
+ "it","es","pt","pl","ru",
+ "se","no","fi","dk","ch",
+ "at","be","cz","ro","hu",
+ "sk","si","bg","ua",
+
+ # Americas
+ "us","ca","mx","br","ar",
+
+ # Oceania
+ "au","nz",
+
+ # Middle East
+ "ae","sa","qa","tr",
+
+ # Vanity
+ "cc","tv","gg","vc","me",
+ "fm","am","ws","to","sh",
+ "is","ly","la","so","im",
+ "bz","li","sc","ms","gd",
+ "top","vip","icu","monster",
+ "buzz","click","link","win",
+ "fun","quest","su","st","cx",
+ "ax","gd","im","li","as","bz",
+ "sc","tk","ml","ga","cf","gq",
+ "nu","so","ms","re","wf","tf",
+ "pm","yt","nf","hn",
+
+ # Additional modern gTLD
+ "team", "global", "care", "social",
+ "video", "chat", "academy", "training",
+ "events", "marketing", "exchange",
+ "international", "technology",
+
+]
+def generate_brute_tlds(max_len=3, min_len=1):
+ """
+ Generate a-z string combinations with lengths ranging from min_len to max_len.
+ WARNING:
+ The number of combinations grows exponentially (26^n).
+ Avoid setting max_len too high (>4) unless you are prepared for
+ long execution times and possible DNS rate limiting.
+ len 1 -> 26
+ len 2 -> 676
+ len 3 -> 17,576
+ len 4 -> 456,976
+ len 5 -> 11,881,376
+ """
+ letters = string.ascii_lowercase
+ for length in range(min_len, max_len + 1):
+ for combo in itertools.product(letters, repeat=length):
+ yield "".join(combo)
+def build_tld_list(args):
+ """
+ Build the TLD list according to the following priority order:
+ 1. Custom TLDs provided via --tlds (if specified)
+ 2. Common TLDs (unless --no-common is enabled)
+ 3. Brute-force generated a-z combinations (if --brute is enabled)
+ The order is preserved to ensure that commonly used and
+ high-value TLDs are processed first, followed by brute-force
+ generated candidates.
+ """
+ tlds = []
+ if args.tlds:
+ tlds.extend([t.strip().lstrip(".") for t in args.tlds.split(",") if t.strip()])
+ if not args.no_common:
+ tlds.extend(COMMON_TLDS)
+ if args.brute:
+ brute_list = list(generate_brute_tlds(max_len=args.brute_max_len, min_len=args.brute_min_len))
+ tlds.extend(brute_list)
+ # dedupe, keep order (common tetap di depan, brute di belakang)
+ seen = set()
+ final = []
+ for t in tlds:
+ if t not in seen:
+ seen.add(t)
+ final.append(t)
+ return final
+def resolve_dns(domain):
+ """Resolve A record. Return list IP or None"""
+ try:
+ answers = dns.resolver.resolve(domain, "A", lifetime=DEFAULT_TIMEOUT)
+ return [str(r) for r in answers]
+ except Exception:
+ # fallback ke socket sebagai cadangan resolver
+ try:
+ ip = socket.gethostbyname(domain)
+ return [ip]
+ except Exception:
+ return None
+def resolve_ns(domain):
+ """Resolve NS record. Return list nameserver or None."""
+ try:
+ answers = dns.resolver.resolve(domain, "NS", lifetime=DEFAULT_TIMEOUT)
+ return [str(r).rstrip(".") for r in answers]
+ except Exception:
+ return None
+def extract_title(html_text):
+ if not html_text:
+ return None
+ match = re.search(r"
]*>(.*?)", html_text, re.IGNORECASE | re.DOTALL)
+ if match:
+ title = re.sub(r"\s+", " ", match.group(1)).strip()
+ return title[:200] if title else None
+ return None
+def check_http(url):
+ """Request HTTP(S), return dict info"""
+ try:
+ resp = requests.get(
+ url,
+ timeout=DEFAULT_TIMEOUT,
+ headers={"User-Agent": USER_AGENT},
+ allow_redirects=True,
+ verify=False,
+ )
+ return {
+ "status_code": resp.status_code,
+ "length": len(resp.content),
+ "title": extract_title(resp.text),
+ "server": resp.headers.get("Server"),
+ "content_type": resp.headers.get("Content-Type"),
+ "final_url": resp.url,
+ "redirected": resp.url != url,
+ }
+ except requests.exceptions.SSLError:
+ return {"error": "ssl_error"}
+ except requests.exceptions.ConnectTimeout:
+ return {"error": "connect_timeout"}
+ except requests.exceptions.ConnectionError:
+ return {"error": "connection_error"}
+ except requests.exceptions.RequestException as e:
+ return {"error": str(e)[:120]}
+def enumerate_one(name, tld):
+ domain = f"{name}.{tld}"
+ result = {
+ "domain": domain,
+ "tld": tld,
+ "resolved": False,
+ "ip": None,
+ "ns": None,
+ "http": None,
+ "https": None,
+ }
+ ips = resolve_dns(domain)
+ ns_records = resolve_ns(domain)
+ result["ns"] = ns_records
+ if ips or ns_records:
+ result["resolved"] = True
+ result["available_guess"] = False
+ else:
+ result["available_guess"] = True
+
+ if not ips:
+ return result
+ result["ip"] = ips
+
+ https_info = check_http(f"https://{domain}")
+ result["https"] = https_info
+ http_info = check_http(f"http://{domain}")
+ result["http"] = http_info
+ return result
+def truncate(text, width):
+ if text is None:
+ return "-"
+ text = str(text)
+ if len(text) <= width:
+ return text
+ return text[: width - 1] + "…"
+def get_combined_status(result):
+
+ parts = []
+ for scheme in ("https", "http"):
+ info = result.get(scheme)
+ if info is None:
+ continue
+ if "status_code" in info:
+ parts.append(f"{scheme[0].upper()}:{info['status_code']}")
+ elif "error" in info:
+ parts.append(f"{scheme[0].upper()}:ERR")
+ return ", ".join(parts) if parts else "-"
+def get_combined_title(result):
+ for scheme in ("https", "http"):
+ info = result.get(scheme)
+ if info and info.get("title"):
+ return info["title"]
+ return None
+def get_combined_length(result):
+ for scheme in ("https", "http"):
+ info = result.get(scheme)
+ if info and "length" in info:
+ return info["length"]
+ return None
+def get_https_service(result):
+ info = result.get("https")
+ if info is None:
+ return "-"
+ if "status_code" in info:
+ return f"yes ({info['status_code']})"
+ if "error" in info:
+ return f"no ({info['error']})"
+ return "-"
+def has_status_200(result):
+
+ for scheme in ("https", "http"):
+ info = result.get(scheme)
+ if info and info.get("status_code") == 200:
+ return True
+ return False
+def colorize_status(code_str, has_200):
+ if has_200:
+ return green(code_str)
+ return code_str
+def print_table(results, use_color=True):
+
+ headers = ["ID", "DOMAIN", "TLD", "HTTP/HTTPS CODE", "TITLE", "NS", "DNS", "HTTPS", "LENGTH"]
+ widths = [4, 28, 8, 18, 26, 22, 8, 14, 8]
+ def fmt_row(cells, colors=None):
+ out = []
+ for i, (c, w) in enumerate(zip(cells, widths)):
+ text = truncate(c, w).ljust(w)
+ if colors and colors.get(i):
+ # pad dulu baru kasih warna, supaya alignment tetap rapi
+ text = colors[i](truncate(c, w)) + " " * (w - len(truncate(c, w)))
+ out.append(text)
+ return " | ".join(out)
+ sep = "-+-".join("-" * w for w in widths)
+ print(fmt_row(headers))
+ print(sep)
+ for idx, r in enumerate(results, start=1):
+ domain = r["domain"]
+ tld = r["tld"]
+ code = get_combined_status(r)
+ title = get_combined_title(r)
+ ns_list = r.get("ns")
+ ns_str = ns_list[0] if ns_list else "-"
+ if ns_list and len(ns_list) > 1:
+ ns_str += f" (+{len(ns_list)-1})"
+ is_up = r.get("resolved")
+ dns_status = "UP" if is_up else "down"
+ https_status = get_https_service(r)
+ length = get_combined_length(r)
+ has_200 = has_status_200(r)
+ if use_color:
+ colors = {}
+ if has_200:
+ colors[3] = green # HTTP/HTTPS CODE
+ if is_up:
+ colors[6] = green # DNS
+ print(fmt_row([str(idx), domain, tld, code, title, ns_str, dns_status, https_status, length], colors))
+ else:
+ print(fmt_row([str(idx), domain, tld, code, title, ns_str, dns_status, https_status, length]))
+def main():
+ parser = argparse.ArgumentParser(
+ description="Enumerate domain availability and metadata across multiple TLDs"
+ )
+ parser.add_argument(
+ "--name",
+ required=True,
+ help="Domain name without TLD (e.g. examplebrand)"
+ )
+ parser.add_argument(
+ "--tlds",
+ help="Custom TLD list separated by commas (e.g. com,id,su,st,cx)"
+ )
+ parser.add_argument(
+ "--no-common",
+ action="store_true",
+ help="Skip the built-in common TLD list"
+ )
+ parser.add_argument(
+ "--brute",
+ action="store_true",
+ help="Enable brute-force generation of alphabetic TLD combinations (a-z)"
+ )
+ parser.add_argument(
+ "--brute-min-len",
+ type=int,
+ default=1,
+ help="Minimum brute-force TLD length (default: 1)"
+ )
+ parser.add_argument(
+ "--brute-max-len",
+ type=int,
+ default=3,
+ help=(
+ "Maximum brute-force TLD length (default: 3). "
+ "WARNING: combinations grow exponentially (26^n). "
+ "len=3 -> 17,576 | len=4 -> 456,976 | "
+ "len=5 -> 11.8 million. Recommended maximum: 3-4."
+ ),
+ )
+ parser.add_argument(
+ "--threads",
+ type=int,
+ default=30,
+ help="Number of worker threads (default: 30)"
+ )
+ parser.add_argument(
+ "--output",
+ default="Results-tld-domain.json",
+ help="Output JSON file path"
+ )
+ parser.add_argument(
+ "--only-resolved",
+ action="store_true",
+ help="Only save domains that successfully resolve to an IP address"
+ )
+ parser.add_argument(
+ "--only-200",
+ action="store_true",
+ help="Only save domains returning HTTP status 200"
+ )
+ parser.add_argument(
+ "--no-color",
+ action="store_true",
+ help="Disable colored CLI output"
+ )
+ args = parser.parse_args()
+ if args.no_color:
+ Colors.disable()
+ tlds = build_tld_list(args)
+ if not tlds:
+ print(
+ "[!] No TLDs selected. Use --tlds or do not enable --no-common."
+ )
+ sys.exit(1)
+ # Calculate how many TLDs come from the common list vs brute force
+ n_common = 0 if args.no_common else len(COMMON_TLDS)
+ n_brute = len(tlds) - n_common - (len(args.tlds.split(",")) if args.tlds else 0)
+ print(f"[*] Target name: {args.name}")
+ print(
+ f"[*] Total TLDs to check: {len(tlds)} "
+ f"(common: {n_common}, brute-force: {max(n_brute, 0)})"
+ )
+ print(f"[*] Worker threads: {args.threads}")
+ if args.brute:
+ print(
+ f"[*] Brute-force TLD length range: "
+ f"{args.brute_min_len}-{args.brute_max_len} characters"
+ )
+ results = []
+ start = time.time()
+ # IMPORTANT:
+ # Process common TLDs first (DNS + HTTP + title + NS lookup),
+ # then continue with brute-force generated TLDs.
+ # This ensures high-value and commonly used TLDs are prioritized.
+ common_set = set(COMMON_TLDS) if not args.no_common else set()
+ ordered_tlds = sorted(
+ tlds,
+ key=lambda t: (t not in common_set, tlds.index(t))
+ )
+ with concurrent.futures.ThreadPoolExecutor(
+ max_workers=args.threads
+ ) as executor:
+ futures = {
+ executor.submit(enumerate_one, args.name, tld): tld
+ for tld in ordered_tlds
+ }
+ done_count = 0
+ for future in concurrent.futures.as_completed(futures):
+ tld = futures[future]
+ try:
+ res = future.result()
+ except Exception as e:
+ res = {
+ "domain": f"{args.name}.{tld}",
+ "tld": tld,
+ "resolved": False,
+ "error": str(e),
+ }
+ done_count += 1
+ if res.get("resolved"):
+ tag = green("RESOLVED")
+ if has_status_200(res):
+ tag = green("RESOLVED + HTTP 200")
+ else:
+ tag = "NO RESOLUTION"
+ origin = "common" if tld in common_set else "brute"
+ print(
+ f"[{done_count}/{len(tlds)}] "
+ f"({origin:6}) "
+ f"{res['domain']:<32} {tag}"
+ )
+ results.append(res)
+ if args.only_resolved:
+ results = [r for r in results if r.get("resolved")]
+ if args.only_200:
+ results = [r for r in results if has_status_200(r)]
+ # Sort results:
+ # 1. HTTP 200 domains first
+ # 2. Resolved domains before unresolved ones
+ # 3. Alphabetical by TLD
+ results.sort(
+ key=lambda r: (
+ not has_status_200(r),
+ not r.get("resolved"),
+ r["tld"]
+ )
+ )
+
+ print()
+ print("===========TABLE VIEW==============")
+ print_table(results, use_color=not args.no_color)
+ print()
+ output_data = {
+ "target_name": args.name,
+ "total_checked": len(tlds),
+ "total_resolved": sum(
+ 1 for r in results if r.get("resolved")
+ ),
+ "total_status_200": sum(
+ 1 for r in results if has_status_200(r)
+ ),
+ "common_tlds_checked": n_common,
+ "brute_force_checked": max(n_brute, 0),
+ "elapsed_seconds": round(
+ time.time() - start,
+ 2
+ ),
+ "results": results,
+ }
+ with open(args.output, "w", encoding="utf-8") as f:
+ json.dump(
+ output_data,
+ f,
+ indent=2,
+ ensure_ascii=False
+ )
+ print(
+ f"\n[+] Completed in "
+ f"{output_data['elapsed_seconds']}s"
+ )
+ print(
+ f"[+] {output_data['total_resolved']} / "
+ f"{output_data['total_checked']} domains resolved"
+ )
+ print(
+ f"[+] {output_data['total_status_200']} "
+ f"domains returned HTTP 200"
+ )
+ print(
+ f"[+] Results saved to: "
+ f"{args.output}"
+ )
+
+if __name__ == "__main__":
+ main()
diff --git a/Script/Web-TLD-Enumerate-NS-Check/requirements.txt b/Script/Web-TLD-Enumerate-NS-Check/requirements.txt
new file mode 100644
index 0000000..7d11685
--- /dev/null
+++ b/Script/Web-TLD-Enumerate-NS-Check/requirements.txt
@@ -0,0 +1,2 @@
+requests
+dnspython