Files
NeuroSploit/tools/recon/subdomain_finder.py
2026-01-01 19:26:00 -03:00

128 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
SubdomainFinder - Discovers subdomains using multiple techniques
"""
import logging
import requests
import socket
from typing import Dict, List, Set
import re
logger = logging.getLogger(__name__)
class SubdomainFinder:
"""
A class for finding subdomains of a given domain.
Uses Certificate Transparency logs, DNS brute-forcing, and common patterns.
"""
def __init__(self, config: Dict):
"""
Initializes the SubdomainFinder.
Args:
config (Dict): The configuration dictionary for the framework.
"""
self.config = config
self.common_subdomains = [
'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'ns2',
'webdisk', 'ns', 'cpanel', 'whm', 'autodiscover', 'autoconfig', 'test',
'dev', 'staging', 'api', 'admin', 'portal', 'beta', 'demo', 'vpn',
'blog', 'shop', 'store', 'forum', 'support', 'm', 'mobile', 'cdn',
'static', 'assets', 'img', 'images', 'git', 'jenkins', 'jira'
]
logger.info("SubdomainFinder initialized")
def find(self, target: str) -> List[str]:
"""
Finds subdomains for a given domain using multiple techniques.
Args:
target (str): The domain name to search subdomains for.
Returns:
List[str]: A list of found subdomains.
"""
logger.info(f"Starting subdomain enumeration for {target}")
# Remove protocol if present
domain = target.replace('http://', '').replace('https://', '').split('/')[0]
found_subdomains: Set[str] = set()
# Method 1: Certificate Transparency logs
ct_subdomains = self._check_crtsh(domain)
found_subdomains.update(ct_subdomains)
# Method 2: Common subdomain brute-forcing
brute_subdomains = self._brute_force_common(domain)
found_subdomains.update(brute_subdomains)
result = sorted(list(found_subdomains))
logger.info(f"Found {len(result)} subdomains for {domain}")
return result
def _check_crtsh(self, domain: str) -> List[str]:
"""
Query Certificate Transparency logs via crt.sh
"""
subdomains = []
try:
url = f"https://crt.sh/?q=%.{domain}&output=json"
logger.info(f"Querying crt.sh for {domain}")
response = requests.get(url, timeout=15)
if response.status_code == 200:
data = response.json()
for entry in data:
name_value = entry.get('name_value', '')
# Split by newlines (crt.sh returns multiple names per entry sometimes)
names = name_value.split('\n')
for name in names:
name = name.strip().lower()
# Remove wildcards
name = name.replace('*.', '')
# Only include valid subdomains for this domain
if name.endswith(domain) and name != domain:
subdomains.append(name)
logger.info(f"Found {len(subdomains)} subdomains from crt.sh")
else:
logger.warning(f"crt.sh returned status code {response.status_code}")
except requests.RequestException as e:
logger.warning(f"Error querying crt.sh: {e}")
except Exception as e:
logger.error(f"Unexpected error in crt.sh query: {e}")
return list(set(subdomains)) # Remove duplicates
def _brute_force_common(self, domain: str) -> List[str]:
"""
Brute-force common subdomain names
"""
found = []
logger.info(f"Brute-forcing common subdomains for {domain}")
for subdomain in self.common_subdomains:
full_domain = f"{subdomain}.{domain}"
if self._check_subdomain_exists(full_domain):
found.append(full_domain)
logger.debug(f"Found subdomain: {full_domain}")
logger.info(f"Found {len(found)} subdomains via brute-force")
return found
def _check_subdomain_exists(self, subdomain: str) -> bool:
"""
Check if a subdomain exists by attempting to resolve it
"""
try:
socket.gethostbyname(subdomain)
return True
except socket.gaierror:
return False
except Exception as e:
logger.debug(f"Error checking {subdomain}: {e}")
return False