Back to Blog

Web Application Security Testing: The 10-Step Process Every QA Team Needs

Learn how to identify and prevent common web application vulnerabilities with automated and manual security testing. This comprehensive guide covers the OWASP Top 10, practical testing techniques, tools like ZAP and Burp Suite, and how to integrate security into your development lifecycle.

Scanly App

Published

10 min read

Reading time

Related articles: Also see the OWASP Top 10 vulnerabilities every QA engineer should test for, securing API endpoints as part of your application security program, and adding dynamic security scanning to your CI/CD pipeline.

Web Application Security Testing: The 10-Step Process Every QA Team Needs

In 2024, the average cost of a data breach reached $4.88 million, according to IBM's Cost of a Data Breach Report. Beyond the financial impact, security incidents erode user trust, damage brand reputation, and can lead to regulatory penalties under laws like GDPR, CCPA, and HIPAA.

Yet, despite the high stakes, many development teams treat security as an afterthought�a final checklist item before launch, if it's addressed at all. This is a dangerous mindset in a world where attackers are increasingly sophisticated, automated, and relentless.

Security testing is the practice of proactively identifying vulnerabilities in your application before attackers can exploit them. For QA engineers, developers, and founders, integrating security testing into your development lifecycle is not optional�it's essential.

In this guide, we'll cover:

  • The OWASP Top 10 vulnerabilities and how to test for them
  • Manual and automated security testing techniques
  • Tools like OWASP ZAP, Burp Suite, Snyk, and npm audit
  • How to integrate security checks into your CI/CD pipeline
  • Best practices for secure development

Whether you're building a SaaS platform, an e-commerce site, or a content management system, this article will give you the knowledge and tools to protect your users and your business.

The OWASP Top 10: A Foundation for Web Security

The Open Web Application Security Project (OWASP) is a nonprofit foundation dedicated to improving software security. Their OWASP Top 10 is the most widely recognized categorization of critical web application security risks. The 2021 version (latest as of 2026) includes:

