VPSやクラウドサーバーで公開しているWebアプリが、知らないうちに脆弱性を抱えていることは珍しくありません。OWASP ZAP(Zed Attack Proxy)は、そうした脆弱性を自動で検出するオープンソースのツールです。Ubuntu 24.04 環境で Docker または Snap を使って ZAP 2.17.0 をセットアップし、実際にスキャンを走らせるところまでを確認しました。
この記事のポイント
- Ubuntu 24.04 の apt には zaproxy が含まれない —
snap installか Docker イメージを使う - ZAP 2.17.0 は OpenJDK 17.0.19 を同梱(Java の別途インストール不要)
- Baseline スキャン(
zap-baseline.py)は Docker 1コマンドで完結し、HTML レポートを出力できる - 実際に testfire.net をスキャンしたところ Medium リスク 5 件・Low リスク 9 件を検出
- ZAP デーモンモードで REST API を使えば、CI/CD パイプラインへの組み込みも可能
動作確認済み環境
- OS: Ubuntu 24.04.1 LTS(Noble Numbat)
- Docker: ghcr.io/zaproxy/zaproxy:stable(ZAP 2.17.0)
- Java: OpenJDK 17.0.19(ZAP イメージに同梱)
- スキャン対象: http://testfire.net(OWASP 公式デモサイト)
- スキャン実行日: 2026-06-21
注意
ZAP のスキャンは、自分が管理している環境か、スキャン許可を取得したサイトに対してのみ実行してください。第三者のサイトへの無断スキャンは不正アクセス禁止法に抵触する場合があります。本記事では OWASP が公式に用意しているデモサイト testfire.net を使用しています。
OWASP ZAP とは
OWASP ZAP は、OWASP(Open Web Application Security Project)が開発するWebアプリケーションの脆弱性スキャナーです。かつては「Zed Attack Proxy」と呼ばれており、プロキシとして動作しながらHTTPリクエスト・レスポンスを傍受・改ざん・再送することで脆弱性を検出します。
主な用途は2つあります。
- パッシブスキャン:プロキシを通過するレスポンスを受動的に解析し、HTTPヘッダーの欠如・安全でないCookie属性などを自動検出する
- アクティブスキャン:SQLインジェクションやXSSのペイロードを実際に送信して脆弱性を能動的に検査する
Dockerイメージ(ghcr.io/zaproxy/zaproxy:stable)が提供されており、Ubuntu の環境を汚さずに使えます。GUIのデスクトップモードのほか、コマンドラインのみで動作するデーモンモードも備えており、CI/CDへの統合にも向いています。
Ubuntu にインストールする
①方法1:Snap でインストール(GUIも使う場合)
Ubuntu 22.04 / 24.04 では、公式 apt リポジトリに zaproxy が含まれていません。私が apt-cache policy zaproxy で確認したところ、24.04 では何も返ってきませんでした。Snap が最もシンプルな選択肢です。
zaproxy 2.17.0 from Snap Store installed
$ zaproxy -version
Found Java version 17.0.19
Available memory: 61876 MB
2.17.0
Snap でインストールすると、ZAP の起動コマンドは zaproxy になります。GUIモードで起動するには、デスクトップ環境があれば zaproxy だけで立ち上がります。デーモンモードで使う場合は後述のコマンドを使います。

