Files
2025-11-29 17:51:02 +08:00

9.8 KiB

ZAP False Positive Handling Guide

Guide for identifying, verifying, and suppressing false positives in OWASP ZAP scan results.

Overview

DAST tools like ZAP generate false positives - alerts for issues that aren't actually exploitable vulnerabilities. This guide helps you:

  1. Identify common false positives
  2. Verify findings manually
  3. Suppress false positives in future scans
  4. Tune scan policies

Common False Positives

1. X-Content-Type-Options Missing

Alert: Missing X-Content-Type-Options header

False Positive Scenario:

  • Static content served by CDNs
  • Third-party resources
  • Legacy browsers not supported

Verification:

curl -I https://example.com/static/script.js
# Check if browser performs MIME sniffing

When to Suppress:

  • Static content only (CSS, JS, images)
  • Content served from trusted CDN
  • No user-controlled content in responses

Suppression Rule:

10021	https://cdn.example.com/.*	.*	693	IGNORE

Alert: Cookie without Secure flag set

False Positive Scenario:

  • Development/testing environments (HTTP)
  • Non-sensitive cookies (analytics, preferences)
  • Localhost testing

Verification:

curl -I https://example.com
# Check Set-Cookie headers
# Verify if cookie contains sensitive data

When to Suppress:

  • Non-sensitive cookies (theme preference, language)
  • HTTP-only development environments
  • Third-party analytics cookies

Suppression Rule:

10054	https://example.com.*	_ga|_gid|theme	614	WARN

3. Cross-Domain JavaScript Source File Inclusion

Alert: JavaScript loaded from external domain

False Positive Scenario:

  • Legitimate CDN usage (jQuery, Bootstrap, etc.)
  • Third-party integrations (Google Analytics, Stripe)
  • Using Subresource Integrity (SRI)

Verification:

<!-- Check if SRI is used -->
<script src="https://cdn.example.com/library.js"
        integrity="sha384-HASH"
        crossorigin="anonymous"></script>

When to Suppress:

  • CDN resources with SRI
  • Trusted third-party services
  • Company-owned CDN domains

Suppression Rule:

10017	https://example.com/.*	https://cdn.jsdelivr.net/.*	829	IGNORE

4. Timestamp Disclosure

Alert: Unix timestamps found in response

False Positive Scenario:

  • Legitimate timestamp fields in API responses
  • Non-sensitive metadata
  • Public timestamps (post dates, etc.)

Verification:

{
  "created_at": 1640995200,  // Legitimate field
  "post_date": "2022-01-01"
}

When to Suppress:

  • API responses with datetime fields
  • Public-facing timestamps
  • Non-sensitive metadata

Suppression Rule:

10096	https://api.example.com/.*	created_at|updated_at	200	IGNORE

5. Server Version Disclosure

Alert: Server version exposed in headers

False Positive Scenario:

  • Behind WAF/load balancer (version is of proxy, not app server)
  • Generic server headers
  • Already public knowledge

Verification:

curl -I https://example.com | grep Server
# Check if version matches actual server

When to Suppress:

  • Proxy/WAF version (not actual app server)
  • Generic headers without version numbers
  • When other compensating controls exist

Suppression Rule:

10036	https://example.com.*	.*	200	WARN

Verification Methodology

Step 1: Understand the Alert

Review ZAP alert details:

  • Description: What is the potential vulnerability?
  • Evidence: What triggered the alert?
  • CWE/OWASP Mapping: What category does it fall under?
  • Risk Level: How severe is it?

Step 2: Reproduce Manually

Attempt to exploit the vulnerability:

# For XSS alerts
curl "https://example.com/search?q=<script>alert(1)</script>"
# Check if script is reflected unencoded

# For SQL injection alerts
curl "https://example.com/api/user?id=1' OR '1'='1"
# Check for SQL errors or unexpected behavior

# For path traversal alerts
curl "https://example.com/download?file=../../etc/passwd"
# Check if file is accessible

Step 3: Check Context

Consider the application context:

  • Is the functionality available to unauthenticated users?
  • Does it handle sensitive data?
  • Are there compensating controls (WAF, input validation)?

Step 4: Document Decision

Create documentation for suppression decisions:

## Alert: SQL Injection in /api/user

**Decision:** False Positive

**Rationale:**
- Endpoint requires authentication
- Input is validated server-side (allowlist: 0-9 only)
- WAF rule blocks SQL injection patterns
- Manual testing confirmed no injection possible

**Suppressed:** Yes (Rule ID 40018, /api/user endpoint)

**Reviewed by:** security-team@example.com
**Date:** 2024-01-15

Creating Suppression Rules

Rules File Format

ZAP uses TSV (tab-separated values) format:

alert_id	URL_pattern	parameter	CWE_id	action
  • alert_id: ZAP alert ID (e.g., 40018 for SQL Injection)
  • URL_pattern: Regex pattern for URL
  • parameter: Parameter name (or .* for all)
  • CWE_id: CWE identifier
  • action: IGNORE, WARN, or FAIL