Rank Vulnerability Description
1 Broken Access Control Failures in restricting what authenticated users can do (e.g., viewing others' data).
2 Cryptographic Failures Weak or missing encryption for sensitive data (e.g., passwords, payment info).
3 Injection Attackers inject malicious code (SQL, NoSQL, OS commands) into inputs.
4 Insecure Design Missing or ineffective security controls in the design phase.
5 Security Misconfiguration Default configs, unnecessary features enabled, verbose error messages.
6 Vulnerable and Outdated Components Using libraries/frameworks with known vulnerabilities (e.g., old npm packages).
7 Identification and Authentication Failures Weak authentication/session management (e.g., weak passwords, session fixation).
8 Software and Data Integrity Failures Untrusted code/data (e.g., unsecured CI/CD, insecure deserialization).
9 Security Logging and Monitoring Failures Lack of logging, delayed detection, no alerting for suspicious activity.
10 Server-Side Request Forgery (SSRF) Attacker tricks server into making requests to unintended locations (e.g., internal systems).

Let's dive into the most critical vulnerabilities and how to test for them.

1. Broken Access Control

What It Is: Attackers can access resources or functions they shouldn't have permission to access.

Example: A user with userId=123 can modify their profile by sending a request to /api/users/123/profile. An attacker changes the URL to /api/users/456/profile and successfully modifies another user's data.

How to Test

Manual Test:

  1. Log in as a regular user.
  2. Note the resource IDs in URLs, cookies, or API requests.
  3. Try changing the IDs to access other users' data.

Automated Test with Playwright:

import { test, expect } from '@playwright/test';

test('should not allow access to other users profiles', async ({ page, request }) => {
  // Login as user 123
  await page.goto('https://example.com/login');
  await page.fill('input[name="email"]', 'user123@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  // Extract the auth token from cookies
  const cookies = await page.context().cookies();
  const authToken = cookies.find((c) => c.name === 'auth_token')?.value;

  // Attempt to access user 456's profile with user 123's auth token
  const response = await request.get('https://example.com/api/users/456/profile', {
    headers: {
      Cookie: `auth_token=${authToken}`,
    },
  });

  // Should return 403 Forbidden or 404 Not Found
  expect([403, 404]).toContain(response.status());
});

Prevention

  • Enforce authorization checks on the server: Never trust the client.
  • Use role-based access control (RBAC) or attribute-based access control (ABAC).
  • Log access attempts to sensitive resources for monitoring.

2. Injection (SQL Injection, XSS)

SQL Injection

What It Is: Attackers inject malicious SQL queries into input fields, potentially reading, modifying, or deleting data.

Example:

-- Normal query
SELECT * FROM users WHERE username = 'john' AND password = 'secret';

-- Malicious input: ' OR '1'='1'; --
SELECT * FROM users WHERE username = '' OR '1'='1'; --' AND password = 'secret';

This query always returns true, bypassing authentication.

How to Test

Manual Test: Input ' OR '1'='1'; -- into login fields, search boxes, or any user input that interacts with a database.

Automated Test with SQLMap (a penetration testing tool):

sqlmap -u "https://example.com/login" --data="username=admin&password=pass" --level=5 --risk=3

Prevention

  • Use parameterized queries/prepared statements:

    // BAD: String concatenation
    const query = `SELECT * FROM users WHERE username = '${username}'`;
    
    // GOOD: Parameterized query
    const query = 'SELECT * FROM users WHERE username = ?';
    const result = await db.execute(query, [username]);
    
  • Use ORMs (e.g., Prisma, Sequelize, TypeORM) that abstract SQL and use parameterized queries by default.

Cross-Site Scripting (XSS)

What It Is: Attackers inject malicious JavaScript into web pages viewed by other users.

Example: User submits a comment: <script>alert('XSS Attack!')</script> If not sanitized, this script executes in every visitor's browser.

Types:

  • Stored XSS: Malicious script is stored in the database (e.g., comments, posts).
  • Reflected XSS: Malicious script is reflected off the server (e.g., search results).
  • DOM-based XSS: Vulnerability exists in client-side JavaScript.

How to Test

Manual Test: Input <script>alert('XSS')</script> in forms, URL parameters, and any user-generated content fields.

Automated Test with Playwright:

test('should sanitize user input to prevent XSS', async ({ page }) => {
  await page.goto('https://example.com/post/create');
  await page.fill('textarea[name="content"]', '<script>alert("XSS")</script>');
  await page.click('button[type="submit"]');

  await page.goto('https://example.com/posts');

  // The script tag should be escaped and not executed
  const postContent = await page.locator('.post-content').first().textContent();
  expect(postContent).toContain('<script>alert("XSS")</script>'); // Should be rendered as text, not executed
});

Prevention

  • Sanitize all user input on the server side before storing or displaying it.
  • Use Content Security Policy (CSP) headers:
    Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com
    
  • Escape output when rendering user content in HTML.

3. Cross-Site Request Forgery (CSRF)

What It Is: An attacker tricks a user into performing an action they didn't intend (e.g., transferring money, changing email) by exploiting their authenticated session.

Example: A user is logged into bank.com. They visit a malicious site that contains:

<img src="https://bank.com/transfer?to=attacker&amount=10000" />

The browser automatically includes the user's bank.com cookies, and the transfer is executed.

How to Test

Manual Test:

  1. Log into your application.
  2. Create an HTML page with a form that submits to a sensitive endpoint (e.g., /api/delete-account).
  3. Open that HTML page in a browser where you're logged in.
  4. See if the action executes.

Prevention

  • Use CSRF tokens: Generate a unique token per session and validate it on state-changing requests.

    // Server generates token
    const csrfToken = generateToken();
    res.cookie('csrf_token', csrfToken, { httpOnly: true, sameSite: 'strict' });
    
    // Client sends token in request header
    fetch('/api/delete-account', {
      method: 'POST',
      headers: {
        'X-CSRF-Token': csrfToken,
      },
    });
    
  • Use SameSite cookies: SameSite=Strict or SameSite=Lax prevents cookies from being sent in cross-origin requests.

4. Vulnerable and Outdated Components

What It Is: Using libraries, frameworks, or dependencies with known security vulnerabilities.

How to Test

npm audit (for Node.js projects):

npm audit

Output:

found 3 vulnerabilities (1 moderate, 2 high)
  run `npm audit fix` to fix them, or `npm audit` for details

Snyk (comprehensive dependency scanning):

npm install -g snyk
snyk test

Snyk provides detailed reports with fix recommendations.

Prevention

  • Keep dependencies up to date: Use npm outdated and npm update.
  • Automate security checks in CI/CD:
    # .github/workflows/security.yml
    jobs:
      security-scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - run: npm audit --audit-level=high
          - uses: snyk/actions/node@master
            env:
              SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
    
  • Use tools like Dependabot (GitHub's automated dependency updater).

5. Security Misconfiguration

What It Is: Leaving default settings, exposing sensitive files, or providing overly detailed error messages.

Examples:

  • Default admin credentials (admin/admin)
  • Exposed .env files or .git directories
  • Detailed stack traces visible to users

How to Test

Manual Test:

  • Try accessing /.env, /.git, /phpinfo.php, /admin with default credentials.
  • Trigger errors and see if stack traces are exposed.

Automated Test with OWASP ZAP:

docker run -t owasp/zap2docker-stable zap-baseline.py -t https://example.com

ZAP will scan for misconfigurations, missing headers, and other issues.

Prevention

  • Disable directory listings.
  • Remove default accounts and enforce strong password policies.
  • Use environment-specific error pages (generic messages in production, detailed logs only in development).
  • Set security headers:
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    

Tools for Security Testing

OWASP ZAP (Zed Attack Proxy)

Type: Free, open-source web application security scanner

Best For: Automated vulnerability scanning, penetration testing

Usage:

docker run -t owasp/zap2docker-stable zap-full-scan.py -t https://example.com -r report.html

ZAP will crawl your site and test for common vulnerabilities, producing an HTML report.

Burp Suite

Type: Commercial web vulnerability scanner (has a free Community Edition)

Best For: Manual penetration testing, intercepting and modifying HTTP requests

Features: Proxy, Scanner, Intruder (automated attacks), Repeater (manual testing)

Snyk

Type: Developer-first security platform

Best For: Dependency scanning, container scanning, IaC scanning

Integration:

# .github/workflows/security.yml
- uses: snyk/actions/node@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

npm audit / yarn audit

Type: Built-in Node.js package manager tool

Best For: Quick dependency vulnerability checks

Usage:

npm audit --json > audit-report.json

Playwright for Custom Security Tests

You can use Playwright to write custom security tests for your specific application logic:

test('should prevent session fixation attack', async ({ page, context }) => {
  await page.goto('https://example.com/login');
  const sessionBefore = (await context.cookies()).find((c) => c.name === 'session_id')?.value;

  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'password');
  await page.click('button[type="submit"]');

  const sessionAfter = (await context.cookies()).find((c) => c.name === 'session_id')?.value;

  // Session ID should change after login
  expect(sessionBefore).not.toEqual(sessionAfter);
});

Integrating Security Testing into CI/CD

Security testing should be automated and continuous, not a one-time audit.

Example GitHub Actions Workflow:

name: Security Checks

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '22'
      - run: npm ci
      - run: npm audit --audit-level=high
      - uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  zap-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run build
      - run: npm run start &
      - run: npx wait-on http://localhost:3000
      - run: docker run -t owasp/zap2docker-stable zap-baseline.py -t http://host.docker.internal:3000

This workflow runs on every pull request, catching vulnerabilities early.

The Shift-Left Security Mindset

Security should not be the responsibility of a single team or a final gate before deployment. It should be integrated throughout the development lifecycle:

  • Design Phase: Threat modeling, security requirements
  • Development: Secure coding practices, code reviews
  • Testing: Automated security scans, manual penetration testing
  • Deployment: Security headers, least-privilege access
  • Production: Monitoring, logging, incident response

This is known as DevSecOps�embedding security into every stage of DevOps.

Conclusion

Security testing is not a one-time checklist�it's an ongoing practice. By understanding the OWASP Top 10, using automated tools like ZAP and Snyk, writing custom security tests with Playwright, and integrating security checks into your CI/CD pipeline, you can dramatically reduce your attack surface and protect your users.

Remember: every line of code is a potential vulnerability. The question is not whether you'll be targeted�it's whether you'll be ready.

Start securing your application today. Sign up for ScanlyApp and integrate comprehensive security testing into your QA workflow.

Related Posts