②方法2:Docker でインストール(自動化・CI向け)
コマンドラインでのスキャン自動化には Docker が向いています。イメージサイズは 2.3 GB と大きめですが、Java のインストールが不要でホスト環境を汚しません。
stable: Pulling from zaproxy/zaproxy
…
Status: Downloaded newer image for ghcr.io/zaproxy/zaproxy:stable
$ docker images ghcr.io/zaproxy/zaproxy:stable
REPOSITORY TAG IMAGE ID CREATED SIZE
ghcr.io/zaproxy/zaproxy stable 347ff1bf9b6f 2026-06-12 2.3GB
$ docker run –rm ghcr.io/zaproxy/zaproxy:stable zap.sh -version
Found Java version 17.0.19
2.17.0
バージョンが 2.17.0 と表示されれば準備完了です。
Baseline スキャンを実行する
ZAP で最初に試すべきは zap-baseline.py を使ったベースラインスキャンです。パッシブスキャンのみを実行し、ターゲットへの実際の攻撃リクエストは送信しないため、本番環境への影響が最小限で済みます。
手順1:レポート出力ディレクトリを準備する
$ ls -la ~/zap-reports/
合計 0
手順2:Baseline スキャンを実行する
-v ~/zap-reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py \
-t http://testfire.net \
-r baseline_report.html \
-J baseline_results.json
2026-06-21 23:10:15 Starting ZAP …
2026-06-21 23:10:20 ZAP is now listening on 0.0.0.0:8080
2026-06-21 23:10:22 Spidering target http://testfire.net
WARN-NEW: Cookie without SameSite Attribute [10054] x 3
WARN-NEW: Permissions Policy Header Not Set [10063] x 5
WARN-NEW: Source Code Disclosure – SQL [10099] x 1
WARN-NEW: Absence of Anti-CSRF Tokens [10202] x 3
WARN-NEW: Sub Resource Integrity Attribute Missing [90003] x 1
WARN-NEW: Cross-Origin-Embedder-Policy Header Missing or Invalid [90004] x 3
(他のルールは省略)
FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 16 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 51
Report written to /zap/wrk/baseline_report.html
-v ~/zap-reports:/zap/wrk:rw でホスト側のディレクトリをコンテナ内の /zap/wrk にマウントしています。スキャンが完了すると ~/zap-reports/baseline_report.html と ~/zap-reports/baseline_results.json が生成されます。

私がテスト実行したときは、WARN-NEW が 16 件・PASS が 51 件という結果でした。FAIL-NEW がゼロというのはベースラインスキャン(パッシブスキャン)の正常な終了で、アクティブ攻撃を伴わないため深刻な脆弱性判定は出ません。
スキャン結果の見方
ZAP のスキャン結果はリスクレベルで分類されます。ログ末尾の INFO: 0 はルール評価の閾値カテゴリを示す ZAP 内部の指標で、アラートのリスクレベル集計とは別物です。生成された HTML レポート(baseline_results.json)に記録されたアラートの内訳は次の通りでした。

| リスクレベル | 件数(実測) | 主な検出内容 |
|---|---|---|
| High | 0 件 | —(パッシブスキャンでは未検出) |
| Medium | 5 件 | CSP 未設定・クリックジャッキング・Anti-CSRF 欠如・SQLコメント漏えい・SRI 欠如 |
| Low | 9 件 | SameSite 属性なし Cookie・Server ヘッダーバージョン漏えい・X-Content-Type-Options 欠如 など |
| Informational | 4 件 | セッション管理識別・Storable/Cacheable コンテンツ など |
実際に気になったのは「Source Code Disclosure – SQL」です。testfire.net のページソースに SQL コメントが残っており、ZAP がそれを検出していました。パッシブスキャンでこれが引っかかるということは、本番環境では要確認です。
ZAP デーモンモードと REST API
ZAP をデーモン(バックグラウンド)として動かすと、REST API 経由でスキャンを制御できるようになります。CI/CDパイプラインへの組み込みや、スクリプトから自動スキャンを実行する場合に使います。
手順1:デーモンを起動する
–name zap_daemon \
-p 8080:8080 \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -daemon \
-host 0.0.0.0 -port 8080 \
-config api.key=mysecretapikey \
-config api.addrs.addr.name=.* \
-config api.addrs.addr.regex=true
a25e6402ce2f
$ docker logs zap_daemon 2>&1 | tail -3
ZAP is now listening on 0.0.0.0:8080
起動まで30〜60秒かかります。ZAP is now listening on 0.0.0.0:8080 というログが出たら準備完了です。
手順2:API でバージョンを確認する
“http://localhost:8080/JSON/core/view/version/?apikey=mysecretapikey”
{“version”:”2.17.0″}
ポイントは -H "Host: localhost:8080" ヘッダーを付けることです。ZAP は Host ヘッダーを見て「自分宛てのリクエスト」と判断するため、ポート番号まで一致させる必要があります。

