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)
Rank | Vulnerability | Impact | Prevalence |
---|---|---|---|
A01 | Broken Access Control | High | Very Common |
A02 | Cryptographic Failures | High | Common |
A03 | Injection | Critical | Common |
A04 | Insecure Design | High | Common |
A05 | Security Misconfiguration | Medium | Very Common |
A06 | Vulnerable and Outdated Components | High | Common |
A07 | Identification and Authentication Failures | High | Common |
A08 | Software and Data Integrity Failures | High | Less Common |
A09 | Security Logging and Monitoring Failures | Medium | Common |
A10 | Server-Side Request Forgery (SSRF) | Medium | Less 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:
- Attempt to access restricted URLs directly
- Modify URL parameters (IDs, usernames)
- Test different user roles and permissions
- Check for missing function-level access controls
- 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:
- Inspect network traffic for unencrypted sensitive data
- Check browser storage (localStorage, sessionStorage, cookies)
- Review error messages for information disclosure
- Test password storage mechanisms
- 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
Tool | Category | Best For | Cost |
---|---|---|---|
OWASP ZAP | Dynamic Testing | Automated vulnerability scanning | Free |
Burp Suite | Proxy/Scanner | Manual security testing | Free/Paid |
Nikto | Web Scanner | Server vulnerability scanning | Free |
SQLMap | Injection | SQL injection detection | Free |
Nmap | Network Scanner | Port and service discovery | Free |
Snyk | Dependency Scan | Vulnerable component detection | Free/Paid |
SonarQube | Static Analysis | Code quality and security | Free/Paid |
Acunetix | Web Scanner | Comprehensive web vulnerability scan | Paid |
Nessus | Vulnerability Scanner | Infrastructure scanning | Paid |
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.