The final loop is deliberately calm: connection failures are expected misses, so the tool skips them and continues. This is the simplest web-flavoured subdomain brute forcer: read a list, build a URL, ask the fixture, and keep going.
import argparsefrom pathlib import Pathimport requestsfrom fixtures.local_targets import course_root, fake_requests_getdef build_argument_parser(): parser = argparse.ArgumentParser(prog="bernauer") parser.add_argument("domain", help="domain to test, e.g. example.test") parser.add_argument("-w","--wordlist", default=str(course_root() /"data"/"wordlist.txt"),help="path to a wordlist file", ) parser.add_argument("--scheme", choices=("http", "https"), default="https") parser.add_argument("--timeout", type=float, default=2.0) parser.add_argument("--limit", type=int, help="only test the first N labels")return parserdef load_wordlist(path: Path) ->list[str]:with path.open(encoding="utf-8") asfile:return [ line.strip()for line infileif line.strip() andnot line.lstrip().startswith("#") ]def build_url(scheme: str, label: str, domain: str) ->str:returnf"{scheme}://{label}.{domain}"def find_subdomains(domain, labels, scheme="https", timeout=2.0, http_get=fake_requests_get): found = []for label in labels: url = build_url(scheme, label, domain)try: response = http_get(url, timeout=timeout)except requests.exceptions.ConnectionError:continueif response.status_code ==200: found.append(f"{label}.{domain}")return founddef main(args): labels = load_wordlist(Path(args.wordlist))if args.limit: labels = labels[: args.limit]for hostname in find_subdomains(args.domain, labels, args.scheme, args.timeout):print(f"Found: {hostname}")return0args = build_argument_parser().parse_args(["example.test", "--limit", "10"])print(main(args))
The --limit option gives you a smaller, faster loop while you’re debugging. It belongs before the loop starts because limiting the input list is simpler than making every later function understand partial runs.