OWASP ASVS: Application Security Verification Standard Testing Guide
The OWASP Top 10 tells you what categories of vulnerability to worry about. OWASP ASVS tells you exactly which controls your application must implement — and how to verify each one. If you're running a serious security program, you need both.
The Application Security Verification Standard (ASVS) is a catalogue of security requirements that scales from basic hygiene to defence-in-depth. It gives penetration testers a structured checklist, gives developers a concrete specification, and gives security teams a language for measuring maturity over time. This guide covers how to use it in practice: what the levels mean, which control domains matter most, how to test them, and how to integrate ASVS verification into your CI/CD pipeline.
What Is OWASP ASVS?
ASVS is a community-driven framework published by the Open Worldwide Application Security Project. The current release is ASVS 4.0, which defines over 280 individual security requirements organised into 14 control chapters (V1 through V14). Each requirement is tagged with a verification level — L1, L2, or L3 — indicating the depth of assurance required.
Unlike a vulnerability scanner, ASVS is not looking for what is broken. It is verifying that required controls exist and function correctly. A clean Nessus scan does not mean you pass ASVS L1. ASVS asks: does your login page enforce account lockout? Does your session cookie carry Secure and HttpOnly flags? Does your API validate that the authenticated user owns the resource they are requesting? These are controls a vulnerability scanner will not confirm.
The Three Verification Levels
| Level | Target | Assurance | Typical Use |
|---|---|---|---|
| L1 — Opportunistic | All applications | Black-box / automated | Baseline hygiene, B2C apps, low-risk internal tools |
| L2 — Standard | Applications handling sensitive data | Grey-box, code review | SaaS platforms, fintech, healthcare, e-commerce |
| L3 — Advanced | Critical or high-value targets | White-box, threat modelling, design review | Banking cores, payment processors, critical infrastructure |
L1 requirements can be verified entirely from the outside: HTTP responses, cookie attributes, TLS configuration, observable behaviour. L2 requires access to source code or configuration — you need to confirm that bcrypt is actually used for password hashing, not just observe a login form. L3 goes further into architectural review, threat modelling, and validating that security controls are tamper-resistant by design.
Most organisations building web applications should target L2 as their baseline and reserve L3 for systems that process financial transactions or sensitive personal data at scale.
Quick orientation: ASVS chapters V1 (Architecture) and V5 (Validation / Sanitisation / Encoding) apply horizontally across every other chapter. Don't skip them — they contain foundational requirements that underpin everything else.
Key Control Domains
V2 — Authentication
Chapter V2 is often the most consequential section in a web application assessment. It covers password strength, credential storage, multi-factor authentication, lookup secrets, and out-of-band verification. Key L1 requirements include:
- Passwords must be at least 12 characters; 64+ characters must be accepted
- Account lockout or rate limiting after no more than 100 failed attempts (the ASVS deliberately avoids "5 attempts" to reduce DoS potential)
- Passwords must be checked against known-breached lists (e.g. HIBP API)
- Credential reset links must be single-use and expire within 15 minutes
- Authentication state must be server-side and not rely solely on client-supplied tokens without verification
Testing V2.1 (password security) from the command line:
# Test minimum password length enforcement
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.example.com/api/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"short"}'
# Probe reset token reuse — second request should return 400/401
TOKEN=$(curl -s -X POST https://target.example.com/api/reset-password \
-d 'token=CAPTURED_TOKEN&password=NewP@ssw0rd123' | jq -r '.token')
curl -s -X POST https://target.example.com/api/reset-password \
-d "token=$TOKEN&password=AnotherP@ss456"
# Brute-force rate limiting check (expect 429 or lockout)
for i in $(seq 1 110); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST https://target.example.com/api/login \
-H "Content-Type: application/json" \
-d "{\"email\":\"victim@example.com\",\"password\":\"wrong$i\"}")
echo "Attempt $i: $CODE"
done
V3 — Session Management
Session management failures are the bridge between authentication bypass and full account takeover. V3 verifies that sessions are properly created, rotated, and invalidated.
- New session token must be issued on every authentication (anti-fixation)
- Session tokens must be at least 128 bits of entropy
- Cookies must carry
Secure,HttpOnly, andSameSite=StrictorLax - Logout must invalidate the server-side session, not just clear the cookie
- Idle timeout: no more than 30 minutes for sensitive applications
# Inspect session cookie attributes
curl -si -X POST https://target.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"ValidPass!"}' \
| grep -i "set-cookie"
# Expected output should include:
# Set-Cookie: session=...; Path=/; HttpOnly; Secure; SameSite=Strict
# Test server-side session invalidation after logout
SESSION=$(curl -si -X POST https://target.example.com/api/login \
-d '{"email":"user@example.com","password":"ValidPass!"}' \
| grep -oP 'session=\K[^;]+')
curl -s -X POST https://target.example.com/api/logout \
-H "Cookie: session=$SESSION"
# Re-use the old session — should return 401
curl -s -o /dev/null -w "%{http_code}" \
-H "Cookie: session=$SESSION" \
https://target.example.com/api/profile
V4 — Access Control
Access control is where ASVS diverges most sharply from what automated scanners catch. V4 requires that every request validate not just that the user is authenticated, but that they are authorised to access the specific resource they are requesting. This is the IDOR (Insecure Direct Object Reference) domain.
- Principle of deny-by-default: all access must be explicitly granted
- Directory browsing must be disabled on all web and application servers
- Access control checks must happen server-side on every request, not just at entry points
- Sensitive administrative interfaces must not be accessible to regular users
# IDOR test: access another user's resource with your own token
# First: authenticate as user A
TOKEN_A=$(curl -s -X POST https://target.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"email":"usera@example.com","password":"PassA!"}' \
| jq -r '.token')
# Then: try to retrieve user B's order (ID discovered via enumeration)
curl -s -H "Authorization: Bearer $TOKEN_A" \
https://target.example.com/api/orders/USER_B_ORDER_ID
# Check for admin panel exposure
for path in /admin /admin/ /administrator /management /console /dashboard/admin; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
https://target.example.com$path)
echo "$path → $CODE"
done
V6 — Cryptography
V6 is one of the more L2/L3-heavy chapters because many cryptographic failures are not observable from the outside. You need source code access to confirm that bcrypt with a cost factor of 10+ is used for passwords, that AES-256-GCM (not ECB) is used for at-rest encryption, and that PRNG seeding is cryptographically secure. What you can test from outside:
# Enumerate TLS configuration and cipher suites
nmap --script ssl-enum-ciphers -p 443 target.example.com
# Check for weak ciphers and deprecated protocols (SSLv3, TLS 1.0, TLS 1.1)
testssl.sh --severity HIGH --color 0 https://target.example.com
# Verify HSTS header presence and max-age
curl -si https://target.example.com | grep -i strict-transport
# Check certificate chain
openssl s_client -connect target.example.com:443 -showcerts 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
For L2 code review, check that password hashing uses a memory-hard algorithm. In Python:
# Good — bcrypt with adequate cost factor
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Bad — MD5, SHA-1, SHA-256 without salt (ASVS V6.4.1 violation)
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()
# Also bad — PBKDF2 with fewer than 310,000 iterations (NIST SP 800-132 guidance)
V7 — Error Handling and Logging
Error handling failures leak internal information (stack traces, database errors, file paths) and logging failures mean you have no forensic record when something goes wrong. V7 L1 requirements:
- Application must not expose stack traces, database error messages, or sensitive debugging output to end users
- Generic error messages must be used in production
- Authentication events (success, failure, lockout) must be logged with sufficient context
- Logs must not contain passwords, session tokens, or payment card data
# Trigger error conditions and inspect responses
# SQL injection probe — look for database error messages
curl -s "https://target.example.com/api/product?id=1'" \
| grep -iE "(mysql|postgresql|oracle|sqlite|syntax error|ORA-|SQLSTATE)"
# Path traversal probe — look for server path disclosure
curl -s "https://target.example.com/api/file?name=../../../../etc/passwd"
# Force a 500 error and check for stack trace leakage
curl -s -X POST https://target.example.com/api/process \
-H "Content-Type: application/json" \
-d '{"__proto__":{"polluted":true}}' \
| grep -iE "(traceback|stack trace|exception|at line|\.py:|\.java:|\.js:)"
V8 — Data Protection
V8 verifies that sensitive data is protected in transit, at rest, and that the application does not cache or expose data unnecessarily. Key L1 checks:
- Sensitive data (PII, tokens, health data) must not appear in URL parameters
- HTTP responses containing sensitive data must include appropriate
Cache-Controlheaders - The application must not store sensitive data client-side in plaintext (localStorage, sessionStorage, cookies without encryption)
# Check Cache-Control on authenticated responses
curl -si -H "Authorization: Bearer $TOKEN" \
https://target.example.com/api/profile \
| grep -i "cache-control"
# Should see: Cache-Control: no-store or no-cache, no-store, must-revalidate
# Inspect browser storage for sensitive data (DevTools console)
# Run in browser console after authentication:
# Object.keys(localStorage).forEach(k => console.log(k, localStorage.getItem(k)));
# Object.keys(sessionStorage).forEach(k => console.log(k, sessionStorage.getItem(k)));
# Check for sensitive data in URL (look for tokens/passwords in GET params)
curl -si "https://target.example.com/reset?token=abc123" -o /dev/null -w "%{redirect_url}"
V9 — Communications
V9 overlaps with V6 cryptography but focuses specifically on transport security: TLS version, certificate validation, HTTP Strict Transport Security, and mixed content. Nuclei has ready-made templates for most of these:
# Run Nuclei against communication security templates
nuclei -u https://target.example.com \
-t ssl/ \
-t http/misconfiguration/http-missing-security-headers.yaml \
-t http/misconfiguration/hsts-missing.yaml \
-severity medium,high,critical
# Check for HTTP→HTTPS redirect (V9.1.1)
curl -si http://target.example.com/ | grep -i location
# Verify HSTS includeSubDomains and preload
curl -si https://target.example.com | grep -i strict-transport-security
# Should contain: max-age=31536000; includeSubDomains
# Test for mixed content (HTTPS page loading HTTP resources)
# Use browser DevTools Security panel or:
gospider -s https://target.example.com -d 1 --other-source \
| grep "^http://"
V13 — API and Web Service
V13 is the control chapter most relevant to modern microservice architectures. It addresses REST and GraphQL API security, schema validation, and service-to-service authentication. Key requirements:
- RESTful APIs must use HTTP methods in their intended semantic sense and validate Content-Type headers
- APIs must reject requests with unexpected or missing Content-Type (prevent CSRF via
text/plainrequests to JSON endpoints) - GraphQL must implement query depth limiting and cost analysis to prevent denial-of-service via deeply nested queries
- Service-to-service calls must use mutual TLS or equivalent — shared secrets in headers are not sufficient for L2+
# Test Content-Type validation bypass (V13.1.3)
# Many APIs incorrectly process requests even with wrong Content-Type
curl -s -X POST https://target.example.com/api/transfer \
-H "Content-Type: text/plain" \
-d '{"amount":1000,"to":"attacker-account"}'
# GraphQL introspection check (V13.4.1 — should be disabled in production)
curl -s -X POST https://target.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name}}}"}'
# GraphQL depth limit test — deeply nested query
curl -s -X POST https://target.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{user{posts{comments{author{posts{comments{author{name}}}}}}}}"}' \
| jq '.errors'
# Mass assignment test — send extra fields not expected by the server
curl -s -X PUT https://target.example.com/api/user/profile \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Attacker","role":"admin","isAdmin":true,"credits":99999}'
V14 — Configuration
V14 covers server configuration: security headers, component security, HTTP methods, and build artefact integrity. This is the most automatable chapter — Nuclei and curl cover 80% of it.
# Full security header audit
curl -si https://target.example.com/ | grep -iE \
"(x-content-type-options|x-frame-options|content-security-policy|\
referrer-policy|permissions-policy|x-xss-protection|feature-policy)"
# Nuclei template for security header misconfigs
nuclei -u https://target.example.com \
-t http/misconfiguration/ \
-t http/vulnerabilities/ \
-tags headers,config \
-severity info,low,medium,high,critical
# Check dangerous HTTP methods (V14.5.1)
curl -si -X OPTIONS https://target.example.com/api/ \
| grep -i allow
# TRACE, CONNECT, PUT, DELETE should not be enabled unless explicitly required
# Verify X-Content-Type-Options (prevents MIME sniffing)
curl -si https://target.example.com/ | grep -i "x-content-type-options"
# Should be: X-Content-Type-Options: nosniff
Mapping ASVS Requirements to Penetration Test Findings
One of ASVS's most practical uses is giving penetration test findings a standardised reference. Instead of a finding titled "Weak Password Policy" with a subjective severity, you can cite the exact requirement that was not met. This makes remediation instructions unambiguous and enables tracking of control coverage over time.
| Finding | ASVS Reference | Level | CWE |
|---|---|---|---|
| No account lockout on login | V2.2.1 | L1 | CWE-307 |
| Session token not rotated on login | V3.3.1 | L1 | CWE-384 |
| Missing HttpOnly on session cookie | V3.4.2 | L1 | CWE-1004 |
| IDOR on user resource endpoint | V4.2.1 | L1 | CWE-639 |
| MD5 used for password hashing | V6.4.1 | L2 | CWE-916 |
| Stack trace in error response | V7.4.1 | L1 | CWE-209 |
| Missing Content-Security-Policy header | V14.4.3 | L1 | CWE-1021 |
| GraphQL introspection enabled in production | V13.4.1 | L2 | CWE-200 |
| TLS 1.0 still accepted | V9.1.2 | L1 | CWE-326 |
| Sensitive data in URL parameters | V8.3.1 | L1 | CWE-598 |
When writing a report, include the ASVS reference alongside CVSS score, CWE, and the specific test that triggered the finding. Many clients now contractually require ASVS L1 or L2 compliance and need the exact reference to track remediation against their compliance programme.
ASVS vs OWASP Top 10 — Complementary, Not Competing
Security professionals sometimes treat ASVS and the OWASP Top 10 as alternatives. They are not — they answer different questions.
| Dimension | OWASP Top 10 | OWASP ASVS |
|---|---|---|
| Purpose | Awareness — what risks are most common and critical | Verification — which controls must be present and working |
| Format | 10 risk categories (A01–A10) | 280+ individual requirements across 14 chapters |
| Audience | Developers, management, broad security awareness | AppSec engineers, penetration testers, auditors |
| Testability | Describes vulnerability classes, not test procedures | Each requirement is verifiable with a specific test |
| Compliance use | Rarely cited in contracts; awareness tool | Directly used in procurement, RFPs, audit frameworks |
The OWASP Top 10 maps to ASVS categories but is not a subset or superset. A01 (Broken Access Control) aligns with ASVS V4 but ASVS V4 is far more specific — it has 19 individual requirements covering horizontal access control, vertical privilege escalation, directory traversal, and metadata exposure separately. Use the Top 10 to explain risk to stakeholders; use ASVS to build the actual test plan.
ASVS and compliance frameworks: ISO 27001 Annex A.14 (secure development) maps well to ASVS L2. SOC 2 Trust Service Criteria for availability and processing integrity align to ASVS L1 baseline. PCI DSS Requirement 6.3 (security of software) explicitly references OWASP ASVS as an acceptable secure coding framework.
Integrating ASVS into CI/CD and DevSecOps
Static checklists only work if someone runs them. The real value of ASVS in a mature engineering organisation comes from automating L1 verification in CI/CD and surfacing deviations before they reach production.
What You Can Automate in CI/CD
Not all 280+ ASVS requirements are automatable, but a significant subset of L1 is. The categories that map cleanly to automated tools:
- V6 Cryptography (partial): TLS configuration via
testssl.shorsslyzein pipeline - V9 Communications: HSTS header, redirect enforcement, mixed content detection
- V14 Configuration: Security headers via curl or Nuclei in a post-deploy check
- V13 API: Schema validation, Content-Type enforcement via automated API tests
- V6 Dependencies: SCA (Software Composition Analysis) to catch known-vulnerable libraries
# Example: GitHub Actions job for ASVS L1 automated checks
name: ASVS L1 Verification
on:
deployment_status:
jobs:
asvs-l1:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- name: Check security headers (V14.4)
run: |
URL="${{ github.event.deployment_status.environment_url }}"
HEADERS=$(curl -si "$URL" | grep -iE \
"(x-content-type-options|x-frame-options|content-security-policy|\
strict-transport-security|referrer-policy)")
echo "$HEADERS"
echo "$HEADERS" | grep -qi "x-content-type-options: nosniff" || \
(echo "FAIL: Missing X-Content-Type-Options" && exit 1)
echo "$HEADERS" | grep -qi "strict-transport-security" || \
(echo "FAIL: Missing HSTS header" && exit 1)
- name: Nuclei scan (V14 misconfig)
uses: projectdiscovery/nuclei-action@main
with:
target: ${{ github.event.deployment_status.environment_url }}
flags: "-t http/misconfiguration/ -severity high,critical"
- name: Dependency audit (V6.2 — known vulnerable libs)
run: npm audit --audit-level=high
# or: pip-audit --fail-on-finding for Python
# or: trivy fs . --exit-code 1 --severity HIGH,CRITICAL
Structuring a ASVS-Driven Security Test Plan
For a structured engagement, map your test plan directly to ASVS chapters. A typical L1 assessment for a SaaS web application:
- Reconnaissance: Map application surface, identify technology stack, enumerate endpoints (feeds V13, V14)
- V14 configuration review: TLS, headers, HTTP methods — fully automatable with Nuclei + testssl.sh
- V2 authentication testing: Password policy, lockout, reset flow, credential exposure
- V3 session management: Cookie attributes, session rotation, logout invalidation
- V4 access control: IDOR probes, privilege escalation, unauthenticated access to authenticated endpoints
- V7 error handling: Force errors across all input vectors, review disclosure level
- V8 data protection: Sensitive data in URLs, cache headers, client-side storage
- V13 API: Content-Type enforcement, mass assignment, API-specific injection
- Reporting: Map each finding to ASVS requirement ID, level, and CWE
ASVS Scoring and Gap Analysis
You can build a simple scoring model against ASVS to track maturity over time. For each chapter, count the number of applicable requirements and how many pass verification:
# Simple ASVS scoring spreadsheet structure (export to CSV)
Chapter,Total_Requirements,Tested,Passed,Failed,Not_Applicable,Score_%
V2 Authentication,56,56,44,12,0,78.6
V3 Session Management,21,21,19,2,0,90.5
V4 Access Control,19,19,14,5,0,73.7
V6 Cryptography,49,30,28,2,19,93.3
V7 Error Handling,8,8,6,2,0,75.0
V8 Data Protection,25,20,17,3,5,85.0
V9 Communications,9,9,9,0,0,100.0
V13 API,15,15,10,5,0,66.7
V14 Configuration,14,14,12,2,0,85.7
Run this gap analysis at the start of an engagement and again after remediation. A mature DevSecOps team uses this data to set quarterly security objectives: "Bring V4 Access Control from 73% to 90% this quarter."
Practical ASVS Testing With Nuclei
Nuclei's community template library has grown to cover a substantial slice of ASVS L1. The templates are tagged and can be filtered by category. A focused ASVS-oriented scan:
# Install or update Nuclei templates
nuclei -update-templates
# Run all templates relevant to ASVS L1 configuration and misconfiguration
nuclei -u https://target.example.com \
-t http/misconfiguration/ \
-t ssl/ \
-t http/exposures/ \
-t http/vulnerabilities/generic/ \
-severity low,medium,high,critical \
-o nuclei-asvs-l1.txt \
-markdown-export ./nuclei-report/
# Targeted: authentication and session issues (V2, V3)
nuclei -u https://target.example.com \
-tags auth,session,login,password \
-severity medium,high,critical
# Targeted: API security (V13)
nuclei -u https://target.example.com \
-tags graphql,api,rest \
-severity medium,high,critical
# Check for information disclosure (V7)
nuclei -u https://target.example.com \
-t http/exposures/configs/ \
-t http/exposures/logs/ \
-t http/exposures/files/ \
-severity low,medium,high,critical
Nuclei will not complete a full ASVS verification for you — particularly V4 (access control) and most of V2 require authenticated sessions and manual logic testing. But it handles the automatable 30–40% efficiently and surfaces the obvious L1 gaps before you burn manual testing time.
Tool coverage reality check: Nuclei + testssl.sh + authenticated curl scripts cover approximately 35–45% of ASVS L1 requirements. The remainder requires manual testing, particularly access control logic (V4), authentication flow analysis (V2), and data protection verification (V8). Plan accordingly when scoping engagements.
ASVS for Developers: Security Requirements as Code
For development teams, ASVS is most effective when requirements are embedded in the definition of done rather than discovered in a penetration test six months later. Practical implementation patterns:
- User story acceptance criteria: For any authentication feature, include "V2.1.1 — password policy enforced: minimum 12 characters, no common passwords" as a testable AC
- Pull request templates: Include a security checklist drawn from applicable ASVS requirements for the feature type (new endpoint, auth change, data handling)
- Unit test coverage: V5 (validation) and V2 (auth) requirements map directly to unit tests — write them as part of TDD cycles
- Dependency scanning: V6.2 (algorithm agility) and V12.3 (component currency) requirements are enforced automatically by SCA tools like Dependabot, Renovate, or Trivy in CI
The OWASP ASVS project maintains a CSV export of all requirements that can be imported into Jira, Linear, or any issue tracker. Tag L1 requirements as "must have for MVP" and L2 requirements as "must have before production launch with real user data" to give engineering clear prioritisation language.
Common ASVS Assessment Mistakes
After running ASVS-mapped assessments across dozens of applications, the failure modes cluster into a few repeating patterns:
- Testing L1 only and calling it "ASVS compliant": L1 is the floor, not the ceiling. If you handle payment data or health records, L2 is the minimum credible claim.
- Skipping V1 (Architecture) because "it's not technical": V1 catches architectural-level control gaps — missing security in depth, absent trust boundaries — that no tool-based test will find.
- Marking V5 (Validation) requirements as passed based on form-level input validation: V5 requires server-side validation. Client-side validation alone fails every V5 requirement.
- Ignoring "not applicable" rigorously: Every requirement marked N/A should have a documented justification. "We don't use cookies" is valid; "we haven't thought about it" is not.
- Confusing "we have a WAF" with "V5 is satisfied": A WAF is a compensating control. The underlying application must still validate input — WAFs are bypassed regularly.
Automate ASVS L1 Verification With Ironimo
Ironimo runs Kali Linux-grade security scans against your web application on demand — covering the automatable slice of ASVS L1: TLS configuration (V9), security headers (V14), API misconfiguration (V13), information disclosure (V7), and more. Get a structured finding report mapped to ASVS requirement IDs, so your remediation backlog is always tied to specific controls.
Run your first ASVS-aligned scan in under five minutes — no agents, no infrastructure changes required.
Start free scan