Back to Articles

Sublist3r: The 10K-Star Subdomain Enumerator That Time Forgot

[ View on GitHub ]

Sublist3r: The 10K-Star Subdomain Enumerator That Time Forgot

Hook

With nearly 11,000 GitHub stars, Sublist3r remains one of the most popular reconnaissance tools in cybersecurity—yet it hasn't been meaningfully updated since 2017. This is the story of a tool that defined a category but got left behind.

Context

Before 2014, subdomain enumeration was a fragmented mess. Penetration testers would manually query different search engines, check DNS records, run bruteforce attacks with separate tools, and manually aggregate results. Each data source required different query syntax, parsing logic, and rate limit handling. A complete reconnaissance of a target domain could take hours of manual work across disconnected tools.

Sublist3r emerged as one of the first tools to systematically aggregate subdomain data from multiple OSINT sources into a single workflow. Created by Ahmed Aboul-Ela, it automated the tedious process of querying search engines (Google, Bing, Yahoo, Baidu, Ask), scraping security databases (VirusTotal, ThreatCrowd, DNSdumpster, Netcraft), and optionally running dictionary-based bruteforce attacks. The tool's simple Python implementation made it accessible to beginners while remaining powerful enough for professional penetration testers. It quickly became a standard component of reconnaissance workflows, earning its place in countless security training courses and certification exam study guides.

Technical Insight

Enumeration Sources

subdomains

subdomains

dictionary match

yes

no

CLI Interface

Main Controller

Subdomain Aggregator

Search Engine

Scrapers

Security Services

APIs

Subbrute Module

Deduplication &

Validation

Port Scan

Enabled?

TCP Port Scanner

Results Output

System architecture — auto-generated

Sublist3r's architecture reveals a straightforward but effective design pattern for OSINT aggregation. At its core, the tool implements a modular scraping engine where each data source is represented as a separate enumeration class inheriting from a base enumratorBase interface. Each enumerator handles HTTP requests, HTML parsing, and subdomain extraction for its specific source.

Here's a simplified example of how Sublist3r structures its enumeration modules:

class enumratorBase(object):
    def __init__(self, base_url, engine_name, domain, subdomains=None):
        self.domain = domain
        self.session = requests.Session()
        self.subdomains = [] if subdomains is None else subdomains
        self.base_url = base_url
        self.engine = engine_name
        
    def enumerate(self):
        # Override in subclasses
        raise NotImplementedError()
        
    def extract_domains(self, resp):
        # Regex to find subdomains in response
        links = re.findall(r'<a[^>]*href=["\']([^"\'>]+)', resp)
        for link in links:
            subdomain = self.parse_url(link)
            if subdomain and subdomain.endswith(self.domain):
                self.subdomains.append(subdomain)

class GoogleEnum(enumratorBase):
    def __init__(self, domain, subdomains=None):
        base_url = "https://www.google.com/search?q=site:{domain}"
        super(GoogleEnum, self).__init__(base_url, "Google", domain, subdomains)
        
    def enumerate(self):
        while self.page <= self.pages:
            url = self.base_url.format(domain=self.domain)
            resp = self.get_response(url)
            self.extract_domains(resp)
            self.page += 1

This pattern makes it trivial to add new data sources—simply create a new class that implements the enumerate() method and knows how to parse that source's specific HTML structure. The main orchestrator iterates through all enabled enumerators and aggregates their results into a deduplicated set.

The tool's hybrid discovery approach combines passive and active techniques. Passive enumeration queries existing databases without touching the target infrastructure, making it stealthy. When passive methods plateau, Sublist3r optionally invokes the integrated subbrute module for dictionary-based DNS bruteforce:

def bruteforce(domain, threads, savefile, silent, verbose, enable_bruteforce, engines):
    subdomains = []
    
    # First, passive enumeration
    for engine in engines:
        subdomains.extend(engine.enumerate())
    
    # Then optionally bruteforce
    if enable_bruteforce:
        wordlist = load_wordlist()  # Pre-optimized based on dnspop research
        with ThreadPoolExecutor(max_workers=threads) as executor:
            futures = []
            for word in wordlist:
                candidate = f"{word}.{domain}"
                future = executor.submit(resolve_dns, candidate)
                futures.append(future)
            
            for future in as_completed(futures):
                result = future.result()
                if result:
                    subdomains.append(result)
    
    return list(set(subdomains))  # Deduplicate

The multithreading implementation is crude but effective—it spawns worker threads that independently resolve DNS queries, dramatically speeding up bruteforce operations compared to sequential resolution. On modern hardware with sufficient bandwidth, this approach can test thousands of subdomain candidates per minute.

Sublist3r also includes optional TCP port scanning through integration with the socket library, allowing immediate verification of which discovered subdomains have services running on common ports (80, 443, 21, 22, etc.). This transforms raw subdomain lists into actionable intelligence about attack surface:

def port_scan(subdomains, ports):
    for subdomain in subdomains:
        open_ports = []
        for port in ports:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(1)
            result = sock.connect_ex((subdomain, port))
            if result == 0:
                open_ports.append(port)
            sock.close()
        
        if open_ports:
            print(f"{subdomain}: {open_ports}")

The minimalist dependency footprint deserves attention. Beyond Python's standard library, Sublist3r requires only requests (HTTP), dnspython (DNS resolution), and argparse (CLI parsing). This lean approach makes deployment trivial—no complex dependency chains, no version conflicts, no Docker containers required. You can drop the script onto any system with Python and be enumerating subdomains within seconds.

Programmatic usage is equally straightforward. Rather than parsing CLI output, developers can import Sublist3r as a module and directly access the subdomain list:

import sublist3r

subdomains = sublist3r.main(
    domain='example.com',
    threads=40,
    savefile=None,
    ports=None,
    silent=True,
    verbose=False,
    enable_bruteforce=False,
    engines=None  # Use all engines
)

for subdomain in subdomains:
    # Process each discovered subdomain
    analyze_target(subdomain)

This API design made Sublist3r easy to integrate into larger security automation frameworks and custom reconnaissance pipelines.

Gotcha

The elephant in the room: Sublist3r's last significant update was in 2017. In internet time, that's geological epochs ago. Many of its core data sources have fundamentally changed how they present data, implemented anti-scraping measures, or deprecated the endpoints Sublist3r targets. Google has repeatedly modified its search result HTML structure, breaking the regex-based parsing. VirusTotal moved to an API-only model requiring authentication. Several search engines now aggressively rate-limit automated queries, causing enumerations to fail or return incomplete results.

The Python 2.7 legacy support creates practical problems for modern users. While the code technically runs on Python 3, it wasn't designed with Python 3 idioms in mind, and you'll encounter deprecation warnings and encoding issues with non-ASCII domain names. The project's dependencies are pinned to old versions that may have known security vulnerabilities—ironic for a security tool. Installing it on current operating systems often requires troubleshooting compatibility issues that didn't exist when the tool was actively maintained.

Beyond technical debt, Sublist3r misses entire categories of modern subdomain discovery. Certificate Transparency logs—a goldmine for passive reconnaissance introduced widely after 2016—aren't queried. Cloud service enumeration techniques for AWS, Azure, and GCP infrastructure aren't included. Modern DNS-over-HTTPS resolvers aren't leveraged. The wordlist for bruteforce attacks, while based on legitimate research, hasn't been updated with patterns from contemporary naming conventions like microservices and containerized deployments. In 2024, you're likely to miss significant portions of a target's attack surface if Sublist3r is your only enumeration tool.

Verdict

Use if: You're learning subdomain enumeration concepts and want to understand the foundational approaches that influenced modern tools. The codebase is small enough to read completely in an afternoon, making it excellent for educational purposes. Also consider it for quick ad-hoc reconnaissance of legacy infrastructure where targets haven't updated their web presence since 2017—surprisingly common in enterprise environments. Skip if: You need reliable reconnaissance for professional penetration testing, bug bounty hunting, or security assessments of modern infrastructure. The broken scrapers and missing data sources mean you'll waste time debugging why results are incomplete. Instead, invest that time learning actively maintained alternatives like Amass or subfinder from ProjectDiscovery, which leverage certificate transparency logs, modern APIs, and receive regular updates. For production security work, Sublist3r's historical importance doesn't compensate for its technical obsolescence.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/aboul3la-sublist3r.svg)](https://starlog.is/api/badge-click/developer-tools/aboul3la-sublist3r)