The OWASP Top 10 represents the most critical web application security risks identified by security experts worldwide, providing QA engineers with a definitive framework for security test planning and coverage. According to OWASP’s own analysis, the Top 10 categories cover vulnerabilities responsible for over 90% of real-world web application breaches, making mastery of this framework a high-leverage investment for any QA professional. According to Veracode’s State of Software Security 2023, 76% of applications have at least one Open Web Application Security Project (OWASP) Top 10 vulnerability, and 24% have critical vulnerabilities that could lead to significant data exposure. For QA engineers building security testing capabilities, understanding the 2021 OWASP Top 10 categories — from Broken Access Control (#1) to Server-Side Request Forgery (#10) — and knowing how to test for each provides comprehensive coverage of the most dangerous vulnerability classes.

TL;DR: OWASP Top 10 2021 testing priorities: #1 Broken Access Control (test IDOR, missing auth), #2 Cryptographic Failures (check HTTPS, encryption at rest), #3 Injection (SQL, NoSQL, LDAP injection), #7 Identification/Auth Failures (brute force, session management), #3 XSS (stored, reflected, DOM). Use OWASP ZAP for automated scanning of all 10 categories.

Understanding OWASP Top 10

The OWASP Top 10 is a regularly updated report outlining the ten most critical security risks facing web applications. For QA professionals, understanding these vulnerabilities is essential for:

  • Risk Assessment: Identifying potential security weaknesses early
  • Test Planning: Building security test cases into QA processes
  • Collaboration: Communicating security concerns with developers
  • Compliance: Meeting security standards and regulations
  • User Protection: Safeguarding sensitive user data and privacy

OWASP Top 10 (2021 Edition)

RankVulnerabilityImpactPrevalence
A01Broken Access ControlHighVery Common
A02Cryptographic FailuresHighCommon
A03InjectionCriticalCommon
A04Insecure DesignHighCommon
A05Security MisconfigurationMediumVery Common
A06Vulnerable and Outdated ComponentsHighCommon
A07Identification and Authentication FailuresHighCommon
A08Software and Data Integrity FailuresHighLess Common
A09Security Logging and Monitoring FailuresMediumCommon
A10Server-Side Request Forgery (SSRF)MediumLess Common

“The OWASP Top 10 is not just a checklist — it’s a curriculum. Each category represents a class of vulnerabilities that requires deep understanding, not just a pass/fail checkbox in a scanning tool.” — Yuri Kan, Senior QA Lead

A01: Broken Access Control

Access control enforces policies preventing users from acting outside their intended permissions. Broken access control allows attackers to access unauthorized functionality or data.

Common Access Control Vulnerabilities

Vertical Privilege Escalation:

# Regular user accessing admin functionality
GET /admin/users HTTP/1.1
Authorization: Bearer regular_user_token

Horizontal Privilege Escalation:

# User accessing another user's data
GET /api/users/123/profile HTTP/1.1
# Should only access own profile (user ID 456)

Insecure Direct Object References (IDOR):

// Predictable resource IDs
/documents/1001
/documents/1002  // Can user access this?
/documents/1003

Testing Approaches

Manual Testing:

  1. Attempt to access restricted URLs directly
  2. Modify URL parameters (IDs, usernames)
  3. Test different user roles and permissions
  4. Check for missing function-level access controls
  5. Verify POST/PUT/DELETE restrictions

Automated Testing:

# Example: Testing IDOR vulnerability
def test_idor_vulnerability():
    # Login as user A
    user_a_token = login("userA", "password")
    user_a_id = get_user_id(user_a_token)

    # Login as user B
    user_b_token = login("userB", "password")
    user_b_id = get_user_id(user_b_token)

    # Attempt to access user B's resources with user A's token
    response = requests.get(
        f"/api/users/{user_b_id}/profile",
        headers={"Authorization": f"Bearer {user_a_token}"}
    )

    # Should return 403 Forbidden
    assert response.status_code == 403, "IDOR vulnerability detected!"

Prevention Best Practices

  • Implement role-based access control (RBAC)
  • Deny access by default (whitelist approach)
  • Use indirect object references (UUIDs instead of sequential IDs)
  • Log access control failures
  • Rate limit API requests

A02: Cryptographic Failures

Cryptographic failures occur when sensitive data is not properly protected through encryption, leading to exposure of passwords, credit card numbers, health records, and other confidential information.

Common Cryptographic Issues

Weak Encryption Algorithms:

# Insecure - MD5 is cryptographically broken
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()

# Secure - Use bcrypt or Argon2
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

Unencrypted Data Transmission:

# Insecure - HTTP transmits data in plaintext
http://example.com/login

# Secure - HTTPS encrypts data in transit
https://example.com/login

Hardcoded Secrets:

// NEVER do this!
const API_KEY = "sk_live_abc123xyz789";
const DB_PASSWORD = "admin123";

// Use environment variables
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;

Testing Approaches

SSL/TLS Configuration Testing:

# Check SSL certificate validity
openssl s_client -connect example.com:443

# Test for weak ciphers
nmap --script ssl-enum-ciphers -p 443 example.com

# Check for TLS version support
testssl.sh https://example.com

Sensitive Data Exposure Testing:

  1. Inspect network traffic for unencrypted sensitive data
  2. Check browser storage (localStorage, sessionStorage, cookies)
  3. Review error messages for information disclosure
  4. Test password storage mechanisms
  5. Verify secure flag on cookies

Testing Checklist:

cryptographic_testing:
  data_at_rest:

    - Database encryption enabled
    - Encrypted file storage
    - Secure key management

  data_in_transit:

    - HTTPS enforced (HSTS enabled)
    - Strong TLS configuration (TLS 1.2+)
    - Valid SSL certificates

  sensitive_data:

    - Passwords hashed with strong algorithm
    - Credit cards tokenized
    - PII encrypted
    - No sensitive data in URLs or logs

A03: Injection

Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. SQL, NoSQL, OS command, and LDAP injection are common variants.

SQL Injection

Vulnerable Code:

# Dangerous - vulnerable to SQL injection
username = request.POST['username']
password = request.POST['password']

query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)

