-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsubscope.py
More file actions
172 lines (141 loc) · 6.56 KB
/
Copy pathsubscope.py
File metadata and controls
172 lines (141 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/env python3
"""
SubScope - Subdomain Enumeration & Asset Discovery
Combines DNS brute force, certificate transparency, HTTP probing, and takeover detection.
⚠️ Only use against domains you own or have authorization to test.
"""
import argparse
import sys
from pathlib import Path
from discovery import dns_brute, cert_transparency, http_probe
from output import table, exporters
RESET = "\033[0m"
CYAN = "\033[36m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[91m"
BOLD = "\033[1m"
WHITE = "\033[37m"
def banner():
print(f"""
{CYAN}╔═══════════════════════════════════════════╗{RESET}
{CYAN}║ SubScope v1.0 ║{RESET}
{CYAN}║ Subdomain Enumeration & Asset Discovery ║{RESET}
{CYAN}╚═══════════════════════════════════════════╝{RESET}
""")
def process_domain(domain, args):
print(f"\n{CYAN}[*]{RESET} Target: {BOLD}{domain}{RESET}")
all_subdomains = {} # subdomain -> ip (or None)
# Step 1: Certificate Transparency
if not args.no_crt:
print(f"{CYAN}[*]{RESET} Querying certificate transparency (crt.sh)...")
crt_subs = cert_transparency.query(domain)
print(f" {GREEN}Found {len(crt_subs)} subdomains via crt.sh{RESET}")
for sub in crt_subs:
all_subdomains[sub] = None
# Step 2: DNS Brute Force
if not args.no_bruteforce:
wordlist = args.wordlist if args.wordlist else None
threads = args.threads
print(f"{CYAN}[*]{RESET} DNS brute forcing (threads={threads})...")
brute_results = dns_brute.brute(domain, wordlist_path=wordlist, threads=threads)
print(f" {GREEN}Found {len(brute_results)} subdomains via DNS brute force{RESET}")
all_subdomains.update(brute_results)
# Resolve IPs for crt.sh results that don't have them
unresolved = [s for s, ip in all_subdomains.items() if ip is None]
if unresolved:
print(f"{CYAN}[*]{RESET} Resolving {len(unresolved)} unresolved subdomains...")
import concurrent.futures, socket
def resolve_one(sub):
try:
return sub, socket.gethostbyname(sub)
except Exception:
return sub, None
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as ex:
for sub, ip in ex.map(resolve_one, unresolved):
if ip:
all_subdomains[sub] = ip
else:
del all_subdomains[sub]
if not all_subdomains:
print(f"{YELLOW}[!] No subdomains found for {domain}{RESET}")
return [], []
print(f"{CYAN}[*]{RESET} Total unique live subdomains: {BOLD}{len(all_subdomains)}{RESET}")
# Step 3: HTTP Probing
print(f"{CYAN}[*]{RESET} HTTP probing {len(all_subdomains)} subdomains...")
probed = http_probe.probe(all_subdomains, threads=args.threads)
print(f" {GREEN}{len(probed)} responded to HTTP/HTTPS{RESET}")
# Step 4: Takeover check (optional)
takeover_findings = []
if args.takeover_check:
print(f"{CYAN}[*]{RESET} Checking for subdomain takeover vulnerabilities...")
from takeover import checker
for sub in list(all_subdomains.keys()):
finding = checker.check_takeover(sub)
if finding:
takeover_findings.append(finding)
if takeover_findings:
print(f" {RED}⚠ {len(takeover_findings)} potential takeover(s) found!{RESET}")
else:
print(f" {GREEN}No obvious takeover vulnerabilities detected{RESET}")
return probed, takeover_findings
def main():
parser = argparse.ArgumentParser(
description="SubScope — Subdomain enumeration and asset discovery",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python subscope.py --domain example.com
python subscope.py --domain example.com --wordlist custom.txt --threads 100
python subscope.py --domain example.com --output results.json --csv results.csv
python subscope.py --scope scope.txt --output all_results.json
python subscope.py --domain example.com --no-bruteforce # crt.sh only
python subscope.py --domain example.com --takeover-check
Only test domains you own or have permission to test.
"""
)
target = parser.add_mutually_exclusive_group(required=True)
target.add_argument("--domain", help="Single target domain")
target.add_argument("--scope", help="Scope file with one domain per line")
parser.add_argument("--wordlist", "-w", help="Custom wordlist file (default: built-in)")
parser.add_argument("--threads", "-t", type=int, default=50, help="Thread count (default: 50)")
parser.add_argument("--no-bruteforce", action="store_true", help="Skip DNS brute force (crt.sh only)")
parser.add_argument("--no-crt", action="store_true", help="Skip crt.sh lookup")
parser.add_argument("--takeover-check", action="store_true", help="Check for subdomain takeover")
parser.add_argument("--output", "-o", help="Write JSON results to file")
parser.add_argument("--csv", help="Write CSV results to file")
args = parser.parse_args()
banner()
domains = []
if args.domain:
domains = [args.domain.lower().strip()]
else:
p = Path(args.scope)
if not p.exists():
print(f"{RED}[!] Scope file not found: {args.scope}{RESET}")
sys.exit(1)
domains = [line.strip().lower() for line in p.read_text().splitlines()
if line.strip() and not line.startswith("#")]
all_results = []
all_takeover = []
for domain in domains:
probed, takeover_findings = process_domain(domain, args)
all_results.extend(probed)
all_takeover.extend(takeover_findings)
table.print_results(probed, domain)
if takeover_findings:
table.print_takeover_findings(takeover_findings)
# Export
if args.output:
exporters.to_json(
domain=", ".join(domains),
results=all_results,
takeover_findings=all_takeover,
output_path=args.output
)
print(f"{CYAN}[*]{RESET} JSON saved: {args.output}")
if args.csv:
exporters.to_csv(all_results, args.csv)
print(f"{CYAN}[*]{RESET} CSV saved: {args.csv}")
if __name__ == "__main__":
main()