セキュリティ規約 — 認証・認可・CSRF(EC-CUBE 4.4)
対象: src/Eccube/Security/**/*.php, app/config/eccube/packages/security.yaml,
および全コントローラの認可・CSRF 判断(コントローラ側の作法は Skill eccube-controller も参照)
前提: Symfony 7.4 / PHP 8.2+
目的: EC-CUBE の「ファイアウォール+ロール+Voter」というアクセス制御モデルを正しく理解し、 認可漏れ・CSRF 漏れ・IDOR(他人のリソース参照)を作り込まない/見逃さないこと。
アクセス制御モデル(まず全体像を掴む)
EC-CUBE は 個別アクションの #[IsGranted] ではなく、ファイアウォール+ロール+Voter で制御する。
設定は app/config/eccube/packages/security.yaml。
- firewalls は 3 つ:
admin:pattern: '^/%eccube_admin_route%/'—Member(管理者) を認証。enable_csrf: true、login throttling 有り。customer:pattern: '^/'(サイト全体)—Customer(会員) を認証。remember_me 有り。dev:security: false— 静的リソース等を認証対象外にする。
- access_decision:
strategy: unanimous/allow_if_all_abstain: false。 → 1 つでも Voter が DENY すれば拒否。全 Voter が棄権したら拒否(明示的に許可が必要)。 - ロール:
ROLE_ADMIN—Member::getRoles()が固定で返す(管理者)。ROLE_USER— フロント会員の暗黙ロール。- 管理者の権限細分は
Authorityマスタ(ADMIN=0システム管理者 /OWNER=1店舗オーナー)。
最重要の含意: 新規の管理アクションは、認可の主装置がファイアウォールなので
必ず %eccube_admin_route% プレフィックス配下のパスに置く。配下に置けば認証が要求される。
配下から外すと無認証で到達できる(よくある重大な事故)。
基本ルール
- 管理アクションは
#[Route(path: '/%eccube_admin_route%/...')]に置く(admin firewall の保護下に入れる)。 - 管理画面内のさらに細かい権限制御は
AuthorityVoter(src/Eccube/Security/Voter/AuthorityVoter.php)が担う。AuthorityRoleのdeny_url(正規表現)に基づき、URL 単位でACCESS_DENIEDを返す。 → 「特定の権限にこの画面を見せない」は コントローラ改変ではなくdtb_authority_roleの設定で実現される。 - GET 以外の状態変更(更新・削除・Ajax)は必ず CSRF トークンを検証する。
フォーム経由(
handleRequest+isValid)は保護込み。フォームを介さない処理は$this->isTokenValid()を明示的に呼ぶ。 - 認証状態の使い分け:
- パスワード変更・退会・購入確定など重要操作 →
IS_AUTHENTICATED_FULLY(remember-me を除外)。 - 単なるログイン状態の確認 →
IS_AUTHENTICATED_REMEMBEREDでよい。
- パスワード変更・退会・購入確定など重要操作 →
- フロントで
{id}等の他人のリソースを受け取るアクションは所有権を検証する(後述の IDOR)。 - パスワードは
PasswordHasher(algorithm: 'auto')任せ。自前ハッシュ・平文比較を書かない。 - Twig 出力のエスケープ(XSS)は Skill
eccube-twig-templateを参照(|rawの濫用に注意)。
実装パターン
CSRF トークン検証(フォームを介さない削除・Ajax)
基底クラス AbstractController::isTokenValid() を呼ぶ。トークン名は Constant::TOKEN_NAME(src/Eccube/Common/Constant.php、値は '_token')、
リクエストパラメータ _token またはヘッダ ECCUBE-CSRF-TOKEN から取得し、失敗時は例外を投げる。
// src/Eccube/Controller/.../CustomerController.php の delete が定石
$this->isTokenValid(); // CSRF 検証。失敗で AccessDeniedHttpException
$this->customerService->delete($Customer);
認可チェック(コントローラ内で明示する場合)
// ROLE で分岐(例: ログイン済み管理者をホームへ)
if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) { ... }
// 重要操作は FULLY を要求(remember-me を弾く)
if ($this->isGranted('IS_AUTHENTICATED_FULLY')) { ... }
URL 単位の権限制御(Voter 経由・コア標準)
AuthorityVoter は Member の Authority に紐づく AuthorityRole.deny_url を取得し、
リクエストパスが一致したら ACCESS_DENIED を返す(src/Eccube/Security/Voter/AuthorityVoter.php)。
新しい「見せない画面」を増やすときは Voter を書くのではなく deny_url 設定で対応できないか先に検討する。
独自 Voter を追加する場合
Symfony\Component\Security\Core\Authorization\Voter\Voter を継承し supports()/voteOnAttribute() を実装する。
services.yaml の autoconfigure: true により security.voter タグは自動付与される(手動登録は不要。コアの既存 Voter に倣う)。
access_decision が unanimous なので、棄権(ABSTAIN)と拒否(DENY)の使い分けを誤ると全体が拒否になる点に注意。
よくある間違い(認可・CSRF・IDOR — ツールでは検出できない観点)
- ❌ 管理アクションを
%eccube_admin_route%配下以外に置く → ✅ 配下に置き admin firewall の保護下にする - ❌ フォームを介さない POST/DELETE/Ajax で CSRF 未検証 → ✅
$this->isTokenValid()を呼ぶ(GET 以外) - ❌ Ajax 専用アクションで XHR 以外も受け付ける → ✅ CSRF 検証に加え
$request->isXmlHttpRequest()を併用し XHR に限定する - ❌ フロントで
{id}から取得したエンティティを所有権チェックせず編集/削除(IDOR) → ✅$this->getUser()と突き合わせ、他人のリソースならAccessDeniedHttpException - ❌ パスワード変更・退会など重要操作を
IS_AUTHENTICATED_REMEMBEREDで許可 → ✅IS_AUTHENTICATED_FULLYを要求(盗難 Cookie での実行を防ぐ) - ❌ 独自 Voter で「対象外」を
ACCESS_DENIEDで返す → ✅ 対象外はACCESS_ABSTAIN(unanimous 戦略で誤拒否を防ぐ) - ❌ 自前でパスワードをハッシュ/平文比較 → ✅
PasswordHasher経由に統一 - ❌ ユーザー入力を Twig で
|raw出力 → ✅ エスケープを効かせる(Skilleccube-twig-template) - ❌ ファイル操作を伴う管理ルートを新設して
eccube_restrict_file_uploadを考慮しない → ✅ 遮断対象(eccube_restrict_file_upload_urls)に含めるべきか検討する - ❌ ユーザー指定のパスをそのまま読み書き(ディレクトリトラバーサル)→ ✅
..を拒否しrealpath()で解決、許可ベース配下かを検証する(FileController::checkDir()が手本)
実行・確認方法
QA ツール(PHPUnit / PHPStan / PHP-CS-Fixer)の実行方法は AGENTS.md「開発コマンド」を参照。
- Voter の単体テスト:
tests/Eccube/Tests/Security/Voter/AuthorityVoterTest.php(deny_url パターンごとの GRANTED/DENIED)。 - 管理ログイン/リダイレクトのテスト:
tests/Eccube/Tests/Web/Admin/LoginControllerTest.php(未ログインで admin 配下にアクセスすると 302 になることを確認)。 - 認可漏れの目視点検: 新規 admin ルートが
%eccube_admin_route%配下にあるか、 状態変更アクションでisTokenValid()またはフォーム検証を通っているか、 フロントの{id}取得に所有権チェックがあるかを確認する。 - 静的解析・整形は
vendor/bin/phpstan analyse src/vendor/bin/php-cs-fixer fix(Docker 経由)。
実装・改修後は、Skill eccube-review-responsibility で責務分離とあわせて認可・CSRF・IDOR を点検すること。