Skip to content

OWASP ZAP の Zest シナリオを 4.4 に移行し、CI とローカルで実行できるようにする - #7196

Draft
nanasess wants to merge 33 commits into
EC-CUBE:4.4from
nanasess:feature/add-zst-scripts
Draft

nanasess wants to merge 33 commits into
EC-CUBE:4.4from
nanasess:feature/add-zst-scripts

Conversation

@nanasess

@nanasess nanasess commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

概要(Overview・Refs Issue)

Refs #7190

OWASP ZAP の Zest シナリオ (zap/scripts/*.zst) を 4.4 に移行し、.github/workflows/zaproxy.yml とローカルの両方で実行できるようにします。あわせて、4.4 で追加された機能のシナリオを追加します。

  • 既存シナリオの 4.4 対応 (パスワードポリシー、_token の抽出、基本設定の追加項目、/help/guide、固定の ECCUBE-CSRF-TOKEN など)
  • 自動実行の仕組み (front_login コンテキスト、セッションの保持、thread_per_host、Path Traversal の誤検知の除外)
  • CI の修正 (APP_ENV=prod、バンドル版プラグインの 4.4 版、再生失敗の検出、ポリシーとターゲットの指定、ZAP のイメージを公式の ghcr.io/zaproxy/zaproxy:stable に変更)
  • ローカル実行スクリプト (zap/local/) と CI の共通スクリプト (zap/bin/)、zap/README.md
  • 4.4 の新機能のシナリオ: 返品・返金申請 (マイページ・管理画面)、FAQ、Cookie 同意、購入手続き中のお届け先の編集・削除、FAQ・定休日カレンダーのブロック
  • 網羅性を調べる仕組み: シナリオが通らないルートの一覧 (zap/bin/coverage.sh)、リクエストごとに攻撃が届いた件数と応答の内訳 (zap/bin/attack_counts.sh、attacks.tsv)
  • multipart/form-data のリクエストに攻撃が届くようにするシナリオの修正と、その検査 (zap/bin/lint_zst.sh。CI の Prepare matrix で実行)
  • スキャンのポリシーから、検出の見込みが無く時間だけかかる Cross Site Scripting (DOM Based) (ルール 40026) を外す (EC-CUBE ポリシー)
  • スキャン用のパッチの補助モード (zap/patches/、workflow_dispatch の patches。既定では当てない)
  • 能動スキャンの攻撃が画面の処理まで届いていなかったシナリオの修正 (商品登録・会員登録・受注登録・メルマガ配信・パスワード再設定・返品・返金申請・商品レビュー・おすすめ商品) と、Smoke での入力エラーの画面の検出 (zap/scripts/report_form_errors.js)
  • シナリオが通らなかったルートの追加 (通らないルートを 63 本から 38 本に。残りはエージェントコマース・eccube-api4・2 段階認証とセキュリティ設定)
  • メールの受信を mailcatcher から mailpit に替える (docker-compose.yml の mailcatcher を mailpit に置き換える #7205)

方針(Policy)

  • 既存のシナリオは録画し直さず、修正して移行しました。パスワードは E2E と同じ EccubeE2ePassword1 にそろえています。
  • EC-CUBE 本体 (src/・app/) には変更を加えていません。zap/ 以外の変更は、ワークフローと ZAP 用の Docker Compose の設定と、chore: docker-compose.yml の mailcatcher を mailpit に置き換える #7207 (docker-compose.yml などの mailcatcher を mailpit に置き換える) の cherry-pick です。chore: docker-compose.yml の mailcatcher を mailpit に置き換える #7207 が先にマージされれば、その分の差分は消えます。
  • CI とローカルは同じスクリプト (zap/bin/) を使います。ワークフローはコンテナの起動と成果物のアップロードだけを受け持ちます。
  • ターゲットの一覧は zap/targets.json に置き、CI のマトリクスとローカル実行の両方がこれを読みます。
  • バンドル版プラグインのうち、公開されていない 1 本は CI から取得できないため導入しません。これを使うシナリオはありません。

実装に関する補足(Appendix)

  • 結果の判定: ZAP は Zest のアサーションが失敗しても自動化プランを成功として扱います。そのためログから判定し、High のアラートかジョブのエラーがあれば失敗にしています。再生の失敗は、Smoke ポリシー (全ルール OFF) ではエラー、Sequence ポリシーでは警告として扱います (zap/bin/check_results.sh)。
  • Path Traversal (ルール 6): 比較用の存在しないファイル名 (38 文字) が入力欄の文字数上限でバリデーションエラーになるだけで High が出ます。根拠 (evidence) の無いものは High の判定から外し、レポートには残します。検証用に仕込んだ実際のパストラバーサルは根拠付きで検出され、見落とさないことを確認しました。
  • APP_ENV: docker-compose.yml に APP_ENV: "dev" が直書きされていて、これまで CI でも dev で動いていました。docker-compose.owaspzap.ci.yml で prod に上書きしています。
  • front_new_item: 外部サイトと _wdt へのリクエストを削除し、作成から削除までを一続きに組み直しました。コンテキストは default のままで、シナリオの中で管理画面にログインします。
  • スキャン中の再生失敗: 前の手順への攻撃で状態が変わり、後の手順の再生が失敗するケースは許容します。既知のものは zap/README.md の「既知の再生の失敗」に記載しました。
  • 定期削除の猶予: delete_data.sh はスキャンで作られたデータを定期的に消します。タグは作成日時を持たないため 2 周前 (10 秒以上前) からあった ID だけを、支払方法とテンプレートは作成の 1 分後に消すようにしました。作成直後に消すと、シナリオが後の手順で編集・削除する前に消えて 404 になっていたためです。
  • スキャン用のパッチ: doc4 の「テストが止まらないようにするための設定」を 4.4 向けに作り直し、パッチの有無で attacks.tsv を比べました。効果があったのはお届け先の登録上限と削除を無効にするものだけだったため、これだけを収録しています。評価の詳細は ZAP: zst シナリオを 4.4 に移行し、4.4 の新機能をスキャン対象に加える #7190 に記載しています。
  • multipart/form-data: ZAP は Content-Type を小文字にしてから boundary を取り出すため、ブラウザが付ける大文字を含む boundary (----WebKitFormBoundary...) では本文を分割できず、能動スキャンの攻撃が 1 件も送られていませんでした (各スキャンルールが IndexOutOfBoundsException で中断し、ZAP の内部ログにだけ出ます)。シナリオの boundary を小文字にし、boundary の行の前の改行を CRLF に揃えました。
  • DOM Based XSS のルール (40026): ブラウザで URL の # 以降とクエリ文字列に値を入れて alert() の実行を見るルールで、POST の本文やシナリオのステップの状態は扱いません。EC-CUBE の JavaScript は location.hash / location.search を読んでおらず、全ターゲットのスキャンで 1 件も検出していません。一方で 1 ステップが ZAP の待ち時間の上限 (600 秒) に達することがあり、上限後もスレッドが止まらず次のルールの所要時間まで延ばしていました。同梱の Sequence ポリシーは読み取り専用のため、このルールだけを OFF にした EC-CUBE ポリシーを prepare_zap.sh で作ります。同梱のポリシーの <locked> (列挙していないルールを無効にする) は残しています。外すと同梱に無いルールまで有効になるためです。
  • 入力エラーの画面の検出: ZAP は再生の成否を状態コードだけで判定するため、入力エラーの画面 (200) を成功とみなし、後続の画面に攻撃が届いていないことに気づけません。Smoke では httpsender スクリプトで、入力エラーの画面が返った POST を Form error: としてログに出し、再生の失敗に数えます。再生と攻撃は ZAP の中で区別できない (どちらも能動スキャンのリクエスト) ため、Smoke に限っています。入力エラーを意図したリクエストには X-ZAP-Expect-Form-Error ヘッダーを付けて対象から外します。
  • パスワード再設定: 直前の POST /forgot への攻撃が再設定キーを作り直すため、宛先で絞った最新のメールからキーを読みます (mailpit の /view/latest.txt?query=to:<宛先>)。再設定の完了でキーが消えるため、攻撃を受ける再設定の POST は確認用のパスワードを一致させずに送ります。
  • 削除のリクエスト: パラメータが _token と _method だけで、パスの ID は ZAP の既定では攻撃されません。URL のパスを攻撃の対象に加えても、ID の \d+ の制約でルーティングが 404 を返すうえ、固定の文字列のパス要素への攻撃でリクエストが増える (entry で 5,291 件から 8,564 件) ため、加えていません。
  • 返品・返金申請: 発送済みの受注だけが対象のため、before_script で会員の作成、購入、発送済みへの変更を行います。admin コンテキストで会員としてログインするとセッション ID が変わり管理画面が未ログイン扱いになるため、管理画面にログインし直しています。

テスト(Test)

  • ローカル (zap/local/、APP_ENV=prod、バンドル版プラグイン導入済み):
    • Smoke で全ターゲットを再生しました。失敗は plugin_coupon_guest_shopping の 1 本だけで、EC-CUBE 4.4 対応: 削除される OrderItem::setTaxRuleId() の呼び出しを修正する coupon-plugin#198 の不具合によるものです。
    • Sequence で全ターゲットをスキャンし、シナリオの再生・レポートの出力・所要時間を確認しました。
    • 今回追加したシナリオは、Smoke と Sequence の両方で確認しました。返品・返金申請とお届け先の編集・削除は、処理が DB に反映されていることも確かめています。
  • fork の workflow_dispatch:
    • 共通スクリプト化した後の Smoke: 3 ターゲットが成功。plugin_coupon_guest_shopping は上記の不具合どおり失敗しました。
    • 追加したシナリオ (FAQ・カレンダー・Cookie 同意・お届け先・返品返金・ブロック) の Smoke: すべて成功しました。
    • 全ターゲットの Smoke: 72 ターゲット中、失敗は plugin_coupon_guest_shopping (上記の不具合) と admin_payment の 2 本でした。admin_payment は支払方法の定期削除との競合で、猶予を延ばした後に再実行して成功しました。
    • ZAP のイメージを公式の stable に切り替えた後の全ターゲットの Smoke: 失敗は plugin_coupon_guest_shopping だけでした。
    • fix: EC-CUBE 4.4 で削除される OrderItem::setTaxRuleId() の呼び出しを除去 coupon-plugin#199 (scrutinizerがtravisでのテストが遅くて落ちてしまうのでパラメーターを調整 #198 の修正) のマージ後、plugin_coupon_.* の Smoke: すべて成功しました。ローカルの Sequence でも購入確定まで再生できています。
    • multipart の修正後、対象の 10 ターゲットの Smoke: lint_zst.sh を含めすべて成功しました。
    • 入力エラーの検出と mailpit への切り替えの後、修正した 3 ターゲット (entry・plugin_mailmagazine_send・admin_order_mail) の Smoke: すべて成功し、Form error: も出ませんでした。
    • ルートを追加・修正した 18 ターゲットの Smoke: すべて成功しました。
  • multipart の修正の効果 (ローカルの Sequence): multipart のリクエスト 14 件で、EC-CUBE が受けたリクエストが 1〜3 件から 425〜4,123 件に増えました (admin_content_file は 2 時間で打ち切った時点で 3 件から 1,840 件)。新たに見つかった 500 は 想定外の入力値で 500 になる箇所が管理画面・フロントに多数ある (入力値の型・存在の未検証) #7192 に追記しました。
  • DOM Based XSS のルールを外した効果 (ローカルの Sequence): plugin_mailmagazine_send は 3 時間 3 分から 18 分、admin_payment は 11 分から 2 分 20 秒になりました。攻撃の件数はほぼ同じで、動いたルールは同梱の Sequence からこのルールを除いた 18 本であることを ZAP のログで確かめました。
  • 攻撃が届くようにした効果 (ローカルの Sequence): admin_product_edit の再生の失敗が 6/11 から 0/11 になり、商品登録への攻撃の 500 が 676/2,079 件から 0 件になりました。パスワード再設定の POST への攻撃 331 件は、すべて 4xx だったものがすべて 2xx になりました。
  • 検知の検証として、検証用のブランチで本体に脆弱性を仕込み、パストラバーサル・SQL インジェクション・反射型 XSS が検出されることを確かめました。仕込んだ変更はマージ対象に含めていません。結果は ZAP: zst シナリオを 4.4 に移行し、4.4 の新機能をスキャン対象に加える #7190 に記載しています。

相談(Discussion)

マイナーバージョン互換性保持のための制限事項チェックリスト

  • 既存機能の仕様変更はありません
  • フックポイントの呼び出しタイミングの変更はありません
  • フックポイントのパラメータの削除・データ型の変更はありません
  • twigファイルに渡しているパラメータの削除・データ型の変更はありません
  • Serviceクラスの公開関数の、引数の削除・データ型の変更はありません
  • 入出力ファイル(CSVなど)のフォーマット変更はありません

レビュワー確認項目

  • 動作確認
  • コードレビュー
  • E2E/Unit テスト確認(テストの追加・変更が必要かどうか)
  • 互換性が保持されているか
  • セキュリティ上の問題がないか
    • 権限を超えた操作が可能にならないか
    • 不要なファイルアップロードがないか
    • 外部へ公開されるファイルや機能の追加ではないか
    • テンプレートでのエスケープ漏れがないか

🤖 Generated with Claude Code

nanasess and others added 14 commits September 29, 2026 17:55
- mypage_* は会員でログインした front_login コンテキストで実行する
  (ユーザーを持たないコンテキストでは sequence-activeScan がセッションを引き継がない)
- plugin_related_product / plugin_sales_report は管理画面だけを操作するため admin コンテキストにする
- ユーザーと forced user はコンテキストから決める (-c で明示した場合も同じ)
- admin コンテキストで before_script を省略した場合は admin_login.zst を使う
- -p でスキャンポリシーを指定できるようにする (省略時は同梱の Sequence ポリシー)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Path Traversal (ルール 6) のうち、URL 末尾の文字列をファイル名として送るチェックは、
比較用の存在しないファイル名 (38 文字) が入力欄の文字数上限でバリデーションエラーになる
だけで High を出す。このチェックは根拠 (evidence) を持たず、ファイルを読み取れた本来の
検知は根拠を持つため、根拠が空のものだけを判定から外す。レポートには残す。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 4.4 で変わったフォーム項目・パスワード要件・CSRF トークンの取得位置に追従する
- 会員を作るシナリオはメールアドレスを乱数で一意にし、固定の会員・受注 ID を
  作成時に取得した ID に置き換える (再生や能動スキャンでの重複登録を避ける)
- 管理画面へログインしていなかった before_script にログインを追加する
- admin_mail_edit / front_new_item は作成から削除までを一続きで組み直す
- 購入フロー画面にヘッダー検索フォームが無いことに合わせて formIndex を直す

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sequence-activeScan は同梱の Sequence ポリシーを使い、CI のマウント先 (/zap/policies) は
ZAP が読むディレクトリ (~/.ZAP/policies) ではなかったため、このファイルは読まれていなかった。
GUI 用の docker-compose.owaspzap.yml のマウントも外し、ZAP 同梱の既定ポリシーを使う。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- EC-CUBE を APP_ENV=prod / APP_DEBUG=0 で起動する。docker-compose.yml が dev を直書きしており、
  ワークフローの環境変数では上書きされていなかった
- バンドル版プラグインを各リポジトリの 4.4 ブランチから導入する (4.2 版は 4.4 で動かない)
- ターゲットと個別の設定を zap/targets.json に移し、入力 targets (正規表現) で絞り込めるようにする
- 入力 policy で Smoke (全ルール OFF、zst の再生だけを確認) を選べるようにする
- 自動化プランは Zest の再生失敗があっても成功扱いになるため、ログから判定する。
  Smoke では失敗、Sequence では能動スキャンによる状態変化で失敗し得るため警告に留める
- before_script のログインをシナリオへ引き継ぐため connection.httpStateEnabled を有効にする
- thread_per_host を scanner.threadPerHost として ZAP に渡す (これまでは使われていなかった)
- before_script の既定値は generate_automation_config.sh に任せ、ワークフロー側の上書き・
  パッチ・customer-id の置換をやめる
- 別ランナーで動くため効果の無かった prune ジョブを削除し、scan に timeout-minutes を設定する

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
securitychecker-plugin は非公開リポジトリのため CI から匿名で clone できず、
プラグインの導入ステップが失敗していた。これを操作するシナリオは無いため導入しない。
あわせて、Autorun まで到達しなかった場合に Check results が誤って
「自動化プランが失敗」を出さないようにする。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 環境の準備 (プラグインの導入・レートリミッタの緩和・ZAP のアドオンと Smoke ポリシー・
  データの定期削除)、プランの生成と実行、結果の判定を zap/bin/ に切り出し、CI からも呼ぶ
- 導入するプラグインを zap/plugins.txt で管理する
- zap/local/ にレーン単位で並列に実行するスクリプトを追加する。ターゲットごとに DB を
  準備直後の状態へ戻してから実行し、zap/targets.json を共有のキューにして複数レーンで回す
- 結果の判定は Smoke では再生の失敗も失敗、Sequence では警告に留める (従来の CI と同じ)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
仕組み (targets.json・ポリシー・プラグイン)、結果の判定、GitHub Actions とローカルでの
実行方法、既知の再生の失敗、シナリオを追加・修正するときの注意を記載する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
owasp/zap2docker-stable は取得できなくなっているため、docker-compose.owaspzap.yml が
ビルドできなかった。後継の公式イメージは webswing を同梱しており、日本語フォントを
追加する手順はそのまま使える。
起動時に ZAP のルート CA (秘密鍵を含む) を zap/ へ書き出す設定のため、.gitignore に追加する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.4 で追加された FAQ 管理 (登録・編集・並び替え・削除) を再生するシナリオ。
お知らせ管理のシナリオと同じ流れで、登録後のリダイレクト先から FAQ の ID を取得する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- admin_calendar: 定休日の登録・インライン編集 (mode=edit_inline, EC-CUBE#6675)・削除。
  税率設定と同じ画面構成のため、admin_tax と同じ流れで一覧の先頭の編集フォームから ID を取得する
- admin_category_faq: カテゴリ別 FAQ の登録と、0 件にして保存する削除。
  FAQ 欄の描画を示す faqs_rendered を送らないと FAQ が無視されるため、必ず送る

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
条件を変えて同じターゲットを実行すると、zap/local/out/<policy>/<target>/ の結果が上書きされる。
比較のために複数の条件で並行して実行できるよう、出力先を環境変数で指定できるようにする。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- front_cookie_consent: Cookie 同意の設定ページ・ポリシー・同意状態の保存。
  before_script (admin_enable_cookie_consent.zst) で店舗設定の Cookie 同意を ON にする
- mypage_shopping_shipping: 購入手続き中に会員の登録済みお届け先を編集・削除する
- mypage_refund_request: マイページから返品・返金を申請する。
  before_script (admin_create_delivered_order.zst) で購入した受注を発送済みにする
- admin_refund_request: 管理画面で返品・返金申請を検索し、対応開始・承認・CSV 出力を行う。
  before_script (admin_create_refund_request.zst) で発送済みの受注に申請を作成する
- front_block: FAQ・定休日カレンダー・新着商品のブロックを追加

README に、シナリオ作成時の注意 (正規表現での ID 抽出、formIndex のずれ、
response 要素による読み込み失敗、admin コンテキストでの再ログイン) を追記した。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ブロックの取得は GET で CSRF トークンを検証しないため、ヘッダー自体を送らない。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

nanasess and others added 4 commits September 30, 2026 17:33
- zap/bin/coverage.sh: EC-CUBE のルートのうち、どのシナリオのリクエストも一致しないものを一覧にする。
  対象外にするルート (インストーラ、ストアのプラグイン管理) は理由と一緒に zap/coverage_exclude.txt に書く
- zap/bin/attack_counts.sh: シナリオのリクエストごとに、EC-CUBE が受けたリクエストの件数と応答の内訳を数える
- autorun.sh: スキャン中のアクセスログを回収して attacks.tsv を出力し、Sequence ではパラメータがあるのに
  正常な応答が数件しか無いリクエストを「攻撃が届いていない可能性」として通知する (判定には影響させない)

画面を通っていても攻撃が届いていない例として、entry のパスワード再設定は再設定用の URL が使用済みで
攻撃がすべて 404 になり、再設定フォームは実質スキャンされていないことが分かった。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- delete_data.sh: dtb_tag を 5 秒ごとに作成直後のものまで削除していたため、admin_product_tag が
  作成したタグを編集する前に消され、Smoke でも再生に失敗することがあった。dtb_tag は作成日時を
  持たないため、2 周前 (10 秒以上前) に既にあった ID までを削除する
- prepare_zap.sh: zap.sh -cmd がプロキシのポートを確保できずに終了することがある
  (Address already in use)。原因は特定できていないため、3 回まで再実行する

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doc4 の「テストが止まらないようにするための設定」を 4.4 向けに作り直し、目的ごとに
zap/patches/*.patch に分けた (会員登録・パスワード再設定・お届け先・退会・会員情報・お気に入り・
購入完了・管理画面の設定・規格の削除)。

スキャンする対象がリリースするコードと変わり、保存の時点で起きるエラーは検出できなくなるため、
既定のスキャンでは当てない。ローカルは zap/local/patch.sh、GitHub Actions は workflow_dispatch の
patches で選んだときだけ当てる。当て済みかを先に判定し、二重に当てて .rej を残さないようにしている。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
generate_automation_config.sh は zap/automation/<target>.yml に書き、autorun.sh は読み終えると
削除していたため、別のレーンで同じターゲットを同時に実行すると、一方が削除したファイルを
もう一方が読んで空のプランになった。生成先を -o で指定できるようにし、autorun.sh は出力先の
ディレクトリへ生成させる。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.43%. Comparing base (3d61c11) to head (f248399).
⚠️ Report is 25 commits behind head on 4.4.

Additional details and impacted files
@@            Coverage Diff             @@
##              4.4    #7196      +/-   ##
==========================================
- Coverage   78.47%   78.43%   -0.05%     
==========================================
  Files         651      651              
  Lines       31391    31384       -7     
==========================================
- Hits        24635    24615      -20     
- Misses       6756     6769      +13     
Flag Coverage Δ
Unit 78.43% <ø> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

nanasess and others added 2 commits October 1, 2026 12:15
作成から 6 秒で削除していたため、Smoke でも作成から削除の手順まで
8 秒ほどかかる admin_payment が、掃除のループの位相次第で 404 に
なっていた (CI の全ターゲット Smoke で発生)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
パッチの有無で attacks.tsv の正常な応答を比べ、改善したのは
mypage-delivery-keep だけだったため、残りの 8 本を削除する。

- 削除を無効にするもの: 削除のリクエストは _token と _method だけで、
  _method への攻撃は Symfony が 400 を返すため、削除済みかに関わらず届かない
- カートを残すもの: /shopping/checkout は CSRF トークンだけで攻撃されない
- 会員を保存しないもの: entry の後半の再生が壊れる
- 再設定のキーを残すもの: 再設定フォームは攻撃前の再生で既に 404

README に評価結果と、multipart のリクエストに攻撃が送られないことを記載する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dotani1111 dotani1111 added this to the 4.4.0 milestone Oct 1, 2026
@dotani1111 dotani1111 added the test label Oct 1, 2026
nanasess and others added 5 commits October 1, 2026 14:42
ghcr.io/ec-cube/zaproxy:0.1.2 も中身は公式の ZAP 2.17.0 だが、作り方が
公開されておらず再現できず、ZAP の更新にも追従しない。GUI 用の
zap/Dockerfile と同じ公式のイメージにそろえる。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EC-CUBE/coupon-plugin#199 で修正され、plugin_coupon_guest_shopping も
Smoke・Sequence ともに再生に成功するようになった。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZAP は Content-Type を小文字にしてから boundary を取り出すため、
ブラウザが付ける大文字を含む boundary (----WebKitFormBoundary...) では
本文を分割できず、各スキャンルールが IndexOutOfBoundsException で中断して
攻撃が 1 件も送られていなかった (商品・支払方法の登録、CSV・テンプレート・画像のアップロード)。

- zst の boundary を小文字にし、boundary の行の前の改行を CRLF に揃える
- 再発を防ぐため zap/bin/lint_zst.sh を追加し、CI の Prepare matrix で実行する

multipart のリクエスト 14 件で、EC-CUBE が受けたリクエストが 1〜3 件から 425〜4123 件に増えた。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ルール 40026 (Cross Site Scripting (DOM Based)) は、ブラウザで URL の # 以降とクエリ文字列に
値を入れて alert() の実行を見るもので、POST の本文やシナリオのステップの状態は扱わない。
EC-CUBE の JavaScript は location.hash / location.search を読んでおらず、全ターゲットのスキャンで
1 件も検出していない。一方で 1 ステップが ZAP の待ち時間の上限 (600 秒) に達することがあり、
上限後もスレッドが止まらず次のルールの所要時間まで延ばしていた。

- prepare_zap.sh で、同梱の Sequence ポリシーから 40026 だけを OFF にした EC-CUBE ポリシーを作る
  (同梱のポリシーは読み取り専用のため別ファイル。<locked> を外すと同梱に無いルールまで有効になるため残す)
- autorun.sh は Sequence の指定で EC-CUBE ポリシーを使う
- 除外の理由を zap/README.md に記載する

plugin_mailmagazine_send は 3 時間 3 分から 18 分、admin_payment は 11 分から 2 分 20 秒になった。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- delete_files.sh が商品画像の一時ファイルを十数秒で消していたため、画像のアップロードを
  能動スキャンしている間に一時ファイルが消え、次の商品登録の再生が入力エラーで失敗していた。
  html/upload/temp_image だけ 60 分残す
- 商品登録・編集の POST に、ブラウザが送る admin_product[faqs_rendered] を足す。
  無いと入力エラーの画面の再表示が 500 になり (EC-CUBE#7192)、攻撃の応答が入力エラーの画面まで届かない

admin_product_edit は再生の失敗が 6/11 から 0/11 になり、商品編集への攻撃 2471 件が
404 だけだったものが登録の処理まで届くようになった。商品登録への攻撃の 500 は
676/2079 件から 0 件になった。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZAP は再生の成否を状態コードで判定するため、入力エラーの画面が 200 で返ると成功とみなす。
その場合、確認画面などの後続の画面には能動スキャンの攻撃が届かない。

- Smoke では、再生で入力エラーの画面が返った POST を Form error: としてログに出し、
  check_results.sh で再生の失敗として数える (scripts/report_form_errors.js)。
  再生と攻撃は ZAP の中で区別できないため、攻撃を送らない Smoke に限る
- 全ターゲットの Smoke で見つかったシナリオを直す
  - entry: 確認画面への POST に、MailMagazine44 が足す必須項目 entry[mailmaga_flg] が無かった。
    確認画面が一度も表示されず、仕込んだ反射型 XSS を検知できなかった原因 (EC-CUBE#7190)。
    直してから仕込みを再現し、ルール 40012 で検知されることを確認した
  - admin_order_edit_search: お届け日は single_text になったため、年・月・日に分けず 1 項目で送る
  - plugin_mailmagazine_send: 記録した環境にしか無いテンプレート ID を送っていた。シナリオの中で
    テンプレートを作り、ID を応答から取り出して、ブラウザと同じく /select/{id} へ送る。
    記録したときの CSRF トークンが値のまま残っていたものも変数にする

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nanasess and others added 7 commits October 2, 2026 14:58
CI は EC-CUBE#6890 で mailpit に移行済みだが、開発環境の docker-compose.yml と
agentic-commerce-e2e.yml には Docker Hub の schickling/mailcatcher が残っていた。

- docker-compose.yml: mailpit を ghcr.io から取得する (バージョンは
  .github/actions/mailpit と揃えて v1.30.3)。UI は 8025、SMTP は 1025。
  未使用の mailcatcher-data ボリュームを削除
- agentic-commerce-e2e.yml: サービスコンテナを .github/actions/mailpit に
  置き換える。4.4 では読まれない MAILER_URL を MAILER_DSN に直す
- eccube-mail Skill の記述を更新し、未使用の MAILCATCHER_URL 定数を削除

refs EC-CUBE#7205

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
entry のパスワード再設定は、再設定画面が 404 になり、再設定の POST への攻撃 331 件が
すべて 404 だった。

- POST /forgot への攻撃が成功するたびに再設定キーが作り直され、最初のメールから読んだ
  キーが古くなっていた。宛先で絞った最新のメールから読む
- 再設定の POST を再生した時点で再設定が成功し、キーが消えていた。確認用のパスワードを
  一致させずに送り、キーを残したまま入力エラーの画面へ攻撃を届ける。入力エラーを意図した
  リクエストには X-ZAP-Expect-Form-Error を付け、Smoke の Form error: の対象から外す
- メールは docker-compose.yml の mailpit で受ける (前のコミットで cherry-pick)。
  /view/latest.txt?query=to:<宛先> で、宛先で絞った最新のメールの本文を取得する。
  ローカルのレーンでは mailpit のポートをホストへ公開しない

再設定の POST への攻撃は 331 件すべてが入力エラーの画面 (2xx) に届くようになり、
再生の失敗は 2/15 から 0/15 になった。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
coverage.sh で「通らない」だったルートのうち、認証の準備が要らないものを、
画面の流れが同じ既存のシナリオに足す。

- 管理画面: カテゴリ・規格名の編集画面、規格・規格分類の CSV 出力、特定商取引法、
  メンバーの並び替え、レイアウトのプレビュー、受注の一括削除、売上グラフ、
  受注の商品検索と返品・返金申請一覧のページ送り
- 画像の読み込み (load) と取り消し (revert): 商品・支払方法。取り消しは別にアップロードした画像で行う
- 返品・返金申請を、ブラウザと同じく添付ファイル付きの multipart で送る。
  確認画面の一時ファイルと、マイページ・管理画面の添付のダウンロードを通す。
  before_script は制御文字があると ZAP が読み込めないため、ASCII だけの GIF にする
- フロント: 商品詳細からのお気に入り削除、購入エラー画面、保存した配送方法・支払方法の復元
  (admin_create_customer.zst の購入で保存する)
- 同梱プラグイン: 商品レビューの CSV 出力と設定、おすすめ商品の商品検索のページ送り。
  商品レビューのページ管理用のダミーのルートは対象外にする

あわせて、攻撃が画面の処理まで届いていなかった 2 シナリオを直す。

- plugin_product_review: フロントの CSRF トークンを位置 (formIndex) で取り出しており、
  ヘッダーの検索フォームを指す環境ではレビューを投稿できず、後続の 8 ステップが失敗していた。
  フィールド名で取り出す
- plugin_recommend: 編集の POST が、記録した環境のおすすめ商品の ID を送っており、
  更新処理が「見つからない」で終わっていた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
coverage.sh で「通らない」に残っていたルートを通し、通らないルートを 0 本にする。

- admin_security: セキュリティ設定。IP 制限・SSL の強制・TRUSTED_HOSTS・管理画面のパスを
  docker-compose.owaspzap.ci.yml で既定値のまま環境変数に固定し、画面から保存しても .env に
  書き込まれないようにして、攻撃で管理画面から締め出されないようにする
- admin_member_setting: 2 段階認証の設定の完了と変更 (/two_factor_auth/edit) を足す。
  コード (TOTP) は httpsender スクリプト (zap/scripts/totp.js) が送信の直前に計算する。
  攻撃は誤ったコードの送信で受ける (設定が完了すると以降は転送され、フォームに届かないため)。
  ログイン後の認証の画面は、ZAP が認証済みの cookie を保持して付け直すため通せず、対象外にする
- admin_agent_commerce: エージェントコマースの設定画面 (ACP Feed の push、UCP Catalog のキャッシュ)
- plugin_api_admin: eccube-api4 の OAuth クライアント・MCP トークン・Webhook の管理
- api_catalog: UCP Catalog (認証なしの JSON API)
- api_acp_checkout / api_ucp_checkout: ACP / UCP のチェックアウト (作成・取得・更新・完了・取り消し)。
  before_script (admin_enable_agent_checkout.zst) でチェックアウトを有効にし、ACP の OAuth クライアントを作る。
  Idempotency-Key は httpsender スクリプト (zap/scripts/idempotency_key.js) がリクエストごとの値にする
  (固定のキーでは、本文を書き換えた攻撃が 422 で止まるため)
- api_oauth: eccube-api4 の認可コードフロー (クライアントの登録・同意・トークン・GraphQL) と、
  MCP クライアントの動的登録 (/register)。/register のレート制限は prepare_ec-cube.sh で緩める

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZAP は anti-CSRF トークン名を部分一致 (大文字小文字を区別しない) で判定し、
該当するパラメータを能動スキャンの対象から外す。一覧の _token が
mcp_token[label] などの MCP トークン発行フォームの全項目と
admin_two_factor_auth[device_token] に当たり、攻撃が届いていなかった。

_token を無効にし、フォームの [_token] に絞る。一覧から消すと ZAP が既定の
トークン名として足し直すため、残したまま無効にする。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 段階認証の設定への攻撃で auth_key が 1 文字などになると、鍵が空になり
totp.js が例外を投げていた。ZAP は例外を投げたスクリプトを止めるため、
続く変更のステップで ZAP_TOTP が置き換わらず、再生が入力エラーになっていた。
計算できないときは置き換えずに送る。

README に、eccube-api4 の /token への攻撃が invalid_grant で止まる理由と、
このままにしている理由を書く。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants