OWASP ZAP 自動化安全測試

OWASP ZAP Automated Security Testing

前言

在現代軟體開發生命週期中,安全測試已經成為不可或缺的一環。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 InjectionA03:2021 Injection
Cross-Site Scripting (XSS)A03:2021 Injection
Broken Access ControlA01:2021 Broken Access Control
Security MisconfigurationA05:2021 Security Misconfiguration
Sensitive Data ExposureA02: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 實務建議清單

  1. 環境隔離

    • 永遠在測試/預發環境執行掃描
    • 避免掃描生產環境,可能影響服務穩定性
  2. 掃描時機

    • 在非尖峰時段執行完整掃描
    • PR/MR 使用輕量級掃描以加快回饋
  3. 結果處理

    • 建立漏洞分類與處理流程
    • 定期檢視並更新規則配置
    • 記錄已知的誤報以避免重複處理
  4. 持續改進

    • 定期更新 ZAP 版本以獲取最新規則
    • 根據應用程式特性調整掃描政策
    • 追蹤掃描指標趨勢
  5. 團隊協作

    • 將安全掃描報告整合到開發流程
    • 建立修復時間 SLA
    • 提供開發人員安全培訓

結語

OWASP ZAP 是一款功能強大的開源安全測試工具,透過本文介紹的自動化技術,您可以將安全測試無縫整合到 CI/CD 流程中,實現「安全左移」的 DevSecOps 實踐。

重點回顧:

  • 使用 Docker 部署 ZAP 確保環境一致性
  • Automation Framework 提供結構化的掃描配置
  • 針對 API 類型選擇適合的掃描方式
  • 將掃描整合到 Jenkins、GitLab CI 或 GitHub Actions
  • 根據風險等級建立適當的處理流程
  • 持續優化掃描效能與準確度

建議從基礎的 Baseline Scan 開始,逐步擴展到完整的自動化安全測試流程,並根據團隊需求調整策略。


參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy