DEV Community

ULNIT
ULNIT

Posted on

Bug Bounty Recon in 280 Lines of Pure Python — No Dependencies

The Hook

Most recon tools for bug bounty hunting come with a 30-minute setup ritual: install Go, clone a dozen repos, wrestle with module dependencies, and pray nothing breaks. I got tired of it. So I built a subdomain enumerator, live host prober, and vulnerability scanner — in 280 lines of pure Python, with zero external dependencies. It runs on a $35 Raspberry Pi and starts producing results in under 5 seconds.

Here's what it looks like in action:

# Enumerate subdomains for a target
$ python3 bb_kit.py enum tesla.com
[*] Enumerating tesla.com...
shop.tesla.com
api.tesla.com
auth.tesla.com
[...]
[+] Found 247 subdomains

# Probe which ones are actually alive
$ python3 bb_kit.py probe shop.tesla.com api.tesla.com auth.tesla.com
  https://shop.tesla.com     [200] nginx
  https://auth.tesla.com     [200] cloudflare
  http://api.tesla.com       [403] nginx/1.18

# Quick vulnerability scan
$ python3 bb_kit.py scan https://example.com
[*] Scanning https://example.com
  [!] EXPOSED: https://example.com/.git/config
  [!] SECURITY.TXT: https://example.com/.well-known/security.txt
Enter fullscreen mode Exit fullscreen mode

No Docker, no go install, no npm. Just Python 3.8+ and you're hunting.


Why Build This?

The bug bounty tool ecosystem is dominated by Go-based tools: subfinder, amass, httpx, nuclei. They're great, but:

Tool Language External Dependencies
subfinder Go Go toolchain, 50+ modules
amass Go Go toolchain, DNS libraries
httpx Go Go toolchain, HTTP stack
bb-automation-kit Python None (stdlib only)

I wanted something I could drop onto any machine — cloud VM, Raspberry Pi, even a locked-down corporate laptop — and have it work immediately. No compilation. No $GOPATH gymnastics. Just python3 bb_kit.py enum target.com and go.


How It Works (The Code)

The core is refreshingly simple. Here's the subdomain enumeration engine — it queries crt.sh (Certificate Transparency logs) and urlscan.io in parallel, deduplicates results, and returns a clean list:

def enum_subdomains(domain):
    """Discover subdomains from crt.sh and urlscan.io"""
    subs = set()

    # crt.sh — Certificate Transparency logs
    url = f"https://crt.sh/?q=%.{domain}&output=json"
    req = urllib.request.Request(url, headers={'User-Agent': 'BB-Kit/1.0'})
    data = json.loads(urllib.request.urlopen(req, timeout=20).read())
    for entry in data:
        name = entry.get('name_value', '').lower().strip()
        for n in name.split('\n'):
            n = n.strip().lstrip('*.')
            if n.endswith(domain) and n != domain:
                subs.add(n)

    # urlscan.io — passive DNS
    url = f"https://urlscan.io/api/v1/search/?q=domain:{domain}"
    req = urllib.request.Request(url, headers={'User-Agent': 'BB-Kit/1.0'})
    data = json.loads(urllib.request.urlopen(req, timeout=15).read())
    for result in data.get('results', []):
        page_domain = result.get('page', {}).get('domain', '')
        if page_domain.endswith(domain):
            subs.add(page_domain)

    return sorted(subs)
Enter fullscreen mode Exit fullscreen mode

The live prober uses ThreadPoolExecutor for parallel HTTP checks — 10 hosts at a time:

def probe_host(host):
    """Check if a host is live via HTTP/HTTPS"""
    for scheme in ['https', 'http']:
        try:
            req = urllib.request.Request(
                f"{scheme}://{host}",
                headers={'User-Agent': 'BB-Kit/1.0'}
            )
            resp = urllib.request.urlopen(req, timeout=5)
            return {
                'host': host,
                'url': f'{scheme}://{host}',
                'status': resp.status,
                'server': resp.headers.get('Server', 'unknown')
            }
        except urllib.error.HTTPError as e:
            return {
                'host': host, 'url': f'{scheme}://{host}',
                'status': e.code,
                'server': e.headers.get('Server', '?')
            }
        except:
            pass
    return {'host': host, 'url': None, 'status': 0, 'server': 'unreachable'}

def probe_hosts(hosts, threads=10):
    """Probe multiple hosts in parallel"""
    with ThreadPoolExecutor(max_workers=threads) as executor:
        futures = {executor.submit(probe_host, h): h for h in hosts}
        return [f.result() for f in as_completed(futures)]
Enter fullscreen mode Exit fullscreen mode

And the vulnerability scanner checks for low-hanging fruit — exposed .git directories, missing security.txt, and open redirects (all via stdlib urllib):

def quick_scan(url):
    """Run quick vulnerability checks"""
    findings = []

    # Check exposed .git
    try:
        r = urllib.request.urlopen(
            urllib.request.Request(f'{url}/.git/config',
                headers={'User-Agent': 'BB-Kit/1.0'}),
            timeout=5
        )
        if r.status == 200:
            body = r.read().decode('utf-8', errors='ignore').lower()
            if 'repositoryformatversion' in body:
                findings.append(f'EXPOSED: {url}/.git/config')
    except:
        pass

    # Check security.txt
    try:
        r = urllib.request.urlopen(
            urllib.request.Request(f'{url}/.well-known/security.txt',
                headers={'User-Agent': 'BB-Kit/1.0'}),
            timeout=5
        )
        if r.status == 200 and 'contact' in r.read().decode().lower():
            findings.append(f'SECURITY.TXT: {url}/.well-known/security.txt')
    except:
        pass

    return findings
Enter fullscreen mode Exit fullscreen mode

The entire toolkit clocks in at ~280 lines — including the argument parser and error handling.


Design Philosophy

  1. Stdlib only. urllib.request, json, threading, argparse. That's it. If you have Python, you can run this.
  2. Fire-and-forget. No config files. No .env. No API keys. The CLI is self-documenting.
  3. Pi-friendly. The toolkit was built and tested on a Raspberry Pi 4. Memory footprint during recon: under 30MB.
  4. Composable. Each command outputs clean text — pipe it into nuclei, feed it to ffuf, or chain it into your own scripts.

Real-World Usage

Here's a real workflow I run daily:

# 1. Enumerate subdomains for a bug bounty target
python3 bb_kit.py enum target.com > subs.txt

# 2. Probe which ones are live (parallel, 20 threads)
python3 bb_kit.py probe $(cat subs.txt) -t 20 > live.txt

# 3. Quick scan each live host for low-hanging vulns
while read line; do
    url=$(echo "$line" | awk '{print $1}')
    python3 bb_kit.py scan "$url"
done < live.txt

# 4. Feed live hosts into your favorite scanner
cat live.txt | awk '{print $1}' | httpx -silent | nuclei -t cves/
Enter fullscreen mode Exit fullscreen mode

From zero to hunting in under 10 seconds.


Get the Toolkit

The full source is open source on GitHub:

👉 github.com/ulnit/bb-automation-kit

Free tier: 100 domains/day, all three commands.

Pro ($15 one-time): Unlimited domains, concurrent scanning, priority support.

If you find this useful, consider supporting the project — every contribution helps keep 23 AI products running 24/7 on a single Raspberry Pi:

👉 paypal.me/ulnit


Built with Python stdlib. Runs on a $35 Raspberry Pi. Zero excuses not to start hunting.

Top comments (0)