Attack Example:

-- Attacker input for username: admin' OR '1'='1
-- Resulting query bypasses authentication:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything'

Secure Code:

# Safe - using parameterized queries
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))

Testing for SQL Injection

Manual Testing Payloads:

-- Basic tests
' OR '1'='1
'; DROP TABLE users; --
' UNION SELECT NULL, NULL, NULL--

-- Time-based blind injection
'; WAITFOR DELAY '00:00:05'--

-- Boolean-based blind injection
' AND 1=1--
' AND 1=2--

Automated Testing:

# SQLMap example
sqlmap -u "http://example.com/product?id=1" --batch --dbs

# Testing with OWASP ZAP
from zapv2 import ZAPv2

zap = ZAPv2(proxies={'http': 'http://127.0.0.1:8080'})
zap.ascan.scan(target_url)

Command Injection

Vulnerable Code:

# Dangerous - vulnerable to command injection
filename = request.GET['file']
os.system(f'cat {filename}')

Attack Example:

# Attacker input: report.txt; rm -rf /
# Executed command: cat report.txt; rm -rf /

Secure Code:

# Safe - input validation and escaping
import subprocess
import shlex

filename = request.GET['file']
# Validate filename
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', filename):
    raise ValueError("Invalid filename")

# Use subprocess with list arguments
subprocess.run(['cat', filename], check=True)

NoSQL Injection

Vulnerable Code:

// Dangerous MongoDB query
const username = req.body.username;
const password = req.body.password;

db.users.find({
  username: username,
  password: password
});

Attack Example:

{
  "username": {"$ne": null},
  "password": {"$ne": null}
}

Secure Code:

// Safe - validate and sanitize input
const username = String(req.body.username);
const password = String(req.body.password);

db.users.findOne({
  username: username,
  password: hashPassword(password)
});

A04: Insecure Design

Insecure design represents missing or ineffective security controls due to flawed architecture and design decisions. This is different from insecure implementation.

Design Flaws to Test

Missing Rate Limiting:

# Test for brute force vulnerability
def test_login_rate_limiting():
    for i in range(1000):
        response = requests.post("/login", data={
            "username": "admin",
            "password": f"password{i}"
        })

        # Should implement rate limiting after N attempts
        if i > 5:
            assert response.status_code == 429, "No rate limiting detected!"

Business Logic Flaws:

# Example: Negative quantity exploit
def test_negative_quantity_vulnerability():
    # Add product with negative quantity
    response = requests.post("/cart/add", json={
        "product_id": 123,
        "quantity": -10,
        "price": 100
    })

    # Check if negative total is calculated (crediting user)
    cart_total = get_cart_total()
    assert cart_total >= 0, "Negative quantity exploit possible!"

Insufficient Validation:

// Check for missing business rules
test('cannot transfer more than account balance', async () => {
  const transfer = {
    from: 'account123',
    to: 'account456',
    amount: 999999  // More than balance
  };

  const response = await api.post('/transfer', transfer);
  expect(response.status).toBe(400);
});

