Testing your own application for vulnerabilities before someone else does is one of the most practical security skills a developer can have. This lesson covers hands-on techniques for finding common web application vulnerabilities using tools you already have or can set up in minutes.
Setting up your testing environment
What you need
| Tool | Purpose | Setup |
|---|---|---|
| Browser DevTools | Inspect requests, responses, cookies, JavaScript | Built into Chrome/Firefox |
| Burp Suite Community | Intercept and modify HTTP requests | Free download from PortSwigger |
| OWASP ZAP | Automated scanning + manual testing proxy | Free, open source |
| curl / httpie | Craft HTTP requests from the command line | Usually pre-installed |
You do not need all of these to start. Browser DevTools and curl are enough for most manual testing.
Important rules
- Only test applications you own or have explicit written permission to test.
- Test against a staging or local environment, not production (unless you have a specific reason and approval).
- Keep notes of everything you find, including the steps to reproduce.
Authentication testing
Default and weak credentials
Try default credentials on login forms, admin panels, and API endpoints:
admin / admin
admin / password
admin / <application_name>
test / test
root / root
Check if the application ships with default accounts that were not removed.
Username enumeration
Submit a valid username with a wrong password, then an invalid username:
POST /api/auth/login
{"username": "real_user", "password": "wrong"}
→ 401 "Invalid password"
POST /api/auth/login
{"username": "nonexistent", "password": "wrong"}
→ 401 "User not found"
If the error messages differ, attackers can enumerate valid usernames. The response should be identical for both cases:
→ 401 "Invalid username or password"
Also check for timing differences — some applications take longer to respond when the user exists (because they hash and compare the password) versus when they do not.
Brute force protection
Send 50 failed login attempts to the same account:
- Does the account get locked? After how many attempts?
- Does rate limiting kick in? Is it per-IP, per-account, or both?
- Can you bypass rate limiting by changing headers (X-Forwarded-For)?
# Quick brute force test with curl
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://staging.example.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"attempt_'$i'"}'
done
If all 50 return 401 with no rate limiting or lockout, that is a finding.
Session management
After logging in, inspect the session:
1. Is the session token in a cookie or localStorage?
2. Cookie flags: HttpOnly? Secure? SameSite?
3. Does the session token change after login? (Session fixation check)
4. How long until the session expires?
5. Does logging out actually invalidate the session on the server?
Test logout invalidation:
# Save the session token
TOKEN="eyJhbG..."
# Log out
curl -X POST https://staging.example.com/api/auth/logout \
-H "Authorization: Bearer $TOKEN"
# Try using the old token
curl https://staging.example.com/api/profile \
-H "Authorization: Bearer $TOKEN"
# Should return 401, not 200
Authorisation testing
Insecure Direct Object References (IDOR)
The most common authorisation vulnerability. Log in as User A and try to access User B's resources:
# Your order
GET /api/orders/1001 → 200 OK (your order)
# Someone else's order
GET /api/orders/1002 → 200 OK (not your order!) ← VULNERABILITY
Test this for every endpoint that takes an ID:
/api/users/{id}/api/orders/{id}/api/documents/{id}/api/invoices/{id}
Vertical privilege escalation
As a regular user, try accessing admin endpoints:
# As a regular user
curl https://staging.example.com/api/admin/users \
-H "Authorization: Bearer $USER_TOKEN"
# Should return 403, not 200
Function-level access control
Some endpoints check authentication but not authorisation:
# User can view their own profile
GET /api/users/me/profile → 200 OK
# Can they update someone else's profile?
PUT /api/users/other_user_id/profile
{"role": "admin"}
# Should return 403
Parameter manipulation
Modify request bodies to include fields that should not be user-controllable:
// Normal profile update
PUT /api/users/me
{"name": "Alice", "bio": "Developer"}
// Attacker adds extra fields
PUT /api/users/me
{"name": "Alice", "bio": "Developer", "role": "admin", "isVerified": true}
If the server blindly accepts all fields and writes them to the database, that is a mass assignment vulnerability.
Injection testing
SQL injection
Test any input that might be used in a database query:
# In search fields, login forms, URL parameters
' OR '1'='1
' UNION SELECT null,null,null--
' AND 1=CONVERT(int,(SELECT TOP 1 table_name FROM information_schema.tables))--
# In URL parameters
/api/products?category=electronics' OR '1'='1
/api/users?sort=name;DROP TABLE users--
Signs of SQL injection:
- Different response when adding
' OR '1'='1(returns all results instead of filtered) - Database error in the response (e.g., "You have an error in your SQL syntax")
- Time-based: adding
' AND SLEEP(5)--causes a 5-second delay
Cross-site scripting (XSS)
Inject script payloads into any field that is reflected back or stored:
<!-- Simple test -->
<script>alert('XSS')</script>
<!-- If angle brackets are filtered -->
" onmouseover="alert('XSS')
' onfocus='alert(1)' autofocus='
<!-- In URL parameters -->
/search?q=<img src=x onerror=alert(1)>
<!-- In markdown/rich text fields -->
[link](javascript:alert(1))
)
Command injection
Test fields that might be passed to system commands (file converters, PDF generators, URL fetchers):
; ls -la
| cat /etc/passwd
`whoami`
$(id)
Server-side request forgery (SSRF)
Test any URL input field (webhooks, image URLs, import URLs):
# Access internal metadata
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Access internal services
http://localhost:8080/admin
http://internal-service.local/api/secrets
# DNS rebinding
http://attacker-controlled-domain.com (resolves to 127.0.0.1)
File upload testing
If the application accepts file uploads:
1. Upload a .php/.jsp/.aspx file — does it get executed?
2. Upload a file with double extension: malicious.php.jpg
3. Upload a file with a manipulated Content-Type header
4. Upload an oversized file — is there a size limit?
5. Upload a file with path traversal in the filename: ../../etc/evil.txt
6. Upload an SVG with embedded JavaScript
# Upload a file with a manipulated name
curl -X POST https://staging.example.com/api/upload \
-F "[email protected];filename=../../etc/cron.d/evil" \
-H "Authorization: Bearer $TOKEN"
Automated scanning
OWASP ZAP baseline scan
# Run ZAP in Docker against your staging environment
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://staging.example.com \
-r report.html
The baseline scan spiders the application, passively analyses responses, and reports common issues. It does not perform active attacks (no injection testing). For active scanning:
docker run -t zaproxy/zap-stable zap-full-scan.py \
-t https://staging.example.com \
-r report.html
Nuclei
Nuclei runs community-maintained templates for thousands of known vulnerabilities:
# Scan for common web vulnerabilities
nuclei -u https://staging.example.com -t http/ -severity medium,high,critical
# Scan for specific issues
nuclei -u https://staging.example.com -t http/misconfiguration/
Reporting findings
For every vulnerability you find, document:
## Finding: IDOR in Orders API
**Severity:** High
**Endpoint:** GET /api/orders/{id}
**Description:** Any authenticated user can access any order by changing the order ID.
**Steps to reproduce:**
1. Log in as user A (user_id: 101)
2. Create an order → order ID 5001
3. Log in as user B (user_id: 102)
4. Request GET /api/orders/5001 with user B's token
5. User B receives user A's full order details
**Impact:** Any authenticated user can view all order details including
shipping address, payment method, and purchase history of any other user.
**Recommendation:** Add an ownership check:
```sql
SELECT * FROM orders WHERE id = $1 AND user_id = $2
## Testing methodology
A structured approach ensures you do not miss things:
1. **Map the application** — visit every page, note every endpoint, every form, every parameter
2. **Test authentication** — default creds, enumeration, brute force, session management
3. **Test authorisation** — IDOR, vertical escalation, function-level access
4. **Test injection** — SQLi, XSS, command injection, SSRF on every input
5. **Test file upload** — type, size, name, content validation
6. **Test business logic** — race conditions, price manipulation, step skipping
7. **Run automated scans** — ZAP, Nuclei for known issues
8. **Review findings** — deduplicate, prioritise, document
## Summary
Web application testing starts with understanding the application's attack surface — every endpoint, parameter, and input. Test authentication (brute force, session handling), authorisation (IDOR, privilege escalation), injection (SQLi, XSS, SSRF), and file uploads systematically. Use browser DevTools and curl for manual testing, and ZAP or Nuclei for automated scanning. Document every finding with reproduction steps, impact, and a recommended fix. The goal is finding and fixing issues before attackers do.
