Web App Security Testing: A Practical Guide for 2026
How to test a web application for vulnerabilities: six testing phases covering reconnaissance, authentication, injection, transport security, security headers, and access control, plus a pre-deploy checklist and tool recommendations.
Key Takeaway
Web app security testing covers six attack surfaces: reconnaissance, authentication, injection, transport security, security headers, and access control. An automated scanner handles transport and header checks in 60 seconds. The manual phases - authentication logic, IDOR, and access control chains - take a few hours and prevent the majority of real-world breaches.
Web app security testing is the practice of finding vulnerabilities in a web application before attackers do. It covers everything from checking your TLS configuration and HTTP response headers to probing authentication flows, testing for injection flaws, and verifying that access controls hold up under adversarial input. This guide walks through the six main testing phases, lists the tools used in each, and ends with a pre-deploy checklist you can run before every release.
What Is Web App Security Testing?
Web app security testing — also called web application penetration testing or web app pentesting — is a structured process for identifying security weaknesses in a web application. It differs from a vulnerability scan (which is automated and passive) in that it includes manual testing, business-logic checks, and chained attack scenarios that automated tools miss.
Most real-world breaches exploit a combination of issues: a misconfigured CORS policy that lets an attacker make authenticated requests, combined with an IDOR flaw that exposes another user's data. Each individual finding might rate as medium severity. Together they are critical. This is why application security testing needs a methodology, not just a scanner.
The six phases below follow the OWASP Web Security Testing Guide (WSTG), the industry-standard framework for web app security assessments.
Phase 1: Reconnaissance
Reconnaissance is passive information gathering. You are not sending attack payloads yet — you are mapping what the application exposes before an authenticated user ever touches it.
Technology fingerprinting
HTTP response headers often reveal the server software and framework version. The Server header might say Apache/2.4.51, and X-Powered-By might say PHP/8.0.3. These headers are pure information leakage — they tell an attacker exactly which CVEs to look up. Disable them in production.
# Check what your server exposes
curl -s -I https://example.com | grep -i "server\|x-powered-by"Certificate transparency
Every certificate issued for your domain is logged publicly in Certificate Transparency logs. Search crt.sh before an attacker does. You will often find forgotten subdomains running outdated software, staging environments with weaker authentication, or internal services that were never meant to be public.
Sensitive path enumeration
Common paths like /.env, /.git, /admin, /phpinfo.php, and /backup.zip are checked by every scanner on the internet. Our scanner verifies 30+ of these paths automatically. If any return 200 OK, the finding is critical — environment files contain database credentials, and exposed git repos leak your entire source code.
Phase 2: Authentication Testing
Authentication is the most attacked surface on most web applications. Walk through each of these checks manually:
Login form
- Rate limiting:Can you submit the form hundreds of times without being blocked? No throttle means brute-force is viable. Test with a simple loop in curl or Burp Suite's Intruder.
- Username enumeration: Does the app respond differently to “user not found” vs “wrong password”? Both messages should be identical. Different responses let attackers build valid username lists before brute-forcing passwords.
- MFA coverage: Is multi-factor authentication available? Is it enforced for admin and privileged accounts? An admin account without MFA is one phished password from a full breach.
Password reset flow
- Token expiry:Does the reset token expire after 15–60 minutes? Tokens that never expire can be used weeks later if an attacker intercepts an email.
- Token entropy: Tokens should be at least 128 bits of secure randomness. Sequential or predictable tokens (based on timestamp or user ID) can be brute-forced.
- Single-use enforcement: Does the token invalidate immediately after use? Reusable reset tokens allow repeated account takeover.
Session management
- Session fixation: Does the session ID change after a successful login? If the ID stays the same, an attacker who plants a session ID before authentication can inherit the authenticated session.
- JWT validation: If the app uses JSON Web Tokens, verify that the signature algorithm is enforced server-side. The
alg: nonebypass strips the signature entirely and has been exploited in production systems. Theroleclaim in the payload should be verified against the database, not trusted blindly.
Phase 3: Injection & Input Validation
Injection flaws appear in every OWASP Top 10 list because developers keep building them. The root cause is identical every time: user input is treated as code or as a command rather than as data.
SQL injection
The minimal test: replace a URL parameter or form field with a single quote '. If the application throws a database error or behaves unexpectedly, it is likely injectable. Use sqlmap to confirm and enumerate the impact. The fix is non-negotiable: parameterized queries. Every time, no exceptions.
-- VULNERABLE: string concatenation
SELECT * FROM users WHERE id = '`' + request.param('id') + '`'
-- SAFE: parameterized query (PostgreSQL)
SELECT * FROM users WHERE id = $1Cross-site scripting (XSS)
Submit <script>alert(1)</script>in a search field, comment form, or profile field. If the script executes, the app has reflected XSS. Stored XSS is worse: the payload persists in the database and fires for every user who loads the affected page. The fix is output encoding — escape < > " ' & before rendering user input as HTML. For full detail see our XSS guide.
Command injection
If the application passes user input to shell commands, test with ; id or | whoami appended to an input value. Command injection gives direct server access. The fix: never pass user input to shell functions. In Python, use subprocess with a list argument (not shell=True). In Node.js, use child_process.execFile instead of exec.
For a full breakdown of OWASP Top 10 injection categories with real-world examples, see OWASP Top 10: Every Vulnerability Explained.
Phase 4: Transport Security (TLS)
TLS testing answers three questions: What protocol versions are enabled? Which cipher suites? Is the certificate valid, trusted, and not close to expiry? A site running TLS 1.0 or using an expired certificate fails basic web app security testing before you even look at the application layer.
TLS (Transport Layer Security) sits between TCP and HTTP, encrypting the channel so that plaintext never crosses the wire. It provides three guarantees: confidentiality (encryption prevents eavesdropping), integrity (MAC codes detect tampering), and authentication (certificates prove you are talking to the right server). SSL (SSLv2, SSLv3) and TLS 1.0/1.1 are all deprecated by RFC 8996. When someone says “SSL” today, they mean TLS 1.2 or TLS 1.3.
The TLS handshake
Before any HTTP data flows, the client and server negotiate a shared secret. In TLS 1.2 this takes two round-trips: ClientHello → ServerHello+ certificate → key exchange → Finished. TLS 1.3 collapses this to a single round-trip by combining the key share with ClientHello. That difference shaves 50–100ms off every new connection, which matters for Time to First Byte (TTFB).
TLS 1.3 removes all legacy ciphers: no RSA key exchange, no CBC mode, no SHA-1. The only key exchange available is ephemeral Diffie-Hellman (ECDHE), which provides forward secrecy by default. Past traffic stays encrypted even if the private key is compromised later.
TLS 1.2 vs TLS 1.3
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| RFC | 5246 | 8446 |
| Handshake round-trips | 2 | 1 (0-RTT with PSK) |
| Forward secrecy | Optional (ECDHE suites) | Mandatory |
| Cipher suites | ~37 supported | 5 (all AEAD) |
| RSA key exchange | Yes | Removed |
| 0-RTT resumption | No | Yes (with replay caveats) |
If you still support TLS 1.2, restrict cipher suites to ECDHE+AESGCM or ECDHE+CHACHA20POLY1305. Anything else is a liability. To check from the command line: openssl s_client -connect example.com:443. For a deeper look at certificate setup, see our SSL/TLS Certificates guide.
Certificate validation
Certificates come in three validation levels: Domain Validation (DV), Organization Validation (OV), and Extended Validation (EV). DV certs only prove you control the domain — Let's Encrypt issues them free in minutes. OV and EV add identity verification, but browsers no longer display a green bar for EV, so the practical benefit is mostly compliance paperwork. Pick DV for most sites. More importantly, track expiry: an expired certificate takes your site offline for every user and destroys trust signals. Set calendar alerts at 30 days and 7 days before expiry.
Phase 5: Security Headers
Security headers are response headers that instruct the browser how to behave. They cost nothing to deploy — a few lines in your web server config — and they block entire attack categories. Despite this, our scans show that the majority of sites ship zero security headers. If your web app security testing reveals missing headers, fix them before anything else. For configuration examples and Nginx/Apache snippets, see What Are HTTP Security Headers and Why They Matter.
Content-Security-Policy (CSP)
CSP is the single most effective defense against Cross-Site Scripting (XSS). It tells the browser which sources are allowed to load scripts, styles, images, fonts, and other resources. If an injected script does not match the policy, the browser blocks it.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'Key directives: default-src sets the fallback, script-src controls JavaScript origins, object-src 'none' kills plugin embeds, and frame-ancestors 'none' replaces X-Frame-Options for modern browsers. Avoid 'unsafe-inline'for scripts — use nonces or hashes instead. Full spec: MDN: Content-Security-Policy.
Strict-Transport-Security (HSTS)
HSTS tells the browser: “Never connect to this domain over plain HTTP again.” Once seen, the browser upgrades all future requests to HTTPS automatically, even if the user types http://. This kills SSL-stripping attacks.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadmax-age is in seconds — 63072000 is two years. includeSubDomains applies to all subdomains. preload lets you submit your domain to the HSTS preload list, hardcoded into browsers. Once preloaded, there is no first-visit vulnerability. Defined in RFC 6797.
X-Frame-Options
Prevents your site from being embedded in an <iframe> on another domain. This stops clickjacking attacks where an attacker overlays invisible frames to hijack clicks.
X-Frame-Options: DENYDENY blocks all framing. SAMEORIGINallows framing by your own domain. CSP's frame-ancestors directive is the modern replacement, but X-Frame-Options is still needed for older browser support.
X-Content-Type-Options
Stops browsers from MIME-sniffing a response away from the declared Content-Type. Without this, a browser might interpret a text file as JavaScript and execute it.
X-Content-Type-Options: nosniffThere is only one valid value: nosniff. Always set it. One line of config, blocks a real attack vector.
Referrer-Policy
Controls how much URL information the browser sends in the Refererheader when navigating between pages. The default behavior leaks full URLs — including query parameters that may contain tokens, session IDs, or PII.
Referrer-Policy: strict-origin-when-cross-originThis sends the full URL for same-origin requests but only the origin (e.g., https://example.com) for cross-origin requests. For maximum privacy, use no-referrer.
Permissions-Policy
Controls which browser features your page can use: camera, microphone, geolocation, payment, USB, and more. If you do not use them, disable them. Malicious scripts cannot access hardware they are explicitly blocked from.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()Empty parentheses () disable the feature entirely.
Cross-Origin-Opener-Policy (COOP)
COOP isolates your browsing context from other origins. Without it, a page opened via window.open can retain a reference to the opener and interact with it. COOP severs that reference.
Cross-Origin-Opener-Policy: same-originSetting same-origin also enables SharedArrayBuffer and high-resolution timers (restricted after Spectre) for pages that need them.
Phase 6: Access Control
Access control failures are the number one category in OWASP 2025. They are also the hardest to catch with automated tools, because testing requires understanding what the application is supposed to allow — knowledge that a scanner does not have.
IDOR (Insecure Direct Object Reference)
Change the ID in a URL or API request to another user's ID. Example: GET /api/orders/1234 — can you change it to /api/orders/1235 and see another user's order? If yes, you have IDOR. The fix: enforce authorization server-side on every request, not just in the UI. Hiding a button in the frontend is not access control.
Privilege escalation
Log in as a low-privilege account and manually visit admin URLs. Can you reach /admin, /api/admin/users, or similar endpoints? For token-based authorization: can you decode a JWT, change role: "user" to role: "admin", and re-sign it? Always verify role claims against the database rather than trusting the token payload directly.
Cookie Security
Cookies carry session tokens, CSRF tokens, and user preferences. An insecure cookie is a direct path to session hijacking. Every authentication cookie must have these flags:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400- Secure— Cookie only sent over HTTPS. Without it, any HTTP request leaks the cookie in plaintext.
- HttpOnly— Cookie inaccessible to JavaScript. Blocks XSS-based session theft via
document.cookie. - SameSite=Lax— Cookie not sent on cross-origin POST requests. Stops most CSRF attacks. Use
Strictif your site does not need cross-origin navigation with cookies. - Max-Age— Set an explicit expiration. Session cookies without Max-Age live until the browser closes; persistent cookies should expire.
Cookie prefixes add an extra layer: __Secure- requires the Secure flag, and __Host- requires Secure, Path=/, and no Domain attribute. These are enforced by the browser and cannot be overridden by subdomain attacks.
CORS Configuration
CORS is not a security feature you add — it is a relaxation of the Same-Origin Policy that browsers enforce by default. The most common misconfiguration: setting Access-Control-Allow-Origin: * while also setting Access-Control-Allow-Credentials: true. Browsers refuse this combination for good reason — it would let any site make authenticated requests to your API.
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: trueAlways whitelist specific origins. Never reflect the Origin header back without validation. A reflected-origin CORS misconfiguration is functionally equivalent to Allow-Origin: * with credentials enabled.
Web App Security Testing Tools
Web app security testing uses a mix of automated scanners for coverage and manual tools for verification. Here is a practical toolkit:
Automated scanners
- ismycodesafe.com — Our scanner. Checks TLS version and certificate validity, all seven critical security headers, 30+ sensitive path exposures, cookie flags, CORS configuration, and more (200+ checks total). No account required, results in 60 seconds. Good first pass before manual testing.
- OWASP ZAP — Open source, runs active scans including injection fuzzing and spider crawling. Useful for testing authenticated flows and discovering injection points.
- Burp Suite Community Edition — Industry standard for manual web app pentesting. Intercept and modify requests, replay attacks, and test authorization logic. The free edition covers most use cases.
Manual testing essentials
- Browser DevTools (Network tab): Inspect request and response headers without installing anything. Check for missing security headers on every response.
- curl:
curl -s -I https://example.comchecks headers from the command line.curl -vshows the full TLS handshake. - openssl:
openssl s_client -connect example.com:443inspects TLS version, cipher suite, and certificate chain without a browser.
Pre-Deploy Security Checklist
Run this before every production deployment. It covers the most common findings from web app security testing across all six phases:
Transport security
- TLS 1.2+ enforced; TLS 1.0/1.1 disabled
- Certificate valid and not expiring within 30 days
- HTTP → HTTPS redirect returns 301 (not 302)
- HSTS header present:
max-age≥ 31536000
Security headers
- Content-Security-Policy set (no
unsafe-inlineon scripts) X-Content-Type-Options: nosniffX-Frame-Options: DENYorSAMEORIGIN- Referrer-Policy set
- Permissions-Policy set
Authentication
- Login form has rate limiting (max 5–10 attempts before lockout or CAPTCHA)
- Password reset tokens expire within 60 minutes and are single-use
- Session cookies have Secure, HttpOnly, and SameSite flags
- Session ID changes after login (prevents session fixation)
- Admin accounts have MFA enforced
Injection
- All database queries use parameterized statements (no string concatenation)
- User input is HTML-encoded before rendering in the DOM
- File uploads validate type and size; files stored outside webroot
- No shell commands receive user input directly
Information exposure
ServerandX-Powered-Byheaders removed or suppressed- Error messages do not expose stack traces in production
/.env,/.git,/adminpaths return 403 or 404- No API keys or credentials in frontend JavaScript bundles
Test Your Site
Our free scanner automates Phase 1 (reconnaissance and path enumeration), Phase 4 (TLS analysis and certificate validity), Phase 5 (all seven security headers), and parts of Phase 6 (cookie flags, CORS configuration). You get a full report in 60 seconds with specific recommendations for every finding.
Frequently Asked Questions
- What is web app security testing?
- Web app security testing is a structured process for finding vulnerabilities in a web application before attackers do. It covers authentication testing, injection checks, transport security (TLS and HTTPS), security headers, access control, and information exposure. It combines automated scanning with manual review, because automated tools miss business-logic flaws that require context to identify.
- How do I start web app security testing on my own site?
- Start with an automated scan to catch the easy wins: TLS misconfiguration, missing security headers, and exposed sensitive paths. Then work through the phases manually: test your login form for rate limiting and username enumeration, check whether session cookies have Secure, HttpOnly, and SameSite flags, and verify that URL parameter IDs cannot be changed to access other users' data. OWASP ZAP and Burp Suite Community Edition are free tools for deeper manual testing.
- What is the OWASP Web Security Testing Guide?
- The OWASP Web Security Testing Guide (WSTG) is the industry-standard methodology for web application security assessments. It defines testing categories across information gathering, configuration, identity management, authentication, authorization, session management, input validation, error handling, cryptography, business logic, and client-side testing. Most professional web app penetration testers structure their reports around WSTG categories.
- What is the difference between DAST and SAST in web app security testing?
- SAST (Static Application Security Testing) analyzes source code without running the application. It finds issues like hardcoded secrets, unsafe function calls, and missing input validation at the code level. DAST (Dynamic Application Security Testing) tests the running application from the outside, the way an attacker would. It finds issues that only appear at runtime, like injection vulnerabilities, misconfigurations, and authentication bypasses. Both approaches complement each other: SAST during development, DAST before and after deployment.
- How often should I run web app security tests?
- At minimum: run an automated scan before every production deployment (use it as a pre-deploy gate), and run a full manual web app penetration test annually or after major architectural changes. For production applications handling sensitive data, quarterly automated scans and annual professional pentests are the industry baseline. Subscribe to CVE feeds for your technology stack so you can retest when new vulnerabilities are disclosed for software you use.
Check your website right now
110 security checks in 60 seconds. Free, no signup required.
Scan My Website (Free)ismycodesafe.com Security Team
We run automated security scans on thousands of websites daily, combining static analysis, SSL/TLS inspection, header auditing, and CVE lookups. Our team tracks OWASP, NIST, and evolving compliance requirements (GDPR, NIS2, PCI DSS) to keep these guides accurate and practical.
Related Articles
What Are HTTP Security Headers and Why They Matter
Guide to CSP, HSTS, X-Frame-Options, and the other headers that stop most common web attacks.
SSL/TLS Certificates Explained: Setup, Errors, and Best Practices
Certificate types, Let's Encrypt setup, common TLS errors, and HSTS preload.
OWASP Top 10 (2021): Every Vulnerability Explained
All ten OWASP vulnerability categories with real-world examples and fixes.
Password Hashing: Salts, bcrypt, and Argon2 Done Right
Hash passwords instead of encrypting them, add a per-user salt, and pick a slow algorithm like Argon2id.