A05: Security Misconfiguration

Security misconfiguration occurs when security settings are not defined, implemented, or maintained properly.

Common Misconfigurations

Default Credentials:

# Test for default credentials
common_defaults = [
    ("admin", "admin"),
    ("administrator", "password"),
    ("root", "root"),
    ("admin", "password123")
]

for username, password in common_defaults:
    test_login(username, password)

Unnecessary Features Enabled:

# Test for enabled HTTP methods
OPTIONS /api/users HTTP/1.1

# Response should limit methods
Allow: GET, POST, DELETE  # PUT/PATCH disabled?

Verbose Error Messages:

# Bad - exposes internal details
try:
    db.query(sql)
except Exception as e:
    return f"Database error: {str(e)}"  # Exposes database structure

# Good - generic error message
try:
    db.query(sql)
except Exception as e:
    logger.error(f"Database error: {e}")
    return "An error occurred. Please try again."

Directory Listing:

# Test for directory traversal
curl http://example.com/uploads/
curl http://example.com/../../../etc/passwd

Security Headers Testing

def test_security_headers():
    response = requests.get("https://example.com")
    headers = response.headers

    # Required security headers
    assert 'X-Content-Type-Options' in headers
    assert headers['X-Content-Type-Options'] == 'nosniff'

    assert 'X-Frame-Options' in headers
    assert headers['X-Frame-Options'] in ['DENY', 'SAMEORIGIN']

    assert 'Strict-Transport-Security' in headers

    assert 'Content-Security-Policy' in headers

    # Should not expose server information
    assert 'Server' not in headers or 'nginx' not in headers.get('Server', '').lower()

A06: Vulnerable and Outdated Components

Using components with known vulnerabilities (libraries, frameworks, software) can compromise application security.

Dependency Scanning

NPM Audit:

# Check for vulnerable npm packages
npm audit

# Fix vulnerabilities automatically
npm audit fix

# Generate detailed report
npm audit --json > audit-report.json

Python Requirements:

# Check Python dependencies
pip install safety
safety check

# Scan requirements file
safety check -r requirements.txt

Dependency-Check (OWASP):

# Scan Java/JavaScript/Python projects
dependency-check --project "MyApp" --scan ./ --format HTML

Testing Process

dependency_testing:
  automated_scanning:

    - npm_audit: "Daily in CI/CD"
    - snyk: "Weekly comprehensive scan"
    - dependabot: "Automated PR for updates"

  manual_review:

    - cve_database: "Check CVE for critical components"
    - version_check: "Ensure latest stable versions"
    - eol_check: "Identify end-of-life components"

  remediation:

    - update_dependencies: "Apply security patches"
    - remove_unused: "Delete unused libraries"
    - find_alternatives: "Replace vulnerable components"

A07: Identification and Authentication Failures

Authentication mechanisms are often implemented incorrectly, allowing attackers to compromise passwords, keys, or session tokens.

Authentication Testing

Credential Stuffing:

# Test account lockout mechanism
def test_account_lockout():
    failed_attempts = 0

    for i in range(10):
        response = login("user@example.com", f"wrong_password_{i}")
        if response.status_code == 401:
            failed_attempts += 1

        # Account should be locked after N attempts
        if failed_attempts >= 5:
            assert "account locked" in response.text.lower()

Session Management:

def test_session_fixation():
    # Get session before login
    response1 = requests.get("https://example.com")
    session_before = response1.cookies.get('sessionid')

    # Login
    response2 = requests.post("https://example.com/login",
                             cookies={'sessionid': session_before},
                             data={"username": "user", "password": "pass"})

    # Session should change after authentication
    session_after = response2.cookies.get('sessionid')
    assert session_before != session_after, "Session fixation vulnerability!"

Weak Password Policy:

weak_passwords = [
    "123456",
    "password",
    "admin",
    "user123",
    "qwerty"
]

for pwd in weak_passwords:
    response = register_user("test@example.com", pwd)
    assert response.status_code == 400, f"Weak password accepted: {pwd}"

Multi-Factor Authentication Testing

def test_mfa_bypass():
    # Login with valid credentials
    response = login("user@example.com", "ValidPassword123!")

    # Check if MFA is enforced
    assert "mfa_required" in response.json() or response.status_code == 401

    # Attempt to access protected resource without MFA
    response = requests.get("/api/sensitive-data",
                           headers={"Authorization": f"Bearer {partial_token}"})

    # Should be denied without MFA completion
    assert response.status_code == 403

A08: Software and Data Integrity Failures

