Vulnerability scanning automates the discovery of known security weaknesses in applications and infrastructure. While manual testing catches logic flaws, scanners excel at finding known patterns across large surfaces quickly.
Manual vs Automated Scanning
| Aspect | Manual Testing | Automated Scanning |
|---|---|---|
| Speed | Slow, thorough | Fast, broad coverage |
| Logic Flaws | Can detect | Usually misses |
| Known Vulns | Time-consuming | Excellent |
| False Positives | Low | Moderate to high |
| Scalability | Limited | Highly scalable |
| Cost | High (skilled testers) | Lower per scan |
The best approach combines both: automated scans for breadth, manual testing for depth.
OWASP ZAP: Your Primary Scanner
OWASP ZAP (Zed Attack Proxy) is a free, open-source security scanner maintained by the OWASP Foundation.
Setting Up ZAP
# Install ZAP
# macOS
brew install --cask owasp-zap
# Docker (recommended for CI)
docker pull ghcr.io/zaproxy/zaproxy:stable
# Start ZAP in daemon mode for scripting
docker run -u zap -p 8080:8080 \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -daemon -host 0.0.0.0 -port 8080Running a Baseline Scan
The baseline scan is a quick, passive scan that checks for common issues without actively attacking the target.
# ZAP baseline scan via Docker
docker run -t ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://target-app.com \
-r report.html
# The scan will:
# 1. Spider the application to discover pages
# 2. Passively analyze all requests and responses
# 3. Check for missing security headers
# 4. Identify information disclosure
# 5. Generate an HTML reportRunning a Full Scan
The full scan includes active attacks and takes significantly longer.
# ZAP full scan
docker run -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://target-app.com \
-r full-report.html \
-z "-config scanner.maxScanDurationInMins=60"ZAP API Scripting
import requests
import time
ZAP_API = "http://localhost:8080"
API_KEY = "your-api-key"
TARGET = "https://target-app.com"
# Start a spider scan
spider_response = requests.get(f"{ZAP_API}/JSON/spider/action/scan/", params={
"apikey": API_KEY,
"url": TARGET,
"maxChildren": 10
})
scan_id = spider_response.json()["scan"]
# Wait for spider to complete
while True:
progress = requests.get(f"{ZAP_API}/JSON/spider/view/status/", params={
"apikey": API_KEY,
"scanId": scan_id
})
status = int(progress.json()["status"])
print(f"Spider progress: {status}%")
if status >= 100:
break
time.sleep(5)
# Start active scan
active_response = requests.get(f"{ZAP_API}/JSON/ascan/action/scan/", params={
"apikey": API_KEY,
"url": TARGET
})
active_scan_id = active_response.json()["scan"]
# Wait for active scan to complete
while True:
progress = requests.get(f"{ZAP_API}/JSON/ascan/view/status/", params={
"apikey": API_KEY,
"scanId": active_scan_id
})
status = int(progress.json()["status"])
print(f"Active scan progress: {status}%")
if status >= 100:
break
time.sleep(10)
# Get alerts
alerts = requests.get(f"{ZAP_API}/JSON/core/view/alerts/", params={
"apikey": API_KEY,
"baseurl": TARGET
})
for alert in alerts.json()["alerts"]:
print(f"[{alert['risk']}] {alert['alert']}")
print(f" URL: {alert['url']}")
print(f" Description: {alert['description'][:100]}")
print()Nikto: Web Server Scanner
Nikto specializes in web server misconfigurations and known vulnerable files.
# Install Nikto
sudo apt install nikto # Debian/Ubuntu
brew install nikto # macOS
# Basic scan
nikto -h https://target-app.com -o report.html -Format html
# Scan specific ports
nikto -h target-app.com -p 80,443,8080
# Tune the scan for specific test categories
# 1 = Interesting File / Seen in logs
# 2 = Misconfiguration / Default File
# 3 = Information Disclosure
# 4 = Injection (XSS/Script/HTML)
nikto -h https://target-app.com -Tuning 1234
Nuclei: Template-Based Scanner
Nuclei uses YAML templates to detect vulnerabilities, making it highly extensible.
# Install Nuclei
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Or via Docker
docker pull projectdiscovery/nuclei:latest
# Run with default templates
nuclei -u https://target-app.com
# Run specific template categories
nuclei -u https://target-app.com -tags cve,misconfig
# Run with severity filter
nuclei -u https://target-app.com -severity critical,high
Writing a Custom Nuclei Template
id: custom-api-key-exposure
info:
name: API Key Exposure Check
author: your-name
severity: high
description: Checks for exposed API keys in JavaScript files
tags: exposure,api,keys
requests:
- method: GET
path:
- "{{BaseURL}}/js/app.js"
- "{{BaseURL}}/js/config.js"
- "{{BaseURL}}/static/js/main.js"
matchers-condition: or
matchers:
- type: regex
regex:
- "(?i)(api[_-]?key|apikey)\\s*[:=]\\s*['\"][a-zA-Z0-9]{20,}['\"]"
- "(?i)(secret[_-]?key)\\s*[:=]\\s*['\"][a-zA-Z0-9]{20,}['\"]"
- "(?i)(access[_-]?token)\\s*[:=]\\s*['\"][a-zA-Z0-9]{20,}['\"]"Interpreting Scan Results
Not every finding is a real vulnerability. You must triage results carefully.
Severity Classification
Critical (CVSS 9.0-10.0)
Action: Fix immediately
Example: Remote code execution, SQL injection
High (CVSS 7.0-8.9)
Action: Fix within 24-48 hours
Example: Authentication bypass, stored XSS
Medium (CVSS 4.0-6.9)
Action: Fix within 1-2 weeks
Example: Missing security headers, CSRF
Low (CVSS 0.1-3.9)
Action: Fix in next release cycle
Example: Information disclosure, verbose errors
Informational (CVSS 0.0)
Action: Note for future reference
Example: Server version disclosureHandling False Positives
# Document false positives in a suppression file
# false-positives.json
{
"suppressions": [
{
"tool": "zap",
"alert_id": "10038",
"reason": "CSP header intentionally allows inline scripts for analytics",
"reviewed_by": "security-team",
"date": "2026-03-24"
},
{
"tool": "nikto",
"finding": "OSVDB-3092: /docs/: Directory listing",
"reason": "Public documentation directory, intentionally open",
"reviewed_by": "security-team",
"date": "2026-03-24"
}
]
}Building a Scan Report
# Vulnerability Scan Report
## Executive Summary
- Target: https://target-app.com
- Scan Date: 2026-03-24
- Tools Used: OWASP ZAP, Nikto, Nuclei
- Critical: 1 | High: 3 | Medium: 5 | Low: 8
## Critical Findings
### 1. SQL Injection in Product Search
- **Severity**: Critical
- **URL**: /api/products?search=
- **Evidence**: Response includes database error when injecting single quote
- **Remediation**: Use parameterized queries
- **CVSS**: 9.8
## Recommendations
1. Implement parameterized queries across all database interactions
2. Add Content-Security-Policy header
3. Enable HTTP Strict Transport Security
4. Remove server version headersKey Takeaways
- Automated scanners find known vulnerabilities quickly but miss logic flaws
- OWASP ZAP is the best free option for web application scanning
- Nuclei templates let you create custom checks for your specific stack
- Always triage results -- false positives are common in automated scanning
- Combine multiple scanners for better coverage
Next, you will move from scanning to actively exploiting vulnerabilities through penetration testing.