My Web Application Pentest Methodology
My Web Application Pentest Methodology
After performing dozens of web application assessments and participating in bug bounty programs, I’ve developed a methodology that balances thoroughness with efficiency. This isn’t meant to be an exhaustive checklist — it’s a practical framework I use as a starting point for every engagement, adapted as needed based on the target’s technology stack and scope.
This methodology aligns with the OWASP Testing Guide v4.2 and PTES (Penetration Testing Execution Standard), but is distilled into what I’ve found actually matters in the field.
Phase 1: Reconnaissance and Scoping
Reconnaissance sets the foundation. Skipping it means missing attack surface, which means missing vulnerabilities.
Passive Reconnaissance
Before sending a single packet to the target, gather as much information as possible from public sources.
Subdomain Enumeration:
# Passive subdomain discovery
subfinder -d target.com -all -o subdomains.txt
amass enum -passive -d target.com -o amass_subs.txt
# Merge and deduplicate
cat subdomains.txt amass_subs.txt | sort -u > all_subs.txt
# Resolve and probe for live hosts
httpx -l all_subs.txt -o live_hosts.txt -sc -title -tech-detect
Technology Stack Identification:
# Wappalyzer CLI or browser extension for tech fingerprinting
whatweb https://target.com
# Check headers for framework/server info
curl -sI https://target.com | grep -iE 'server|x-powered|x-aspnet|x-framework'
Additional OSINT:
- Check the Wayback Machine for old endpoints, parameters, and JavaScript files
- Search GitHub/GitLab for leaked credentials, API keys, or internal documentation
- Review DNS records (TXT, MX, CNAME) for additional context on infrastructure
- Check certificate transparency logs via crt.sh
Active Reconnaissance
With a list of live hosts, begin active discovery.
Port Scanning:
nmap -sC -sV -p- --min-rate 1000 -oA nmap_full target.com
Directory and File Discovery:
feroxbuster -u https://target.com -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-x php,asp,aspx,jsp,html,js,json -t 50 --smart
JavaScript Analysis:
# Extract endpoints and secrets from JS files
cat live_hosts.txt | gau --threads 5 | grep '\.js$' | sort -u > js_files.txt
cat js_files.txt | while read url; do python3 LinkFinder.py -i "$url" -o cli; done > js_endpoints.txt
Phase 2: Mapping the Attack Surface
Before testing anything, I build a thorough map of the application. This phase involves manually browsing every feature while Burp Suite passively records traffic.
Systematic Application Walkthrough
- Register an account (or multiple with different privilege levels if possible)
- Use every feature — forms, uploads, search, profile settings, password reset, export/import
- Map all API endpoints by reviewing Burp’s sitemap and any API documentation
- Identify input vectors — every parameter, header, cookie, and upload field is a potential injection point
- Note the technology stack — framework, ORM, template engine, frontend library
Key Questions to Answer
- How does the application handle authentication? (Session cookies, JWTs, OAuth, API keys)
- What authorization model is in place? (RBAC, ABAC, or ad-hoc checks)
- Where does user input flow into server-side operations?
- Are there file upload/download features?
- Is there a WebSocket or GraphQL endpoint?
- What third-party integrations exist?
Phase 3: Authentication Testing
Authentication is the front gate. If it’s broken, everything behind it is exposed.
Tests I Always Run
Credential Policies:
- Test password complexity requirements (can you set
passwordas your password?) - Check for account lockout policies and rate limiting
- Test username enumeration through registration, login, and password reset flows
Session Management:
- Verify session tokens have sufficient entropy (Burp Sequencer)
- Test for session fixation — does the session ID change after login?
- Check session expiration — do tokens expire after logout and after idle timeout?
- Test concurrent session handling
Multi-Factor Authentication Bypass:
- Can you skip the MFA step by directly browsing to post-auth pages?
- Is the MFA code brute-forceable (no rate limiting, short code)?
- Does the “remember this device” feature properly validate?
Password Reset Flow:
- Is the reset token predictable or reusable?
- Does the token expire appropriately?
- Can you manipulate the Host header to redirect the reset link (host header injection)?
Phase 4: Injection Testing
This is where critical vulnerabilities live. I test methodically through each injection class.
SQL Injection
# Classic detection payloads
' OR 1=1--
' UNION SELECT NULL--
' AND SLEEP(5)--
# For each parameter, test:
# 1. Error-based (does a single quote cause a 500?)
# 2. Boolean-based (does OR 1=1 vs OR 1=2 change the response?)
# 3. Time-based (does SLEEP(5) cause a delay?)
# 4. UNION-based (can we extract data inline?)
When I find a potential injection point, I confirm it manually before using automation:
sqlmap -u "https://target.com/api/users?id=1" --batch --risk 3 --level 5 \
--cookie "session=abc123" --tamper=space2comment
Cross-Site Scripting (XSS)
I test three contexts for each reflected/stored input:
- HTML context:
<img src=x onerror=alert(1)> - Attribute context:
" onfocus=alert(1) autofocus=" - JavaScript context:
';alert(1)//
For DOM-based XSS, I review JavaScript source for dangerous sinks (innerHTML, eval, DOM manipulation methods that accept raw HTML) that consume user-controlled sources (location.hash, document.referrer, URL parameters).
Server-Side Template Injection (SSTI)
SSTI is often overlooked but devastating when present. I test with polyglot payloads:
{{7*7}}
${7*7}
<%= 7*7 %>
#{7*7}
If any of these return 49, I identify the template engine and escalate to RCE. A Jinja2 SSTI, for example, can be escalated:
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Other Injection Classes
- Command Injection: Test parameters that interact with the OS (
;id,|whoami) - LDAP Injection: If the app queries LDAP (
*)(objectClass=*) - XML External Entity (XXE): Test any XML input or file upload that parses XML
- Header Injection (CRLF):
%0d%0aInjected-Header:value
Phase 5: Business Logic Flaws
These are the vulnerabilities that scanners will never find. They require understanding the application’s intended workflow and then breaking the assumptions.
Common Patterns I Test
- Price manipulation — Can you modify the price of an item in the cart by intercepting and editing the request?
- Quantity/amount tampering — Does the app validate negative quantities or decimal values?
- Workflow bypasses — Can you skip steps in a multi-step process (e.g., skip payment, jump to order confirmation)?
- Race conditions — Can you redeem a coupon twice by sending concurrent requests?
- IDOR (Insecure Direct Object Reference) — Change
user_id=123touser_id=124and check if you can access another user’s data - Privilege escalation — Can a regular user access admin API endpoints by simply calling them directly?
IDOR Testing Approach
For every endpoint that references an object by ID:
GET /api/orders/1001 → Try /api/orders/1002
GET /api/users/me/profile → Try /api/users/other-uuid/profile
DELETE /api/documents/55 → Can a non-owner delete?
I test with both sequential IDs and UUIDs. UUIDs are not a security control — they’re often leaked in other responses, URLs, or API listings.
Phase 6: API Testing
Modern applications are API-driven, and APIs often have weaker security controls than the corresponding web UI.
API-Specific Tests
Discovery:
# Check for API documentation
curl -s https://target.com/swagger.json
curl -s https://target.com/api/docs
curl -s https://target.com/openapi.yaml
curl -s https://target.com/.well-known/openapi.yaml
# Fuzz for undocumented endpoints
ffuf -u https://target.com/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc all -fc 404
Authorization Testing:
- Test every endpoint with no token, expired token, and tokens from different privilege levels
- Check for mass assignment — can you set
is_admin=trueorrole=adminwhen updating your profile? - Test horizontal privilege escalation across all resource-accessing endpoints
Rate Limiting and Input Validation:
- Send oversized payloads — does the API handle a 10MB JSON body gracefully?
- Test type confusion — send a string where an integer is expected, arrays where strings are expected
- Check for verbose error messages that leak internal details
Phase 7: Reporting
A penetration test is only as good as its report. The report is the deliverable, and it needs to clearly communicate risk to both technical and non-technical stakeholders.
Report Structure I Follow
- Executive Summary — 1 page maximum. Business risk in plain language. This is what the CISO presents to the board.
- Scope and Methodology — What was tested, what wasn’t, and how.
- Findings — Each finding includes:
- Descriptive title
- Risk rating (Critical/High/Medium/Low/Informational) using CVSS 3.1
- Affected component(s)
- Detailed description of the vulnerability
- Steps to reproduce (clear enough that a developer can replicate it)
- Evidence (screenshots, request/response pairs)
- Business impact
- Remediation recommendation with specific, actionable guidance
- Positive Observations — What the application does well. This matters for stakeholder buy-in.
- Appendix — Raw tool output, full scan results, and methodology details.
Reporting Best Practices
- Write findings as you go. Don’t leave reporting until the end — you’ll forget context.
- Include impact, not just technical details. “SQL injection in search parameter” means nothing to a CTO. “An attacker can extract the entire customer database including payment information” communicates risk.
- Provide remediation, not just findings. Every vulnerability should have a clear, prioritized fix. Include code examples where helpful.
- Rate findings consistently. Use CVSS, but apply professional judgment. A theoretical RCE behind five layers of authentication isn’t the same as an unauthenticated SQLi on the login page.
Conclusion
Methodology is a starting point, not a straitjacket. The best testers adapt their approach based on what they discover. If reconnaissance reveals a GraphQL API, I’ll spend more time on query introspection and batching attacks. If the app is built on Django, I’ll focus on SSTI with Django templates and check for debug mode.
The key principles that stay constant:
- Enumerate before you exploit. Thorough reconnaissance means fewer missed vulnerabilities.
- Understand the application before testing it. Spend time using it as a real user.
- Think like a developer. What shortcuts might they have taken? What assumptions did they make?
- Document everything. Your report is the product. Make it count.