Example Rules File

.zap/rules.tsv:

# Suppress X-Content-Type-Options for CDN static content
10021	https://cdn.example.com/static/.*	.*	693	IGNORE

# Warn (don't fail) on analytics cookies without Secure flag
10054	https://example.com/.*	_ga|_gid	614	WARN

# Ignore timestamp disclosure in API responses
10096	https://api.example.com/.*	.*	200	IGNORE

# Ignore legitimate external JavaScript (with SRI)
10017	https://example.com/.*	https://cdn.jsdelivr.net/.*	829	IGNORE

# Suppress CSRF warnings for stateless API
10202	https://api.example.com/.*	.*	352	IGNORE

Using Rules File

# Baseline scan with rules
docker run -t zaproxy/zap-stable zap-baseline.py \
  -t https://example.com \
  -c .zap/rules.tsv \
  -r report.html

# Full scan with rules
docker run -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable zap-full-scan.py \
  -t https://example.com \
  -c /zap/wrk/.zap/rules.tsv \
  -r /zap/wrk/report.html

Custom Scan Policies

Disable Entire Scan Rules

Create custom scan policy to disable problematic rules:

  1. Via ZAP GUI:

    • Analyze > Scan Policy Manager
    • Create new policy
    • Disable specific rules
    • Export policy file
  2. Via Automation Framework:

# zap_automation.yaml
jobs:
  - type: activeScan
    parameters:
      policy: Custom-Policy
    rules:
      - id: 40018  # SQL Injection
        threshold: MEDIUM
        strength: HIGH
      - id: 10202  # CSRF
        threshold: OFF  # Disable completely

Handling Different Alert Types

High-Risk Alerts (Never Suppress Without Verification)

  • SQL Injection
  • Command Injection
  • Remote Code Execution
  • Authentication Bypass
  • Server-Side Request Forgery (SSRF)

Process:

  1. Manual verification required
  2. Security team review
  3. Document compensating controls
  4. Re-test after fixes

Medium-Risk Alerts (Contextual Suppression)

  • XSS (if output is properly encoded)
  • CSRF (if tokens are implemented)
  • Missing headers (if compensating controls exist)

Process:

  1. Verify finding
  2. Check for compensating controls
  3. Document decision
  4. Suppress with WARN (not IGNORE)

Low-Risk Alerts (Can Be Suppressed)

  • Informational headers
  • Timestamp disclosure
  • Technology fingerprinting

Process:

  1. Quick verification
  2. Document reason
  3. Suppress with IGNORE

Quality Assurance

Review Suppression Rules Regularly

# Monthly review checklist
- [ ] Review all suppression rules for continued relevance
- [ ] Check if suppressed issues have been fixed
- [ ] Verify compensating controls are still in place
- [ ] Update rules file with new false positives

Track Suppression Metrics

Monitor suppression trends:

# Count suppressions by alert type
grep -v '^#' .zap/rules.tsv | awk '{print $1}' | sort | uniq -c

# Alert if suppression count increases significantly

Peer Review Process

Require security team approval for suppressing high-risk alerts:

# .github/workflows/security-review.yml
- name: Check for new suppressions
  run: |
    git diff origin/main .zap/rules.tsv > suppressions.diff
    if [ -s suppressions.diff ]; then
      echo "New suppressions require security team review"
      # Notify security team
    fi

Anti-Patterns to Avoid

Don't Suppress Everything

Never create blanket suppression rules:

# BAD: Suppresses all XSS findings
40012	.*	.*	79	IGNORE

Don't Suppress Without Documentation

Always document why a finding is suppressed:

# BAD: No context
10054	https://example.com/.*	session_id	614	IGNORE

# GOOD: Documented reason
# Session cookie is HTTPS-only in production; suppressing for staging environment
10054	https://staging.example.com/.*	session_id	614	IGNORE

Don't Ignore High-Risk Findings

Never suppress critical vulnerabilities without thorough investigation:

# DANGEROUS: Never suppress SQL injection without verification
40018	https://example.com/.*	.*	89	IGNORE

Tools and Scripts

Analyze ZAP JSON Report

#!/usr/bin/env python3
import json
import sys

with open('report.json') as f:
    report = json.load(f)

false_positives = []
for site in report['site']:
    for alert in site['alerts']:
        if alert['risk'] in ['High', 'Medium']:
            print(f"{alert['alert']} - {alert['risk']}")
            print(f"  URL: {alert['url']}")
            print(f"  Evidence: {alert.get('evidence', 'N/A')}")
            print()

Generate Suppression Rules Template

# Extract unique alert IDs from report
jq -r '.site[].alerts[] | "\(.pluginid)\t\(.url)\t.*\t\(.cweid)\tWARN"' report.json \
  | sort -u > rules-template.tsv

Additional Resources