
How to Do Web Application Security Testing: A Practical Guide for Developers
Key Takeaways (TL;DR)
- Web application security testing is the process of systematically probing your app for vulnerabilities like injection flaws, broken authentication, and misconfigurations — before an attacker does it for you.
- The OWASP Web Security Testing Guide (WSTG) is the gold-standard methodology, covering 90+ test categories.
- You don't need to be a penetration tester. Developers can catch the majority of critical vulnerabilities using a combination of automated scanning, manual code review, and secure coding patterns.
- Every step below includes real code examples showing vulnerable vs. secure patterns, specific CVE references, and tool recommendations you can use today.
What Is Web Application Security Testing?
Web application security testing is the practice of evaluating a web application's security posture by identifying vulnerabilities, misconfigurations, and logic flaws that could be exploited by malicious actors. It encompasses techniques ranging from automated vulnerability scanning to manual penetration testing and source code review.
According to Verizon's 2023 Data Breach Investigations Report, web applications were the primary attack vector in over 80% of breaches involving hacking. The IBM Cost of a Data Breach Report 2023 puts the average cost of a data breach at $4.45 million — a number that's devastating for any company, but existential for an indie hacker or small startup.
If you're building with AI coding tools like Cursor, Bolt.new, or Lovable, understanding how to do web application security testing is not optional. AI-generated code can introduce subtle vulnerabilities that look correct on the surface but fail catastrophically under adversarial conditions.
Why Developers — Not Just Pentesters — Need This Skill
Traditional security testing was siloed: developers wrote code, and a separate security team tested it months later. That model is broken. The NIST Secure Software Development Framework (SSDF) now explicitly recommends integrating security testing into the development lifecycle, not bolting it on afterward.
For indie developers shipping fast, this means you are the security team. The good news: most critical web vulnerabilities follow well-documented patterns, and learning to test for them is a high-leverage skill.
A Step-by-Step Methodology for Web Application Security Testing
The following methodology is adapted from the OWASP Web Security Testing Guide (WSTG) v4.2, simplified for developer-led testing. It covers the categories responsible for the vast majority of real-world breaches.
Step 1: Reconnaissance and Information Gathering
Before testing a single endpoint, map your application's attack surface. This means identifying every URL, API endpoint, authentication mechanism, and third-party integration.
- Enumerate endpoints: Review your route definitions (e.g.,
routes.py,next.config.js, Express router files). Document every route, its HTTP method, and its authentication requirements. - Identify technology stack: Note your framework, ORM, authentication library, and hosting provider. Each has known vulnerability patterns (e.g., CWE-1321: Improperly Controlled Modification of Object Prototype Attributes is specific to JavaScript).
- Check for information leakage: Visit
/robots.txt,/.env,/.git/config, and common backup file paths. The 2022 Toyota breach exposed source code because a contractor left a.gitdirectory publicly accessible.
Step 2: Test for Injection Vulnerabilities
Injection flaws remain the most dangerous class of web vulnerabilities. OWASP ranks Injection as #3 in its 2021 Top 10, and SQL injection alone was responsible for the catastrophic 2019 Capital One breach (which involved SSRF and command injection via a misconfigured WAF).
Vulnerable code — SQL Injection in Node.js (Express + raw SQL):
// VULNERABLE: Direct string concatenation in SQL query
app.get('/api/users', async (req, res) => {
const { username } = req.query;
const query = `SELECT * FROM users WHERE username = '${username}'`;
const result = await db.query(query);
res.json(result.rows);
});An attacker sends ?username=' OR '1'='1 and dumps your entire user table.
Secure code — Parameterized query:
// SECURE: Parameterized query prevents SQL injection
app.get('/api/users', async (req, res) => {
const { username } = req.query;
const query = 'SELECT * FROM users WHERE username = $1';
const result = await db.query(query, [username]);
res.json(result.rows);
});This pattern works because the database driver treats $1 as a value, never as executable SQL. This is documented in the node-postgres parameterized query documentation and recommended by OWASP's Query Parameterization Cheat Sheet.
How to test: For every input field and URL parameter, try payloads like ' OR 1=1--, "; DROP TABLE users;--, and {{7*7}} (for template injection). Tools like sqlmap automate SQL injection detection.
Step 3: Test Authentication and Session Management
Identification and Authentication Failures are OWASP Top 10 #7. Common failures include:
- No rate limiting on login endpoints (enabling brute force attacks)
- Session tokens in URLs instead of secure cookies
- Missing
HttpOnly,Secure, orSameSitecookie flags - JWTs with
alg: nonevulnerability (CVE-2018-0114)
Vulnerable code — JWT without algorithm verification (Node.js):
// VULNERABLE: Accepts any algorithm, including 'none'
const jwt = require('jsonwebtoken');
app.use((req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
const decoded = jwt.verify(token, publicKey); // No algorithms specified!
req.user = decoded;
next();
});Secure code — Explicit algorithm restriction:
// SECURE: Only accepts RS256, rejects 'none' and HS256 with public key
const jwt = require('jsonwebtoken');
app.use((req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
try {
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
});This is documented in the jsonwebtoken library's verification options and is a critical defense against JWT algorithm confusion attacks described in detail by PortSwigger's JWT algorithm confusion research.
How to test: Use Burp Suite to intercept authentication requests. Try modifying the JWT header to use "alg": "none". Test for session fixation by copying session cookies between browsers. Verify that cookies have the correct flags using browser DevTools.
Step 4: Test for Cross-Site Scripting (XSS)
XSS remains alarmingly prevalent. OWASP categorizes XSS under Injection, and CWE-79 (Improper Neutralization of Input During Web Page Generation) is consistently one of the most commonly reported weaknesses. In 2023, a stored XSS vulnerability in the MOVEit Transfer application (CVE-2023-34362, which combined XSS with SQLi) impacted hundreds of organizations.
Vulnerable code — React with dangerouslySetInnerHTML:
// VULNERABLE: Renders user-controlled HTML without sanitization
function UserComment({ comment }) {
return <div dangerouslySetInnerHTML={{ __html: comment.body }} />;
}Secure code — Using a sanitization library:
// SECURE: Sanitize HTML before rendering
import DOMPurify from 'dompurify';
function UserComment({ comment }) {
const cleanHTML = DOMPurify.sanitize(comment.body);
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
// Even better: avoid dangerouslySetInnerHTML entirely
function UserComment({ comment }) {
return <div>{comment.body}</div>; // React auto-escapes this
}React escapes content by default when you use JSX expressions — this is documented in the React documentation on dangerouslySetInnerHTML. The key rule: never use dangerouslySetInnerHTML with unsanitized user input.
How to test: Insert payloads like <script>alert(1)</script>, <img src=x onerror=alert(1)>, and javascript:alert(1) into every input field, URL parameter, and anywhere user data is reflected in the page.
Step 5: Test for Broken Access Control
Broken Access Control is OWASP's #1 risk for 2021, present in 94% of applications tested. The most common pattern: Insecure Direct Object References (IDOR), where changing an ID in a URL or request body gives you access to another user's data.
Vulnerable code — IDOR in an API endpoint:
// VULNERABLE: No authorization check — any authenticated user can
// access any other user's data by changing the ID
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
const profile = await db.query(
'SELECT * FROM profiles WHERE user_id = $1',
[req.params.id]
);
res.json(profile.rows[0]);
});Secure code — Authorization check:
// SECURE: Verify the authenticated user owns the resource
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
if (req.user.id !== parseInt(req.params.id, 10)) {
return res.status(403).json({ error: 'Forbidden' });
}
const profile = await db.query(
'SELECT * FROM profiles WHERE user_id = $1',
[req.params.id]
);
res.json(profile.rows[0]);
});How to test: Log in as User A, capture a request to access User A's resource, then change the user ID to User B's. If you get User B's data, you have a broken access control vulnerability. Test every endpoint that takes a resource ID as input.
Step 6: Test for Security Misconfigurations
Security Misconfiguration (OWASP #5) covers a broad range of issues:
| Misconfiguration | Risk | How to Test |
|---|---|---|
| Debug mode enabled in production | Stack traces leak internal paths, library versions | Trigger a 500 error and inspect the response body |
| Default credentials on admin panels | Full application takeover | Try admin/admin, admin/password on /admin routes |
| Missing security headers | XSS, clickjacking, MIME sniffing | Use securityheaders.com to scan |
| Overly permissive CORS | Cross-origin data theft | Check Access-Control-Allow-Origin: * on authenticated endpoints |
Exposed .env files or source maps | Database credentials, API key leakage | Request /.env, /main.js.map directly |
The MDN Web Docs on Content-Security-Policy are the definitive reference for implementing security headers correctly.
Step 7: Test for Vulnerable and Outdated Components
Vulnerable and Outdated Components (OWASP #6) was the attack vector behind the devastating 2017 Equifax breach, which exploited a known Apache Struts vulnerability (CVE-2017-5638) that had a patch available two months before the breach.
How to test:
- Run
npm audit(Node.js) orpip audit(Python) to identify known vulnerabilities in your dependencies. - Use Snyk or GitHub Dependabot for continuous monitoring.
- Check your dependencies against the NIST National Vulnerability Database.
- Pay special attention to transitive dependencies — the node-ipc protestware incident in 2022 showed how deeply nested dependencies can introduce risk.
Tools for Web Application Security Testing
A practical testing workflow combines automated tools with manual verification:
| Tool | Type | Best For | Cost |
|---|---|---|---|
| OWASP ZAP | DAST (Dynamic) | Automated scanning, spidering, active attacks | Free / open source |
| Burp Suite Community | Proxy / DAST | Intercepting and modifying requests, manual testing | Free (Community) / Paid (Pro) |
| Semgrep | SAST (Static) | Finding vulnerable code patterns in source code | Free tier available |
| TruffleHog | Secrets scanner | Finding leaked API keys and credentials in code/git history | Free / open source |
| PreBreach | AI-powered DAST | Automated OWASP Top 10 scanning designed for indie devs | Free tier available |
For developers building with AI tools, automated scanning is especially important because AI-generated code may contain subtle vulnerabilities that pass code review. A tool like PreBreach can quickly surface OWASP Top 10 issues before your app goes live — acting as a security-focused second opinion on your AI-assisted codebase.
Common Mistakes When Learning How to Do Web Application Security Testing
- Only testing the happy path: Security testing is adversarial. Think about what happens when inputs are empty, extremely long, contain special characters, or are in unexpected formats.
- Ignoring business logic flaws: Automated scanners cannot find logic bugs like "user can apply a discount code unlimited times" or "user can skip the payment step." These require manual testing with an understanding of the application's intended behavior.
- Testing only in development: Your production environment may have different configurations, CDN behavior, and security headers. Always verify your security posture against production (or a staging environment that mirrors it).
- Treating security as a one-time event: New vulnerabilities are disclosed daily. The NIST NVD published over 25,000 CVEs in 2023 alone. Security testing must be continuous.
A Practical Security Testing Checklist
Use this checklist as a starting point every time you ship a feature or prepare for launch:
- Input validation: Every user input is validated on the server side (not just the client).
- Parameterized queries: All database queries use parameterized statements or an ORM — never string concatenation.
- Authentication: Login endpoints have rate limiting. Passwords are hashed with bcrypt or Argon2 (OWASP Password Storage Cheat Sheet).
- Authorization: Every API endpoint verifies the authenticated user has permission to access the requested resource.
- Security headers: CSP, X-Content-Type-Options, X-Frame-Options, and Strict-Transport-Security are set.
- Dependencies:
npm audit/pip auditreturns zero critical or high vulnerabilities. - Secrets: No API keys, database credentials, or tokens in source code or git history.
- Error handling: Production error responses do not leak stack traces, file paths, or database schema.
- HTTPS: All traffic is served over TLS. HTTP redirects to HTTPS.
- CORS:
Access-Control-Allow-Originis set to specific trusted origins, not*, on authenticated endpoints.
Actionable Next Steps You Can Take Today
- Run
npm auditorpip auditright now. Fix any critical or high-severity vulnerabilities in your dependencies. This takes five minutes and is the single highest-ROI security action. - Scan your security headers. Go to securityheaders.com, enter your production URL, and fix any missing headers.
- Test your API endpoints for IDOR. Log in as one user, capture a request, and try changing resource IDs to access another user's data. This is the most common critical vulnerability in modern web apps.
- Read the OWASP Web Security Testing Guide. Even skimming the table of contents will give you a mental model of what professional pentesters look for.
- Set up automated scanning in your CI/CD pipeline. Whether it's Semgrep for SAST, OWASP ZAP for DAST, or a purpose-built tool — automated, continuous testing catches regressions before they reach production.
Learning how to do web application security testing is not about becoming a full-time security researcher. It's about building the habit of thinking adversarially about your own code. The vulnerabilities described in this guide account for the vast majority of real-world breaches — and every one of them is preventable with the patterns and tools outlined above.