
How to Test Web App Security: A Developer's Guide to Finding Vulnerabilities Before Attackers Do
How to Test Web App Security: Why Every Developer Needs This Skill
If you're shipping a web application in 2024, knowing how to test web app security is no longer a nice-to-have — it's a survival skill. According to Verizon's 2024 Data Breach Investigations Report, web applications were the attack vector in over 60% of breaches. The OWASP Top 10 — the industry-standard classification of critical web application security risks — hasn't gotten shorter over the years. It's gotten more nuanced.
This is especially critical for indie hackers and developers using AI coding tools like Cursor, Bolt.new, and Lovable. AI-generated code can be functional and fast, but it often inherits insecure patterns from training data. A Stanford University study found that developers using AI code assistants produced significantly less secure code than those who didn't — and were more confident it was secure.
This guide gives you a practical, no-fluff framework for testing your web application's security, whether you're a solo developer or a small team.
Key Takeaways (TL;DR)
- Start with the OWASP Top 10 as your baseline checklist — it covers the vast majority of real-world web app vulnerabilities.
- Combine automated scanning with manual testing — scanners catch low-hanging fruit, but business logic flaws require human eyes.
- Test authentication, authorization, input handling, and data exposure — these four areas account for most critical vulnerabilities.
- Use real tools: OWASP ZAP, Burp Suite Community, browser DevTools, and specialized scanners like PreBreach for AI-generated codebases.
- Make security testing a habit, not a one-time event — integrate it into your development workflow.
Step 1: Understand What You're Testing For
Before you run a single scan, you need a threat model. The OWASP Top 10 (2021) is the best starting point. Here's what matters most for modern web apps:
| OWASP Category | What It Means | Real-World Example |
|---|---|---|
| A01: Broken Access Control | Users can act outside their intended permissions | CVE-2019-16759 — vBulletin RCE via improper access control |
| A02: Cryptographic Failures | Sensitive data exposed due to weak or missing encryption | The 2017 Equifax breach exposed 147M records due to unpatched vulnerabilities and poor data protection |
| A03: Injection | Untrusted data sent to an interpreter as part of a command | SQL injection remains the #1 technique in web application attacks per Akamai's State of the Internet report |
| A07: Identification and Authentication Failures | Broken login, session management, or credential handling | CVE-2023-42793 — JetBrains TeamCity auth bypass |
| A09: Security Logging and Monitoring Failures | Attacks go undetected because of insufficient logging | The SolarWinds breach went undetected for months due to monitoring gaps |
Step 2: How to Test Web App Security with Automated Scanning
Automated scanners are your first line of defense. They won't catch everything, but they'll find common misconfigurations, missing headers, known CVEs, and basic injection points quickly.
OWASP ZAP (Free, Open Source)
OWASP ZAP is the most widely used free web application security scanner. It acts as a man-in-the-middle proxy between your browser and your app, intercepting and analyzing every request.
Run a baseline scan from the command line:
# Pull the stable Docker image
docker pull ghcr.io/zaproxy/zaproxy:stable
# Run a baseline scan against your app
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t https://your-app.com \
-r report.htmlThis generates an HTML report flagging issues like missing security headers, cookie misconfigurations, and potential injection points. For a deeper scan, use zap-full-scan.py instead, which includes active attack testing.
Security Headers Check
Missing HTTP security headers are one of the most common findings. You can quickly test yours using browser DevTools or a curl command:
curl -I https://your-app.comYou should see headers like:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()The MDN Web Docs security headers reference is the authoritative source for what each header does and how to configure it.
Step 3: Manual Testing for Critical Vulnerabilities
Automated tools miss business logic flaws, complex authorization issues, and chained exploits. Manual testing is where you catch the vulnerabilities that actually lead to breaches.
Testing for Broken Access Control (OWASP A01)
Broken access control has been the #1 vulnerability category since OWASP's 2021 update. Here's a concrete example of what to test for, using a typical Node.js/Express API:
Vulnerable code — no authorization check:
// VULNERABLE: Any authenticated user can access any user's data
app.get('/api/users/:id/profile', authenticateToken, async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});Secure code — proper authorization:
// SECURE: Users can only access their own data (or admin)
app.get('/api/users/:id/profile', authenticateToken, async (req, res) => {
// Check that the authenticated user matches the requested resource
if (req.user.id !== req.params.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});How to test this manually:
- Log in as User A and note the session token.
- Copy an API request that fetches User A's data (e.g.,
/api/users/123/profile). - Change the ID to another user's ID (e.g.,
/api/users/456/profile). - If you get User B's data back, you have an Insecure Direct Object Reference (IDOR) vulnerability — classified under CWE-639.
This is the exact technique used in the 2021 Parler data scrape, where sequential API IDs allowed complete data enumeration.
Testing for SQL Injection (OWASP A03)
Vulnerable code — string concatenation in queries:
# VULNERABLE: Direct string interpolation in SQL
@app.route('/search')
def search():
query = request.args.get('q')
cursor.execute(f"SELECT * FROM products WHERE name LIKE '%{query}%'")
return jsonify(cursor.fetchall())Secure code — parameterized queries:
# SECURE: Parameterized query prevents injection
@app.route('/search')
def search():
query = request.args.get('q', '')
cursor.execute(
"SELECT * FROM products WHERE name LIKE %s",
(f'%{query}%',)
)
return jsonify(cursor.fetchall())How to test manually:
- Find any input field or URL parameter that interacts with data.
- Enter a single quote:
'— if you get a database error, injection is likely possible. - Try a boolean test:
' OR '1'='1— if it returns all records, you've confirmed SQL injection. - For more thorough testing, use sqlmap:
sqlmap -u "https://your-app.com/search?q=test" --batch --level=3
Testing for Cross-Site Scripting (XSS)
XSS remains pervasive. PortSwigger's research consistently ranks it among the most frequently discovered vulnerabilities in bug bounty programs.
Vulnerable code — rendering user input without escaping:
<!-- VULNERABLE: Directly inserting user input into HTML -->
<div class="comment">
<p>${comment.body}</p>
</div>Secure code — proper output encoding:
// SECURE: Using a framework's built-in escaping (React auto-escapes)
function Comment({ body }) {
return (
<div className="comment">
<p>{body}</p> {/* React escapes this automatically */}
</div>
);
}
// DANGEROUS: Never use dangerouslySetInnerHTML with user input
// <div dangerouslySetInnerHTML={{ __html: body }} /> // DO NOT DO THISQuick manual test payloads:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
javascript:alert('XSS')
" onfocus="alert('XSS')" autofocus="Enter these into every input field, URL parameter, and form in your application. If any of them execute, you have an XSS vulnerability (CWE-79).
Step 4: Test Authentication and Session Management
Authentication flaws are high-impact and common, especially in apps that roll their own auth instead of using established libraries. Here's a checklist:
- Test for brute force protection: Send 100 login attempts with wrong passwords. If there's no rate limiting or account lockout, attackers can crack credentials. Implement rate limiting using libraries like express-rate-limit.
- Test session tokens: After login, check that the session cookie has
Secure,HttpOnly, andSameSiteflags set. Inspect with browser DevTools → Application → Cookies. - Test password reset flows: Can you enumerate valid usernames from different error messages? Does the reset token expire? Can it be reused?
- Test for session fixation: Does the session ID change after login? If not, an attacker who sets a session cookie before authentication can hijack the session afterward.
Reference the OWASP Authentication Cheat Sheet for comprehensive guidance on implementing and testing authentication securely.
Step 5: Test for Sensitive Data Exposure
This is where many AI-generated apps fail silently. Check for:
- API responses that return too much data: Does your user endpoint return password hashes, internal IDs, or other users' emails? Fetch your own profile and inspect every field in the response.
- Source maps in production: If you're using React, Vue, or Angular, check whether
.mapfiles are accessible. These expose your entire source code. Test:curl https://your-app.com/static/js/main.js.map - Environment variables and secrets: Try accessing common paths like
/.env,/config.json,/.git/config,/api/debug. You'd be surprised how often these are publicly accessible. - Directory listing: Try accessing
/uploads/,/static/, or/backup/directly. If you see a file listing, your server is misconfigured.
Step 6: Dependency and Supply Chain Testing
Your code might be secure, but your dependencies might not be. The Snyk State of Open Source Security Report found that 84% of codebases contain at least one known vulnerability in their dependencies.
# For Node.js projects
npm audit
# For Python projects
pip-audit
# For comprehensive scanning with Snyk
npx snyk testPay special attention to critical CVEs. For example, CVE-2021-44228 (Log4Shell) had a CVSS score of 10.0 and affected countless applications through a single transitive dependency.
A Practical Web App Security Testing Checklist
Use this as a repeatable checklist every time you ship a feature or deploy:
- Run an automated scan (OWASP ZAP baseline or a tool like PreBreach that's designed for AI-generated web apps).
- Check security headers using
curl -Ior securityheaders.com. - Test every API endpoint for access control — change user IDs, tokens, and roles.
- Try injection payloads in all input fields and URL parameters.
- Inspect API responses for data leakage.
- Verify authentication — rate limiting, session flags, password policies.
- Audit dependencies with
npm auditorpip-audit. - Check for exposed files —
.env,.git, source maps, backups. - Test CORS configuration — send requests from an unauthorized origin and verify they're rejected.
- Review error handling — ensure stack traces and debug info aren't exposed in production.
Common Mistakes When Testing Web App Security
Only testing in development
Your production environment often has different configurations, CDN layers, and environment variables. Always test against a staging environment that mirrors production, or test production directly with non-destructive techniques.
Trusting the framework blindly
Frameworks like React, Django, and Rails have built-in protections, but they're not magic. React prevents XSS by default — unless you use dangerouslySetInnerHTML. Django's ORM prevents SQL injection — unless you use raw() or extra() with user input. Know where your framework's protections end.
Ignoring the OWASP Top 10's top entry
Developers spend disproportionate time on injection prevention but neglect access control. Broken access control is #1 on the OWASP Top 10 for a reason — it's the most common critical vulnerability found in real-world assessments, according to OWASP's own data gathered from over 234,000 applications.
Further Reading and Resources
- OWASP Web Security Testing Guide — the comprehensive, free, and open-source guide to web app security testing methodology.
- PortSwigger Web Security Academy — free, hands-on labs covering every major web vulnerability class.
- NIST Cybersecurity Framework — for understanding security testing within a broader risk management context.
- OWASP Cheat Sheet Series — quick-reference secure coding guides for specific topics.
Actionable Next Steps You Can Take Today
- Right now (5 minutes): Run
curl -I https://your-app.comand check your security headers. Fix any that are missing. - This afternoon (30 minutes): Run
npm auditorpip-auditon your project and update any packages with known vulnerabilities. - This week (2 hours): Pick three API endpoints in your app and manually test them for IDOR by changing resource IDs while authenticated as a different user.
- This month: Run a full OWASP ZAP scan against your staging environment and work through the report systematically.
- Ongoing: Complete at least the SQL injection and XSS labs on PortSwigger Web Security Academy — they're free and the best hands-on training available.
Security testing isn't a phase — it's a practice. The developers who ship secure applications aren't the ones who know every CVE by heart. They're the ones who test consistently, question assumptions, and treat every user input as hostile until proven otherwise.