Security (as discussed in Penetration Testing Basics for QA Testers) testing is a critical aspect of quality assurance that ensures applications are protected against malicious attacks and unauthorized access. The OWASP (as discussed in Security Testing for QA: A Practical Guide) (Open Web Application Security Project) Top 10 represents the most critical security risks to web applications, providing a foundation for QA professionals to build comprehensive security testing strategies.

This guide explores each OWASP (as discussed in OWASP ZAP Automation: Security Scanning in CI/CD) Top 10 vulnerability, practical testing approaches, tools for detection, and best practices for identifying and mitigating security risks in modern applications.

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

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.