This category focuses on code and infrastructure that doesn’t protect against integrity violations, such as insecure CI/CD pipelines or auto-updates without verification.

Testing for Integrity Issues

Unsigned Updates:

def test_update_signature_verification():
    # Download update package
    update_package = download_update("https://cdn.example.com/update.zip")

    # Verify digital signature
    signature = download_signature("https://cdn.example.com/update.zip.sig")

    # Should verify before installation
    assert verify_signature(update_package, signature), "Update not signed!"

Deserialization Vulnerabilities:

# Test for insecure deserialization
import pickle
import base64

# Malicious payload
malicious_data = base64.b64encode(pickle.dumps(malicious_object))

response = requests.post("/api/process",
                        data={"data": malicious_data})

# Should reject untrusted deserialization
assert response.status_code == 400

CI/CD Pipeline Security:

ci_cd_security_checklist:
  pipeline_integrity:

    - "✓ Signed commits required"
    - "✓ Protected branches enforced"
    - "✓ Code review mandatory"
    - "✓ Security scans in pipeline"

  artifact_integrity:

    - "✓ Build artifacts signed"
    - "✓ Checksum verification"
    - "✓ Provenance tracking"
    - "✓ Immutable artifact storage"

A09: Security Logging and Monitoring Failures

Insufficient logging and monitoring allows attackers to maintain persistence, pivot to other systems, and tamper with data without detection.

Logging Requirements Testing

Critical Events to Log:

critical_events = [
    "login_attempts",
    "failed_authentication",
    "access_control_failures",
    "input_validation_failures",
    "privilege_escalation_attempts",
    "admin_operations",
    "data_modifications",
    "cryptographic_failures"
]

def test_logging_coverage():
    for event_type in critical_events:
        trigger_event(event_type)

        # Check if event is logged
        logs = get_application_logs()
        assert any(event_type in log for log in logs),
               f"Event not logged: {event_type}"

Log Content Verification:

def test_log_content_quality():
    # Trigger suspicious activity
    failed_login("admin", "wrong_password", ip="192.168.1.100")

    # Retrieve log entry
    log_entry = get_latest_log()

    # Verify essential information is captured
    assert "username" in log_entry  # Who
    assert "ip_address" in log_entry  # Where
    assert "timestamp" in log_entry  # When
    assert "action" in log_entry  # What
    assert "result" in log_entry  # Outcome

    # Ensure sensitive data is not logged
    assert "password" not in log_entry

A10: Server-Side Request Forgery (SSRF)

SSRF flaws occur when a web application fetches a remote resource without validating the user-supplied URL, allowing attackers to coerce the application to send requests to unexpected destinations.

SSRF Testing

Basic SSRF Detection:

def test_ssrf_vulnerability():
    # Attempt to access internal resources
    internal_urls = [
        "http://localhost/admin",
        "http://127.0.0.1:8080/status",
        "http://169.254.169.254/latest/meta-data/",  # AWS metadata
        "http://internal-service:3000/api"
    ]

    for url in internal_urls:
        response = requests.post("/api/fetch", json={"url": url})

        # Should block internal URLs
        assert response.status_code in [400, 403], f"SSRF possible: {url}"

Cloud Metadata Access:

# Test for AWS metadata access (common SSRF target)
def test_cloud_metadata_ssrf():
    aws_metadata_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"

    response = requests.post("/api/fetch-url",
                            json={"url": aws_metadata_url})

    # Should not expose cloud credentials
    assert "AWS" not in response.text
    assert "AccessKeyId" not in response.text

Security Testing Tools

ToolCategoryBest ForCost
OWASP ZAPDynamic TestingAutomated vulnerability scanningFree
Burp SuiteProxy/ScannerManual security testingFree/Paid
NiktoWeb ScannerServer vulnerability scanningFree
SQLMapInjectionSQL injection detectionFree
NmapNetwork ScannerPort and service discoveryFree
SnykDependency ScanVulnerable component detectionFree/Paid
SonarQubeStatic AnalysisCode quality and securityFree/Paid
AcunetixWeb ScannerComprehensive web vulnerability scanPaid
NessusVulnerability ScannerInfrastructure scanningPaid

Tool Integration Example

# CI/CD Security Pipeline
security_pipeline:
  pre_commit:

    - git_secrets: "Scan for hardcoded secrets"
    - pre_commit_hooks: "Security linting"

  build:

    - sonarqube: "SAST analysis"
    - dependency_check: "Vulnerable components"
    - container_scan: "Docker image vulnerabilities"

  test:

    - owasp_zap: "DAST scanning"
    - security_regression_tests: "Automated security tests"

  pre_production:

    - penetration_test: "Manual security assessment"
    - security_review: "Architecture review"

Best Practices for QA Security Testing

1. Integrate Security Early (Shift Left)

  • Include security requirements in user stories
  • Perform threat modeling during design phase
  • Conduct security code reviews
  • Run automated security scans in CI/CD

2. Maintain Security Test Cases

# Example: Security test case in Gherkin
Feature: Authentication Security

  Scenario: Account lockout after failed attempts
    Given a registered user account
    When I attempt login with wrong password 5 times
    Then the account should be locked for 30 minutes
    And I should receive a lockout notification email

  Scenario: Session timeout
    Given I am logged in
    When I remain inactive for 30 minutes
    Then my session should expire
    And I should be redirected to login page

3. Security Testing Checklist

## Pre-Release Security Checklist

### Authentication & Authorization
- [ ] Password complexity enforced
- [ ] Account lockout implemented
- [ ] MFA available for sensitive actions
- [ ] Session timeout configured
- [ ] Access control properly enforced

### Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] HTTPS enforced (HSTS enabled)
- [ ] Secure cookies configured
- [ ] No sensitive data in logs/URLs

### Input Validation
- [ ] SQL injection prevention verified
- [ ] XSS protection implemented
- [ ] CSRF tokens present
- [ ] File upload restrictions enforced

### Configuration
- [ ] Default credentials changed
- [ ] Unnecessary features disabled
- [ ] Security headers configured
- [ ] Error handling doesn't leak info

### Dependencies
- [ ] All components up to date
- [ ] No known vulnerabilities
- [ ] Unused libraries removed
- [ ] License compliance verified

4. Continuous Monitoring

  • Set up security alerting in production
  • Monitor for unusual patterns
  • Track authentication failures
  • Review security logs regularly

5. Collaborate with Security Team

  • Participate in threat modeling sessions
  • Share QA findings with security team
  • Learn from penetration test results
  • Stay updated on emerging threats

Conclusion

Security testing is an essential responsibility for modern QA professionals. Understanding the OWASP Top 10 provides a solid foundation for identifying and preventing critical vulnerabilities. By integrating security testing throughout the development lifecycle, using appropriate tools, and following best practices, QA teams can significantly reduce security risks and protect both users and organizations.

Key takeaways:

  • Understand each OWASP Top 10 vulnerability category and testing approach
  • Integrate automated security scanning into CI/CD pipelines
  • Maintain comprehensive security test cases and checklists
  • Use a combination of manual and automated testing techniques
  • Stay updated on new vulnerabilities and attack vectors
  • Collaborate closely with development and security teams

Remember that security is not a one-time activity but an ongoing process requiring vigilance, continuous learning, and proactive testing throughout the application lifecycle.

FAQ

What is the OWASP Top 10 and why is it important for QA?

The OWASP Top 10 is a regularly updated report listing the ten most critical web application security risks, published by the Open Web Application Security Project. It is important for QA because these categories cover vulnerabilities responsible for over 90% of real-world web application breaches. Mastering the OWASP Top 10 gives QA engineers a structured testing framework that covers the most dangerous vulnerability classes, from Broken Access Control to Server-Side Request Forgery.

What are the most common OWASP vulnerabilities found in applications?

According to Veracode’s State of Software Security 2023, 76% of applications have at least one OWASP Top 10 vulnerability. The most prevalent are Broken Access Control (A01), which moved to the number one spot in 2021, Security Misconfiguration (A05), and Injection flaws (A03). Broken Access Control is especially common because it requires proper authorization checks on every endpoint, and even one missed check creates a vulnerability.

How do I test for SQL injection without breaking anything?

Always test on non-production environments with explicit authorization. Start with safe detection payloads like a single quote (’) to observe error responses without modifying data. Use boolean-based tests (‘AND 1=1’ vs ‘AND 1=2’) to detect injection points by comparing responses. For automated testing, use SQLMap with the –batch flag on staging environments. Never use destructive payloads like DROP TABLE, and always coordinate with developers before running active SQL injection tests.

What changed between OWASP Top 10 2017 and 2021 editions?

The 2021 edition introduced three new categories: Insecure Design (A04), Software and Data Integrity Failures (A08), and Server-Side Request Forgery (A10). Broken Access Control moved from fifth to first place, reflecting its growing prevalence. Injection dropped from first to third. Several 2017 categories were merged: XML External Entities was absorbed into Security Misconfiguration, and Cross-Site Scripting was merged into Injection. These changes reflect the evolving threat landscape and growing focus on design-level security.

Official Resources

See Also