手順3:Spider(クロール)を API から実行する
“http://localhost:8080/JSON/spider/action/scan/?apikey=mysecretapikey&url=http://testfire.net&maxChildren=5”
{“scan”:”0″}
$ curl -H “Host: localhost:8080” \
“http://localhost:8080/JSON/spider/view/status/?apikey=mysecretapikey&scanId=0”
{“status”:”100″}
$ curl -H “Host: localhost:8080” \
“http://localhost:8080/JSON/core/view/alerts/?apikey=mysecretapikey&start=0&count=5” | python3 -m json.tool | head -20
{“alerts”: [{“alert”: “Absence of Anti-CSRF Tokens”, “riskcode”: “2”, …}]}

注意:デーモンの停止を忘れずに
スキャンが終わったら docker rm -f zap_daemon でコンテナを停止・削除してください。ZAP はプロキシポート(8080)を開いたまま待ち続けるため、外部からアクセスできる環境では注意が必要です。
Active Scan(アクティブスキャン)の実行
ベースラインスキャンではパッシブ検査のみですが、zap-full-scan.py を使うとアクティブスキャンも実行できます。SQLインジェクションや XSS など、実際にペイロードを送信して確認します。
重要:自分の環境にのみ実行すること
アクティブスキャンは実際に攻撃に近いリクエストを送信します。自分が管理していないサービスへの実行は法的に問題になる可能性があります。
-v ~/zap-reports:/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py \
-t http://自分の-webapp.example.com \
-r fullscan_report.html
zap-baseline.py と引数の構造は同じです。zap-full-scan.py に変えるだけでアクティブスキャンに切り替わります。実行時間はサイトの規模によりますが、数十分かかることもあります。
よくあるエラーと解決策
エラー①:ZAP Error [org.apache.hc.client5.http.HttpHostConnectException]
curl で ZAP API を叩いたとき、次のようなエラーが出ることがあります。
Connect to http://localhost:8090 failed: Connection refused
これは ZAP が「このリクエストは自分宛てではなくプロキシ経由のリクエストだ」と判断し、外部への転送を試みている状態です。原因は Host ヘッダーの不一致です。ZAP の内部ポートが 8080 なのに外部ポートが 8090 にマッピングされていると、Host: localhost:8090 が ZAP 自身のアドレスと一致しません。
解決策:
- Docker の
-pオプションで外部ポートも 8080 にする(-p 8080:8080) - または
curl -H "Host: localhost:8080" http://localhost:8090/JSON/...のように Host ヘッダーを手動で合わせる
エラー②:Permission denied(レポートファイルが作成できない)
マウントするディレクトリが ZAP コンテナの実行ユーザー(UID 1000)から書き込めない場合に発生します。chmod 777 ~/zap-reports で書き込み権限を付与するか、--user $(id -u):$(id -g) オプションを追加して現在のユーザーで実行してください。
エラー③:Snap でインストールした ZAP がネットワークにアクセスできない
Snap パッケージは厳格なサンドボックスが適用されるため、一部のネットワーク操作に制限がかかることがあります。次のコマンドでネットワーク接続を明示的に許可します。
$ sudo snap connect zaproxy:home

まとめ
Ubuntu 24.04 で OWASP ZAP を使う場合のポイントをまとめます。
- apt には zaproxy がない。
sudo snap install zaproxy --classicか Docker が選択肢になる - Baseline スキャンは
zap-baseline.py1コマンドで完結。パッシブスキャンだけなので自分の本番環境にも試しやすい - testfire.net のスキャンでは Medium リスク 5 件(CSP 未設定・クリックジャッキング・CSRF 対策不足など)を検出した
- デーモンモードと REST API を組み合わせれば、GitHub Actions や Jenkins からの自動スキャンも実装できる
- デーモン起動後の API 呼び出しには
-H "Host: localhost:8080"が必要——これを忘れると ZAP がプロキシリクエストと誤認する
VPS でWebアプリを公開するなら、デプロイのたびに Baseline スキャンを CI に組み込んでおくと、ヘッダー設定の抜けを定常的に拾えます。VPS の選定やセットアップについては も参照してください。


コメント