前言
在現代軟體開發生命週期中,安全測試已經成為不可或缺的一環。OWASP ZAP(Zed Attack Proxy)是一款由 OWASP(Open Web Application Security Project)維護的開源動態應用程式安全測試(DAST)工具,廣泛應用於 Web 應用程式的安全掃描與滲透測試。
本文將深入介紹如何利用 OWASP ZAP 實現自動化安全測試,從基礎部署到進階的 CI/CD 整合,協助開發團隊在軟體開發流程中及早發現並修復安全漏洞。
1. OWASP ZAP 工具概述
1.1 什麼是 OWASP ZAP
OWASP ZAP 是一款免費、開源的安全測試工具,主要用於發現 Web 應用程式中的安全漏洞。它提供了以下核心功能:
- 主動掃描(Active Scan):主動發送攻擊請求以探測漏洞
- 被動掃描(Passive Scan):分析流經代理的流量,不發送額外請求
- Spider 爬蟲:自動發現網站結構與頁面
- Ajax Spider:專門處理 JavaScript 密集型應用程式
- Fuzzer:透過模糊測試發現潛在漏洞
- API 掃描:支援 OpenAPI、GraphQL 等 API 規範的掃描
1.2 ZAP 架構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| ┌─────────────────────────────────────────────────────────┐
│ OWASP ZAP │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Spider │ │ Ajax Spider │ │ Active Scan │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Passive Scan│ │ Fuzzer │ │ API Scan │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Automation Framework │
├─────────────────────────────────────────────────────────┤
│ REST API / CLI Interface │
└─────────────────────────────────────────────────────────┘
|
1.3 支援的漏洞類型
OWASP ZAP 能夠偵測多種常見的 Web 安全漏洞,包括但不限於:
| 漏洞類型 | OWASP Top 10 對應 |
|---|
| SQL Injection | A03:2021 Injection |
| Cross-Site Scripting (XSS) | A03:2021 Injection |
| Broken Access Control | A01:2021 Broken Access Control |
| Security Misconfiguration | A05:2021 Security Misconfiguration |
| Sensitive Data Exposure | A02:2021 Cryptographic Failures |
| XML External Entities (XXE) | A05:2021 Security Misconfiguration |
2. Docker 部署與設定
2.1 官方 Docker 映像檔
OWASP ZAP 提供多種 Docker 映像檔,適用於不同場景:
1
2
3
4
5
6
7
8
| # 穩定版本
docker pull ghcr.io/zaproxy/zaproxy:stable
# 每週更新版本
docker pull ghcr.io/zaproxy/zaproxy:weekly
# 最新開發版本
docker pull ghcr.io/zaproxy/zaproxy:latest
|
2.2 基本執行模式
Daemon 模式(背景執行)
1
2
3
4
5
6
7
8
9
| docker run -d --name zap \
-u zap \
-p 8080:8080 \
-v $(pwd)/zap-work:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -daemon -host 0.0.0.0 -port 8080 \
-config api.addrs.addr.name=.* \
-config api.addrs.addr.regex=true \
-config api.key=your-api-key
|
互動式 GUI 模式(使用 VNC)
1
2
3
4
5
| docker run -u zap \
-p 8080:8080 \
-p 8090:8090 \
-i ghcr.io/zaproxy/zaproxy:stable \
zap-webswing.sh
|
2.3 Docker Compose 設定
建立 docker-compose.yml 檔案以便於管理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| version: '3.8'
services:
zap:
image: ghcr.io/zaproxy/zaproxy:stable
container_name: owasp-zap
user: zap
ports:
- "8080:8080"
volumes:
- ./zap-work:/zap/wrk:rw
- ./zap-config:/home/zap/.ZAP:rw
command: >
zap.sh -daemon -host 0.0.0.0 -port 8080
-config api.addrs.addr.name=.*
-config api.addrs.addr.regex=true
-config api.key=${ZAP_API_KEY:-changeme}
environment:
- ZAP_API_KEY=${ZAP_API_KEY:-changeme}
networks:
- security-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/JSON/core/view/version/"]
interval: 30s
timeout: 10s
retries: 3
networks:
security-net:
driver: bridge
|
2.4 權限與安全性設定
1
2
3
4
5
6
7
8
9
| # 建立工作目錄並設定權限
mkdir -p zap-work zap-config
chmod 777 zap-work zap-config
# 設定 API 金鑰環境變數
export ZAP_API_KEY=$(openssl rand -hex 32)
# 啟動服務
docker-compose up -d
|
3. Automation Framework 使用
3.1 Automation Framework 簡介
OWASP ZAP 的 Automation Framework 是一個強大的自動化掃描框架,使用 YAML 格式的設定檔來定義掃描流程。它取代了傳統的腳本方式,提供更結構化且易於維護的自動化方案。
3.2 基本設定檔結構
建立 automation.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| ---
env:
contexts:
- name: "Target Application"
urls:
- "https://target-app.example.com"
includePaths:
- "https://target-app.example.com.*"
excludePaths:
- "https://target-app.example.com/logout.*"
- "https://target-app.example.com/static.*"
authentication:
method: "form"
parameters:
loginPageUrl: "https://target-app.example.com/login"
loginRequestUrl: "https://target-app.example.com/api/auth"
loginRequestBody: "username={%username%}&password={%password%}"
verification:
method: "response"
loggedInRegex: "\\Qwelcome\\E"
loggedOutRegex: "\\Qlogin\\E"
users:
- name: "test-user"
credentials:
username: "testuser"
password: "testpassword"
parameters:
failOnError: true
failOnWarning: false
progressToStdout: true
jobs:
- type: passiveScan-config
parameters:
maxAlertsPerRule: 10
scanOnlyInScope: true
- type: spider
parameters:
context: "Target Application"
user: "test-user"
maxDuration: 10
maxDepth: 5
maxChildren: 10
- type: spiderAjax
parameters:
context: "Target Application"
user: "test-user"
maxDuration: 10
maxCrawlDepth: 5
numberOfBrowsers: 2
- type: passiveScan-wait
parameters:
maxDuration: 5
- type: activeScan
parameters:
context: "Target Application"
user: "test-user"
maxRuleDurationInMins: 5
maxScanDurationInMins: 30
- type: report
parameters:
template: "modern"
reportDir: "/zap/wrk/reports"
reportFile: "zap-report"
reportTitle: "Security Scan Report"
reportDescription: "Automated security scan results"
risks:
- high
- medium
- low
- info
|
3.3 執行 Automation Framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 使用 Docker 執行自動化掃描
docker run --rm \
-v $(pwd)/automation.yaml:/zap/wrk/automation.yaml:ro \
-v $(pwd)/reports:/zap/wrk/reports:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/automation.yaml
# 設定記憶體限制
docker run --rm \
-v $(pwd)/automation.yaml:/zap/wrk/automation.yaml:ro \
-v $(pwd)/reports:/zap/wrk/reports:rw \
-e JAVA_OPTS="-Xmx2g" \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/automation.yaml
|
3.4 進階設定範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| ---
env:
contexts:
- name: "API Application"
urls:
- "https://api.example.com"
technology:
exclude:
- "Db.CouchDB"
- "Db.Firebird"
- "Language.ASP"
sessionManagement:
method: "headers"
parameters:
header: "Authorization: Bearer {%token%}"
jobs:
# 匯入 OpenAPI 規範
- type: openapi
parameters:
apiUrl: "https://api.example.com/openapi.json"
context: "API Application"
# 設定請求標頭
- type: requestor
parameters:
user: "api-user"
requests:
- url: "https://api.example.com/auth/token"
method: "POST"
httpVersion: "HTTP/1.1"
headers:
- "Content-Type: application/json"
data: '{"client_id": "test", "client_secret": "secret"}'
# 自訂掃描規則
- type: activeScan
parameters:
context: "API Application"
policy: "API-Scan-Policy"
policyDefinition:
defaultStrength: "medium"
defaultThreshold: "medium"
rules:
- id: 40012 # XSS Reflected
strength: "high"
threshold: "low"
- id: 40014 # XSS Persistent
strength: "high"
threshold: "low"
- id: 40018 # SQL Injection
strength: "insane"
threshold: "low"
|
4. API 掃描設定
4.1 OpenAPI/Swagger 掃描
OWASP ZAP 原生支援 OpenAPI 規範,可自動匯入 API 端點進行掃描。
1
2
3
4
5
6
7
8
9
| # 使用 Docker 掃描 OpenAPI
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py \
-t https://api.example.com/openapi.json \
-f openapi \
-r api-scan-report.html \
-w api-scan-report.md
|
4.2 GraphQL 掃描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # graphql-automation.yaml
---
env:
contexts:
- name: "GraphQL API"
urls:
- "https://graphql.example.com"
jobs:
- type: graphql
parameters:
endpoint: "https://graphql.example.com/graphql"
schemaUrl: "https://graphql.example.com/graphql/schema"
maxQueryDepth: 5
lenientMaxQueryDepthEnabled: true
maxArgsDepth: 5
optionalArgsEnabled: true
querySplitType: "leaf"
requestMethod: "POST"
- type: activeScan
parameters:
context: "GraphQL API"
maxScanDurationInMins: 60
- type: report
parameters:
template: "modern"
reportDir: "/zap/wrk"
reportFile: "graphql-report"
|
4.3 SOAP/WSDL 掃描
1
2
3
4
5
6
7
8
| # 掃描 SOAP Web Service
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py \
-t https://soap.example.com/service?wsdl \
-f soap \
-r soap-scan-report.html
|
4.4 API 認證處理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| # api-auth-automation.yaml
---
env:
contexts:
- name: "Authenticated API"
urls:
- "https://api.example.com"
authentication:
method: "script"
parameters:
scriptName: "jwt-auth.js"
sessionManagement:
method: "headers"
parameters:
header: "Authorization: Bearer {%token%}"
jobs:
# 先取得 JWT Token
- type: script
parameters:
action: "add"
type: "authentication"
name: "jwt-auth.js"
engine: "ECMAScript : Graal.js"
file: "/zap/wrk/scripts/jwt-auth.js"
- type: openapi
parameters:
apiUrl: "https://api.example.com/v3/api-docs"
context: "Authenticated API"
- type: activeScan
parameters:
context: "Authenticated API"
|
JWT 認證腳本範例 (jwt-auth.js):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // jwt-auth.js
function authenticate(helper, paramsValues, credentials) {
var loginUrl = "https://api.example.com/auth/login";
var requestBody = JSON.stringify({
username: credentials.getParam("username"),
password: credentials.getParam("password")
});
var msg = helper.prepareMessage();
msg.setRequestHeader(
new org.parosproxy.paros.network.HttpRequestHeader(
org.parosproxy.paros.network.HttpRequestHeader.POST,
new java.net.URI(loginUrl, true),
"HTTP/1.1"
)
);
msg.setRequestBody(requestBody);
msg.getRequestHeader().setHeader("Content-Type", "application/json");
helper.sendAndReceive(msg, false);
var response = JSON.parse(msg.getResponseBody().toString());
if (response.token) {
return response.token;
}
return null;
}
function getRequiredParamsNames() {
return ["username", "password"];
}
function getOptionalParamsNames() {
return [];
}
function getCredentialsParamsNames() {
return ["username", "password"];
}
|
5. CI/CD 整合
5.1 Jenkins Pipeline 整合
建立 Jenkinsfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
| pipeline {
agent any
environment {
ZAP_IMAGE = 'ghcr.io/zaproxy/zaproxy:stable'
TARGET_URL = 'https://staging.example.com'
ZAP_API_KEY = credentials('zap-api-key')
}
stages {
stage('Prepare') {
steps {
sh '''
mkdir -p ${WORKSPACE}/zap-reports
chmod 777 ${WORKSPACE}/zap-reports
'''
}
}
stage('Baseline Scan') {
steps {
sh '''
docker run --rm \
-v ${WORKSPACE}/zap-reports:/zap/wrk:rw \
${ZAP_IMAGE} \
zap-baseline.py \
-t ${TARGET_URL} \
-r baseline-report.html \
-x baseline-report.xml \
-J baseline-report.json \
-I
'''
}
post {
always {
archiveArtifacts artifacts: 'zap-reports/baseline-report.*'
}
}
}
stage('Full Scan') {
when {
branch 'main'
}
steps {
sh '''
docker run --rm \
-v ${WORKSPACE}/zap-reports:/zap/wrk:rw \
-v ${WORKSPACE}/automation.yaml:/zap/wrk/automation.yaml:ro \
${ZAP_IMAGE} \
zap.sh -cmd -autorun /zap/wrk/automation.yaml
'''
}
post {
always {
archiveArtifacts artifacts: 'zap-reports/**/*'
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'zap-reports',
reportFiles: 'zap-report.html',
reportName: 'ZAP Security Report'
])
}
}
}
stage('Quality Gate') {
steps {
script {
def report = readJSON file: 'zap-reports/baseline-report.json'
def highAlerts = report.site[0].alerts.findAll {
it.riskcode == '3'
}
if (highAlerts.size() > 0) {
error "發現 ${highAlerts.size()} 個高風險漏洞,建置失敗!"
}
}
}
}
}
post {
failure {
emailext(
subject: "安全掃描失敗: ${env.JOB_NAME} - Build #${env.BUILD_NUMBER}",
body: """
安全掃描發現問題,請檢查報告。
建置連結: ${env.BUILD_URL}
報告連結: ${env.BUILD_URL}ZAP_20Security_20Report/
""",
to: 'security-team@example.com'
)
}
}
}
|
5.2 GitLab CI 整合
建立 .gitlab-ci.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
| stages:
- build
- test
- security
- deploy
variables:
ZAP_IMAGE: ghcr.io/zaproxy/zaproxy:stable
TARGET_URL: https://staging.example.com
security-baseline:
stage: security
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- mkdir -p reports
- chmod 777 reports
script:
- |
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
${ZAP_IMAGE} \
zap-baseline.py \
-t ${TARGET_URL} \
-r baseline-report.html \
-x baseline-report.xml \
-J baseline-report.json \
-I
artifacts:
paths:
- reports/
reports:
junit: reports/baseline-report.xml
expire_in: 1 week
allow_failure: true
only:
- merge_requests
- main
- develop
security-full-scan:
stage: security
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- mkdir -p reports
- chmod 777 reports
script:
- |
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
-v $(pwd)/automation.yaml:/zap/wrk/automation.yaml:ro \
-e JAVA_OPTS="-Xmx4g" \
${ZAP_IMAGE} \
zap.sh -cmd -autorun /zap/wrk/automation.yaml
artifacts:
paths:
- reports/
expire_in: 30 days
only:
- main
when: manual
security-api-scan:
stage: security
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- mkdir -p reports
- chmod 777 reports
script:
- |
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
${ZAP_IMAGE} \
zap-api-scan.py \
-t ${API_SPEC_URL} \
-f openapi \
-r api-report.html \
-x api-report.xml \
-J api-report.json
artifacts:
paths:
- reports/
reports:
junit: reports/api-report.xml
expire_in: 1 week
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
|
5.3 GitHub Actions 整合
建立 .github/workflows/zap-scan.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
| name: OWASP ZAP Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # 每週一凌晨 2 點執行
env:
TARGET_URL: https://staging.example.com
jobs:
baseline-scan:
runs-on: ubuntu-latest
name: Baseline Security Scan
steps:
- name: Checkout
uses: actions/checkout@v4
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.10.0
with:
target: ${{ env.TARGET_URL }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Upload Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-baseline-report
path: report_html.html
full-scan:
runs-on: ubuntu-latest
name: Full Security Scan
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.8.0
with:
target: ${{ env.TARGET_URL }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Upload Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-full-report
path: report_html.html
api-scan:
runs-on: ubuntu-latest
name: API Security Scan
steps:
- name: Checkout
uses: actions/checkout@v4
- name: ZAP API Scan
uses: zaproxy/action-api-scan@v0.5.0
with:
target: ${{ env.TARGET_URL }}/openapi.json
format: openapi
cmd_options: '-a'
- name: Upload Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-api-report
path: report_html.html
- name: Create Issue on High Alerts
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Security Alert: High Risk Vulnerabilities Found',
body: 'The ZAP security scan has identified high-risk vulnerabilities. Please review the scan report.',
labels: ['security', 'high-priority']
})
|
6. 報告生成與解讀
6.1 報告格式
OWASP ZAP 支援多種報告格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # HTML 報告(適合人工閱讀)
docker run --rm -v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://example.com \
-r scan-report.html
# XML 報告(適合 CI/CD 整合)
docker run --rm -v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://example.com \
-x scan-report.xml
# JSON 報告(適合程式處理)
docker run --rm -v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://example.com \
-J scan-report.json
# Markdown 報告
docker run --rm -v $(pwd)/reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://example.com \
-w scan-report.md
|
6.2 自訂報告範本
在 Automation Framework 中設定自訂報告:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| jobs:
- type: report
parameters:
template: "modern" # modern, traditional, risk-confidence-html
reportDir: "/zap/wrk/reports"
reportFile: "security-report"
reportTitle: "應用程式安全掃描報告"
reportDescription: "由 OWASP ZAP 自動化框架產生"
displayReport: false
risks:
- high
- medium
- low
confidences:
- high
- medium
sections:
- instancecount
- alertdetails
- alertcount
|
6.3 報告解讀指南
風險等級說明
| 等級 | 說明 | 建議處理方式 |
|---|
| High (高) | 可能造成嚴重安全影響 | 立即修復,阻止部署 |
| Medium (中) | 可能造成中度安全影響 | 盡快修復,納入下個迭代 |
| Low (低) | 影響較小的安全問題 | 評估後排入待辦事項 |
| Informational (資訊) | 潛在問題或最佳實踐建議 | 參考並改善 |
信心等級說明
| 等級 | 說明 |
|---|
| High | 高確定性,幾乎確定是真正的漏洞 |
| Medium | 中確定性,可能需要人工驗證 |
| Low | 低確定性,可能是誤報 |
| False Positive | 已確認為誤報 |
6.4 報告解析腳本
建立 Python 腳本解析 JSON 報告:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| #!/usr/bin/env python3
"""
ZAP 報告解析腳本
解析 JSON 格式的掃描報告並產生摘要
"""
import json
import sys
from collections import defaultdict
def parse_zap_report(report_file):
with open(report_file, 'r', encoding='utf-8') as f:
report = json.load(f)
summary = defaultdict(list)
risk_count = {'High': 0, 'Medium': 0, 'Low': 0, 'Informational': 0}
for site in report.get('site', []):
for alert in site.get('alerts', []):
risk = alert.get('risk', 'Unknown')
risk_count[risk] = risk_count.get(risk, 0) + 1
summary[risk].append({
'name': alert.get('name'),
'description': alert.get('desc'),
'solution': alert.get('solution'),
'count': len(alert.get('instances', []))
})
return summary, risk_count
def generate_summary(summary, risk_count):
print("=" * 60)
print("OWASP ZAP 掃描報告摘要")
print("=" * 60)
print("\n風險統計:")
for risk, count in risk_count.items():
print(f" {risk}: {count}")
print("\n" + "-" * 60)
for risk in ['High', 'Medium', 'Low', 'Informational']:
if summary[risk]:
print(f"\n{risk} 風險漏洞:")
for alert in summary[risk]:
print(f" - {alert['name']} (發現 {alert['count']} 處)")
# 判斷是否通過安全檢查
if risk_count['High'] > 0:
print("\n結果:失敗 - 發現高風險漏洞")
return 1
elif risk_count['Medium'] > 5:
print("\n結果:警告 - 中風險漏洞過多")
return 1
else:
print("\n結果:通過")
return 0
if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python parse_report.py <report.json>")
sys.exit(1)
summary, risk_count = parse_zap_report(sys.argv[1])
exit_code = generate_summary(summary, risk_count)
sys.exit(exit_code)
|
7. 自訂規則與掃描政策
7.1 規則配置檔
建立 .zap/rules.tsv 來控制規則行為:
1
2
3
4
5
6
7
8
9
10
11
12
13
| 10015 IGNORE (Incomplete or No Cache-control and Pragma HTTP Header Set)
10020 WARN (X-Frame-Options Header Not Set)
10021 FAIL (X-Content-Type-Options Header Missing)
10035 FAIL (Strict-Transport-Security Header Not Set)
10038 IGNORE (Content Security Policy (CSP) Header Not Set)
10054 FAIL (Cookie Without SameSite Attribute)
10055 FAIL (CSP: Wildcard Directive)
10096 IGNORE (Timestamp Disclosure)
10109 WARN (Modern Web Application)
40012 FAIL (Cross Site Scripting (Reflected))
40014 FAIL (Cross Site Scripting (Persistent))
40018 FAIL (SQL Injection)
90033 FAIL (Loosely Scoped Cookie)
|
規則動作說明:
IGNORE:忽略此規則的警告WARN:發出警告但不影響結果FAIL:觸發此規則將導致掃描失敗
7.2 自訂掃描政策
在 Automation Framework 中定義掃描政策:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| jobs:
- type: activeScan
parameters:
context: "Target Application"
policy: "Custom-Policy"
policyDefinition:
defaultStrength: "medium"
defaultThreshold: "medium"
rules:
# SQL Injection 相關
- id: 40018
name: "SQL Injection"
strength: "insane"
threshold: "low"
- id: 40019
name: "SQL Injection - MySQL"
strength: "high"
threshold: "low"
- id: 40020
name: "SQL Injection - Hypersonic SQL"
strength: "default"
threshold: "off" # 關閉不相關的資料庫
- id: 40021
name: "SQL Injection - Oracle"
strength: "default"
threshold: "off"
- id: 40022
name: "SQL Injection - PostgreSQL"
strength: "high"
threshold: "low"
# XSS 相關
- id: 40012
name: "Cross Site Scripting (Reflected)"
strength: "high"
threshold: "low"
- id: 40014
name: "Cross Site Scripting (Persistent)"
strength: "high"
threshold: "low"
# 路徑遍歷
- id: 6
name: "Path Traversal"
strength: "high"
threshold: "low"
# 指令注入
- id: 90020
name: "Remote OS Command Injection"
strength: "high"
threshold: "low"
|
7.3 自訂被動掃描規則
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| jobs:
- type: passiveScan-config
parameters:
maxAlertsPerRule: 10
scanOnlyInScope: true
maxBodySizeInBytesToScan: 10000000
rules:
- id: 10015 # Incomplete Cache-control Header
threshold: "off"
- id: 10020 # X-Frame-Options Header
threshold: "low"
- id: 10021 # X-Content-Type-Options Header
threshold: "medium"
- id: 10035 # Strict-Transport-Security Header
threshold: "high"
- id: 10038 # Content Security Policy Header
threshold: "medium"
|
7.4 自訂腳本規則
建立自訂的主動掃描腳本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // custom-header-check.js
// 檢查自訂安全標頭
function scanNode(as, msg) {
var header = msg.getResponseHeader();
// 檢查自訂安全標頭
var customHeader = header.getHeader("X-Custom-Security");
if (customHeader == null) {
as.newAlert()
.setRisk(1) // Low
.setConfidence(2) // Medium
.setName("Missing X-Custom-Security Header")
.setDescription("The response is missing the X-Custom-Security header.")
.setSolution("Add the X-Custom-Security header to all responses.")
.setCweId(693)
.setWascId(15)
.setMessage(msg)
.raise();
}
}
function getCategory() {
return "Active Scanner";
}
function getName() {
return "Custom Security Header Check";
}
|
在 Automation Framework 中載入自訂腳本:
1
2
3
4
5
6
7
8
9
10
11
12
| jobs:
- type: script
parameters:
action: "add"
type: "active"
name: "custom-header-check"
engine: "ECMAScript : Graal.js"
file: "/zap/wrk/scripts/custom-header-check.js"
- type: activeScan
parameters:
context: "Target Application"
|
8. 最佳實務與效能優化
8.1 效能優化建議
記憶體配置
1
2
3
4
5
6
| # 大型網站掃描建議配置
docker run --rm \
-v $(pwd)/reports:/zap/wrk:rw \
-e JAVA_OPTS="-Xmx4g -XX:+UseG1GC" \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/automation.yaml
|
執行緒配置
1
2
3
4
5
6
7
8
9
10
11
12
13
| # automation.yaml
env:
parameters:
progressToStdout: true
jobs:
- type: activeScan
parameters:
context: "Target Application"
maxRuleDurationInMins: 5
maxScanDurationInMins: 60
threadPerHost: 5 # 每個主機的執行緒數
delayInMs: 20 # 請求間隔,避免過載目標
|
8.2 掃描範圍最佳化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| env:
contexts:
- name: "Optimized Scan"
urls:
- "https://app.example.com"
includePaths:
- "https://app.example.com/api/.*"
- "https://app.example.com/app/.*"
excludePaths:
# 排除靜態資源
- ".*\\.js$"
- ".*\\.css$"
- ".*\\.png$"
- ".*\\.jpg$"
- ".*\\.gif$"
- ".*\\.woff2?$"
# 排除不需要掃描的路徑
- ".*/logout.*"
- ".*/static/.*"
- ".*/vendor/.*"
- ".*/node_modules/.*"
technology:
exclude:
# 排除不相關的技術
- "Db.CouchDB"
- "Db.Firebird"
- "Db.HypersonicSQL"
- "Db.IBM DB2"
- "Db.Microsoft Access"
- "Db.Oracle"
- "Language.ASP"
- "Language.C"
- "OS.Linux"
- "OS.MacOS"
- "OS.Windows"
|
8.3 安全測試策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| ┌─────────────────────────────────────────────────────────────┐
│ 安全測試分層策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每次 Commit / PR │ │
│ │ - Baseline Scan(被動掃描) │ │
│ │ - 掃描時間:< 5 分鐘 │ │
│ │ - 目標:快速回饋,發現明顯問題 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每日/每次合併到主分支 │ │
│ │ - API Scan(針對 API 端點) │ │
│ │ - 掃描時間:10-30 分鐘 │ │
│ │ - 目標:確保 API 安全性 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每週/版本發布前 │ │
│ │ - Full Scan(完整主動掃描) │ │
│ │ - 掃描時間:1-4 小時 │ │
│ │ - 目標:深度安全評估 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
|
8.4 錯誤處理與重試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| env:
parameters:
failOnError: false # 發生錯誤時不立即停止
failOnWarning: false
progressToStdout: true
jobs:
- type: spider
parameters:
context: "Target Application"
maxDuration: 10
handleODataParametersVisited: true
tests:
- type: "stats"
statistic: "automation.spider.urls.added"
operator: ">="
value: 1
onFail: "warn" # 如果沒有發現 URL,發出警告而非失敗
- type: activeScan
parameters:
context: "Target Application"
maxScanDurationInMins: 60
tests:
- type: "alertCount"
alertType: "any"
operator: "<="
value: 100
onFail: "warn"
|
8.5 實務建議清單
環境隔離
- 永遠在測試/預發環境執行掃描
- 避免掃描生產環境,可能影響服務穩定性
掃描時機
- 在非尖峰時段執行完整掃描
- PR/MR 使用輕量級掃描以加快回饋
結果處理
- 建立漏洞分類與處理流程
- 定期檢視並更新規則配置
- 記錄已知的誤報以避免重複處理
持續改進
- 定期更新 ZAP 版本以獲取最新規則
- 根據應用程式特性調整掃描政策
- 追蹤掃描指標趨勢
團隊協作
- 將安全掃描報告整合到開發流程
- 建立修復時間 SLA
- 提供開發人員安全培訓
結語
OWASP ZAP 是一款功能強大的開源安全測試工具,透過本文介紹的自動化技術,您可以將安全測試無縫整合到 CI/CD 流程中,實現「安全左移」的 DevSecOps 實踐。
重點回顧:
- 使用 Docker 部署 ZAP 確保環境一致性
- Automation Framework 提供結構化的掃描配置
- 針對 API 類型選擇適合的掃描方式
- 將掃描整合到 Jenkins、GitLab CI 或 GitHub Actions
- 根據風險等級建立適當的處理流程
- 持續優化掃描效能與準確度
建議從基礎的 Baseline Scan 開始,逐步擴展到完整的自動化安全測試流程,並根據團隊需求調整策略。
參考資源