156 lines
6.0 KiB
Python
156 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
# Nmap Integrator for Subdomains - v1.0
|
|
# Integrates Nmap port scanning with subdomain lists (e.g., from Sublist3r).
|
|
# Filters live subdomains (DNS + HTTP check), then scans for open ports.
|
|
# Usage: python nmap_integrator.py -i subdomains.txt -o nmap_results.xml
|
|
# Requires: pip install requests dnspython; nmap installed on system
|
|
|
|
import argparse
|
|
import sys
|
|
import subprocess
|
|
import xml.etree.ElementTree as ET
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
import requests
|
|
import dns.resolver
|
|
import dns.exception
|
|
import tempfile
|
|
import os
|
|
|
|
def is_dns_live(subdomain):
|
|
"""Check if subdomain resolves to an IP."""
|
|
try:
|
|
dns.resolver.resolve(subdomain, 'A')
|
|
return True
|
|
except (dns.exception.DNSException, Exception):
|
|
return False
|
|
|
|
def is_http_live(subdomain, timeout=5):
|
|
"""Check if subdomain responds to HTTP/HTTPS."""
|
|
for protocol in ['http', 'https']:
|
|
url = f"{protocol}://{subdomain}"
|
|
try:
|
|
resp = requests.get(url, timeout=timeout, verify=False, allow_redirects=True)
|
|
if resp.status_code > 0:
|
|
return True
|
|
except requests.RequestException:
|
|
continue
|
|
return False
|
|
|
|
def check_live(subdomain, dns_only=False, timeout=5):
|
|
"""Full live check: DNS + optional HTTP."""
|
|
if not is_dns_live(subdomain):
|
|
return False
|
|
if dns_only:
|
|
return True
|
|
return is_http_live(subdomain, timeout)
|
|
|
|
def run_nmap_scan(subdomain, ports='top-1000', output_dir=None, output_format='xml'):
|
|
"""Run Nmap scan on a subdomain and return results."""
|
|
if output_dir:
|
|
output_file = os.path.join(output_dir, f"{subdomain}_nmap.{output_format}")
|
|
else:
|
|
output_file = f"{subdomain}_nmap.{output_format}"
|
|
|
|
cmd = [
|
|
'nmap', '-sV', '-sC', # Service version + script scan
|
|
f'-p{ports}', # Ports to scan
|
|
f'--open', # Only show open ports
|
|
f'-o{output_format}', output_file, # Output format
|
|
subdomain
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) # 5-min timeout per host
|
|
if result.returncode == 0:
|
|
print(f"[SCAN] {subdomain}: Scan complete. Output: {output_file}")
|
|
return output_file
|
|
else:
|
|
print(f"[ERROR] {subdomain}: Nmap failed - {result.stderr}")
|
|
return None
|
|
except subprocess.TimeoutExpired:
|
|
print(f"[TIMEOUT] {subdomain}: Scan timed out")
|
|
return None
|
|
except FileNotFoundError:
|
|
print("[ERROR] Nmap not found. Install Nmap and ensure it's in PATH.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
def parse_nmap_xml(xml_file):
|
|
"""Parse Nmap XML for summary (open ports)."""
|
|
try:
|
|
tree = ET.parse(xml_file)
|
|
root = tree.getroot()
|
|
host = root.find('host')
|
|
if host is None:
|
|
return []
|
|
ports = []
|
|
for port in host.findall('.//port[@state="open"]'):
|
|
port_id = port.get('portid')
|
|
service = port.find('service')
|
|
service_name = service.get('name') if service is not None else 'unknown'
|
|
ports.append(f"{port_id}/{service_name}")
|
|
return ports
|
|
except ET.ParseError:
|
|
return []
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Integrate Nmap port scanning with subdomain lists.")
|
|
parser.add_argument('-i', '--input', required=True, help="Input file with subdomains (one per line)")
|
|
parser.add_argument('-o', '--output-dir', help="Directory for Nmap output files (default: current dir)")
|
|
parser.add_argument('-t', '--threads', type=int, default=10, help="Threads for live check (default: 10); Nmap is sequential")
|
|
parser.add_argument('--dns-only', action='store_true', help="Only check DNS (faster, skip HTTP)")
|
|
parser.add_argument('--ports', default='top-1000', help="Nmap ports (default: top-1000)")
|
|
parser.add_argument('--timeout', type=int, default=5, help="HTTP timeout in seconds (default: 5)")
|
|
parser.add_argument('--summary', action='store_true', help="Print summary of open ports after scanning")
|
|
args = parser.parse_args()
|
|
|
|
# Read subdomains
|
|
try:
|
|
with open(args.input, 'r') as f:
|
|
subdomains = [line.strip() for line in f if line.strip()]
|
|
except FileNotFoundError:
|
|
print(f"Error: Input file '{args.input}' not found.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"[INFO] Filtering {len(subdomains)} subdomains for live hosts...")
|
|
|
|
# Filter live subdomains
|
|
live_subdomains = []
|
|
with ThreadPoolExecutor(max_workers=args.threads) as executor:
|
|
futures = {executor.submit(check_live, sub, args.dns_only, args.timeout): sub for sub in subdomains}
|
|
for future in as_completed(futures):
|
|
sub = futures[future]
|
|
try:
|
|
if future.result():
|
|
live_subdomains.append(sub)
|
|
print(f"[LIVE] {sub}")
|
|
else:
|
|
print(f"[DEAD] {sub}")
|
|
except Exception as e:
|
|
print(f"[ERROR] {sub}: {e}", file=sys.stderr)
|
|
|
|
print(f"[INFO] Found {len(live_subdomains)} live subdomains. Starting Nmap scans...")
|
|
|
|
# Create output dir if specified
|
|
if args.output_dir:
|
|
os.makedirs(args.output_dir, exist_ok=True)
|
|
|
|
# Run Nmap sequentially (to avoid overwhelming the network; parallelize if needed)
|
|
scan_results = {}
|
|
for subdomain in live_subdomains:
|
|
output_file = run_nmap_scan(subdomain, args.ports, args.output_dir)
|
|
if output_file and args.summary:
|
|
open_ports = parse_nmap_xml(output_file)
|
|
if open_ports:
|
|
scan_results[subdomain] = open_ports
|
|
print(f"[PORTS] {subdomain}: {', '.join(open_ports)}")
|
|
|
|
if args.summary and scan_results:
|
|
print("\n[SUMMARY] Open Ports by Host:")
|
|
for host, ports in scan_results.items():
|
|
print(f"{host}: {', '.join(ports)}")
|
|
|
|
print(f"[COMPLETE] Scanned {len(live_subdomains)} hosts. Check output files for details.")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|