Skip to content
これは pieceofcake2/security-headers のドキュメントです。pieceofcake2/cakephp 用の CakePHP 2.x プラグインです。 このフォークについて

Security Headers プラグインドキュメント ​

Security Headers プラグインは、ディスパッチャーフィルターで、アプリケーションのすべてのレスポンスに セキュリティヘッダーを付けます。対象は、ページ、リダイレクト、エラーページ、ダウンロード、プラグインと テーマのファイルです。ヘッダーは、ページが実行するスクリプト、ほかのサイトがページをフレームで表示 できるか、ほかのサイトに送る情報などを制限するよう、ブラウザーに伝えます。

ウェブサーバーや CDN が送るべきヘッダーと、ページを壊しやすいヘッダーは、デフォルトでオフです。 Content Security Policy は、デフォルトでは報告のみのモードで送ります。

インストール ​

必要なもの: PHP 8.0+、pieceofcake2/cakephp 2.12+。

bash
composer require pieceofcake2/security-headers

プラグインは cakephp-plugin タイプなので、composer/installers でプラグインのディレクトリに インストールされます。アプリケーションの composer.json に installer-paths を設定してください。

レイアウトinstaller-paths
モダン (config/、src/、templates/、plugins/)"plugins/{$name}/": ["type:cakephp-plugin"]
レガシー (app/Config/、app/Plugin/)"app/Plugin/{$name}/": ["type:cakephp-plugin"]

config/bootstrap.php (レガシーのレイアウトでは app/Config/bootstrap.php) でプラグインを読み込み、 ディスパッチャーフィルターに SecurityHeaders.SecurityHeadersFilter を追加します。 CakePlugin::loadAll() を使っている場合、プラグインの読み込みは不要です。

php
CakePlugin::load('SecurityHeaders');

Configure::write('Dispatcher.filters', [
    'AssetDispatcher',
    'CacheDispatcher',
    'SecurityHeaders.SecurityHeadersFilter',
]);

フィルターは、リストのどこに書いても、ほかのフィルターより先に動きます (優先度は 1。AssetDispatcher と CacheDispatcher は 9)。そのため、プラグインのファイルやキャッシュしたビューにもヘッダーが付きます。

webroot/ のファイルは CakePHP を通りません。ウェブサーバーが、ウェブサーバーのヘッダーを付けて送ります。

設定 ​

config/bootstrap.php で Configure::write('SecurityHeaders', [...]) を書いて設定を変えます。設定の値は そのヘッダーの値で、false にすると送りません。書かなかった設定はデフォルトのままです。未知の設定、型の違う値 (ヘッダーに配列、true の代わりに 1 など)、ポリシーの未知のディレクティブは InvalidArgumentException を 投げます。誤字で、ヘッダーが気づかないままデフォルトになったり、ブラウザーが無視するディレクティブが残ったり することを防ぐためです。

php
Configure::write('SecurityHeaders', [
    'xFrameOptions' => 'DENY',
    'contentSecurityPolicyAllow' => [
        'cdn' => ['script-src' => ['https://cdn.example.com']],
    ],
]);

デフォルトでオン:

設定ヘッダーデフォルト役割
contentSecurityPolicyContent-Security-Policydefault-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'ページのスクリプト、スタイル、画像、フレーム、フォームの送信先を制限します。XSS に対する主な防御です。ディレクティブとソースの配列か、ポリシーの文字列で書きます。
contentSecurityPolicyAllow[]ポリシーで許可するほかのサイト。サービスのクラス (例: GoogleTagManager::class) か、名前を付けたディレクティブごとのソースを書きます。ほかのサイトを許可する を参照してください。
contentSecurityPolicyNoncenullリクエストごとに新しい nonce を script-src に追加します。nonce 属性にその値を持つインラインのスクリプトが許可されます。デフォルト (null) では、contentSecurityPolicyAllow のプリセットが必要とするときにオンです。true で常にオン、false でオフです。インラインのスクリプトと nonce を参照してください。
contentSecurityPolicyReportOnlytrueポリシーを Content-Security-Policy-Report-Only で送ります。ブラウザーは違反をブロックせず、コンソール (と reportUri) に報告します。Content Security Policy を有効にする を参照してください。
reportUrifalse違反の報告を受け取る URL。ポリシーに report-uri として追加します。
xFrameOptionsX-Frame-OptionsSAMEORIGINほかのサイトがページをフレームで表示できるか (クリックジャッキング)。モダンなブラウザーではポリシーの frame-ancestors が優先されます。古いブラウザー向けです。
xContentTypeOptionsX-Content-Type-Optionsnosniffブラウザーがレスポンスの種類を推測しないようにします。たとえば、アップロードされたテキストファイルをスクリプトとして実行しません。
referrerPolicyReferrer-Policystrict-origin-when-cross-originほかのサイトの Referer には、ページのオリジンだけを送ります。ID やトークンを含みうるパスとクエリーは送りません。
permissionsPolicyPermissions-Policycamera=(), microphone=(), geolocation=()ページとそのフレームが使えないブラウザーの機能。
crossOriginOpenerPolicyCross-Origin-Opener-Policysame-originページが開いた、またはページを開いたほかのサイトのウィンドウは、window.opener でページにアクセスできません。
crossOriginResourcePolicyCross-Origin-Resource-Policysame-originほかのサイトは、<img> や <script> でレスポンス (画像や JSON など) を読み込めません。
removePoweredByX-Powered-Bytrueexpose_php がオンのとき PHP が付ける PHP のバージョンを取り除きます。

デフォルトでオフ:

設定ヘッダー例オフの理由
strictTransportSecurityStrict-Transport-Securitymax-age=31536000; includeSubDomainsウェブサーバーや CDN で送るものです。下記を参照してください。
cacheControlCache-Controlno-storeウェブサーバーや CDN、各アクションで決めるものです。キャッシュすべきファイルも含め、すべてのレスポンスの Cache-Control を置き換えてしまいます。アクションは $this->response->cache() や disableCache() で設定します。セッションのあるページは セッションのあるページ を参照してください。
crossOriginEmbedderPolicyCross-Origin-Embedder-Policyrequire-corpCross-Origin-Resource-Policy や CORS のヘッダーを送らないほかのサイトの画像、スクリプト、フレームが表示されなくなります。必要なのは SharedArrayBuffer や精度の高いタイマーを使う場合だけです。
xXssProtectionX-XSS-Protection0廃止されたヘッダーです。モダンなブラウザーは無視し、古いブラウザーのフィルターは悪用されることがありました。スキャナーに求められた場合は 0 を送ってください。

ウェブサーバーが送る Server はプラグインでは取り除きません。ウェブサーバーでオフにしてください (nginx では server_tokens off;、Apache では ServerTokens Prod と ServerSignature Off)。

ほかのサイトを許可する ​

ほかのサイトのスクリプト、画像、フレームを許可するには、contentSecurityPolicyAllow に並べます。 サービスのクラスか、自分で付けた名前とディレクティブごとのソースを書きます。ポリシーのほかの ディレクティブとソースはそのまま残ります。

php
use PieceOfCake2\SecurityHeaders\ContentSecurityPolicy\GoogleAnalytics;
use PieceOfCake2\SecurityHeaders\ContentSecurityPolicy\GoogleTagManager;
use PieceOfCake2\SecurityHeaders\ContentSecurityPolicy\YouTube;

Configure::write('SecurityHeaders', [
    'contentSecurityPolicyAllow' => [
        GoogleTagManager::class,
        GoogleAnalytics::class,
        'cdn' => [
            'script-src' => ['https://cdn.example.com'],
        ],
        YouTube::class,
    ],
]);

ポリシーにないディレクティブは、ブラウザーが代わりに使うディレクティブ (script-src なら default-src) のソースから始めるので、'self' は許可されたままです。ソースを追加したディレクティブからは 'none' を取り除きます。デフォルトのポリシーに上の cdn と YouTube::class を追加すると、次のようになります。

text
default-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none';
script-src 'self' https://cdn.example.com https://www.youtube.com; img-src 'self' https://i.ytimg.com;
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com

プラグインには、Google、Adobe、Meta (Facebook、Instagram)、X、TikTok、LINE、YouTube、Yahoo! JAPAN、Microsoft の タグ、コンバージョンタグ、埋め込みのためのクラス (プリセット) があります。それぞれのソースと注意点は プリセット を参照してください。スクリプトの必要に応じて、style-src に 'unsafe-inline' や script-src に 'unsafe-eval' を追加するものがあります。

複数のアプリケーションで同じサービスのソースを使うには、Sources を実装したクラスを src/Lib/ などに 書き、同じように並べます。

php
use PieceOfCake2\SecurityHeaders\ContentSecurityPolicy\Sources;

class StripeSources implements Sources
{
    /**
     * Stripe.js のソース
     *
     * @return array<string, array<string>>
     */
    public function sources(): array
    {
        return [
            'script-src' => ['https://js.stripe.com'],
            'frame-src' => ['https://js.stripe.com'],
        ];
    }
}

プラグインはクラスを引数なしで作ります。サービスのコードにインラインのスクリプトがある場合は、Sources の 代わりに NeedsNonce を実装してください。そのクラスは nonce をオンにします (インラインのスクリプトと nonce)。Sources を実装していないクラス、引数が必要な クラス、名前のない値は InvalidArgumentException を投げます。

代わりにポリシー全体を書くには、contentSecurityPolicy を配列か文字列で設定します。

php
Configure::write('SecurityHeaders', [
    'contentSecurityPolicy' => [
        'default-src' => ["'self'"],
        'img-src' => ["'self'", 'data:'],
        'frame-ancestors' => ["'none'"],
        'form-action' => ["'self'"],
        'base-uri' => ["'self'"],
        'object-src' => ["'none'"],
    ],
]);

一部のアクションだけで追加するには、コンポーネントの addSources() を使います。

インラインのスクリプトと nonce ​

ポリシーは、インラインのスクリプト (<script> ブロック、onclick などの属性、javascript: のリンク) を ブロックします。nonce 属性にレスポンスの nonce (レスポンスごとに変わるランダムな値) を持つ <script> ブロック は許可します。

インラインのスクリプトを使うプリセット (GoogleTagManager など。プリセット のページの 「nonce をオンにします」) を contentSecurityPolicyAllow に入れると、nonce はオンになります。 'contentSecurityPolicyNonce' => true でも常にオンになり、false でオフになります。

AppController でヘルパーを読み込みます。

php
public $helpers = ['SecurityHeaders.SecurityHeaders'];

ビューやレイアウトのインラインのスクリプトに nonce を付けます。

php
<!-- ビューやレイアウトの中 -->
<script nonce="<?= h($this->SecurityHeaders->nonce()) ?>">
    window.dataLayer = window.dataLayer || [];
</script>
<?= $this->Html->scriptBlock('console.log(1);', ['nonce' => $this->SecurityHeaders->nonce()]) ?>
  • 属性と javascript: のリンクには nonce を付けられません。コードをスクリプトのファイルに移してください (addEventListener() など)。アプリケーションのスケルトン (pieceofcake2/app。CakePHP 5 のものも同じ) には 2 つあります。フラッシュメッセージは onclick 属性でクリックすると消え、エラーのレイアウトには javascript:history.back() のリンクがあります。ポリシーを有効にすると、これらは動かなくなります。
  • nonce を使わない場合、サービスのガイドは 'unsafe-inline' でインラインのスクリプトを許可します。 これは XSS のインラインのスクリプトも許可します。nonce を付けられない場合だけ、 contentSecurityPolicyAllow に追加してください。
  • キャッシュ。 nonce はレスポンスごとに変わります。CacheHelper や CDN が HTML ごとキャッシュした ページには別のレスポンスの nonce が入っているため、インラインのスクリプトがブロックされます。

ウェブサーバーや CDN で送るヘッダー ​

次のヘッダーはウェブサーバー、ロードバランサー、CDN で送るもので、デフォルトでオフです。

  • Strict-Transport-Security (HSTS) は、そのサイトに HTTPS だけで接続するようブラウザーに伝えます。 webroot/ のファイルを含むすべてのレスポンスで、HTTPS のときだけ送る必要があります。TLS を終端する ロードバランサーや CDN の後ろでは、アプリケーションからは HTTP に見えるため判断できません。 プラグインでオンにした場合は、$request->is('ssl') が true のときだけ送ります。
  • 静的ファイルやキャッシュしたページの Cache-Control は、CDN とウェブサーバーで決めます。

これらを送れるウェブサーバーや CDN を使わずにアプリケーションを公開する場合だけ、プラグインでオンに してください。

Content Security Policy を有効にする ​

デフォルトのポリシーは、サイト自身のスクリプト、スタイル、画像だけを許可します。有効にすると、 インラインの <script> ブロック、style 属性、onclick などのハンドラー、CDN のファイルは ブロックされます。多くのアプリケーションでは先に修正が必要なため、デフォルトでは Content-Security-Policy-Report-Only で送ります。

  1. ブラウザーのコンソールを開いてアプリケーションのページを開き、報告されたものを修正するか許可します。 インラインのスクリプトはファイルに移すか、必要なサイトを許可します。
  2. 必要なら、reportUri で利用者のブラウザーからの報告を集めます。
  3. 報告が出なくなったら、ポリシーを有効にします。
php
Configure::write('SecurityHeaders', [
    'contentSecurityPolicyReportOnly' => false,
]);

アクションでヘッダーを変える ​

一部のアクションでヘッダーを変えるには SecurityHeadersComponent を使います。たとえば、パートナーの サイトがフレームで表示するページでは次のようにします。

php
class WidgetsController extends AppController
{
    public $components = ['SecurityHeaders.SecurityHeaders'];

    /**
     * パートナーのサイトのフレームで表示するページ
     *
     * @return void
     */
    public function embed(): void
    {
        $this->SecurityHeaders->set('Content-Security-Policy', "frame-ancestors 'self' https://partner.example.com");
        $this->SecurityHeaders->disable('X-Frame-Options');
    }

    /**
     * YouTube の動画を表示する
     *
     * @return void
     */
    public function video(): void
    {
        $this->SecurityHeaders->addSources('frame-src', 'https://www.youtube-nocookie.com');
    }
}
  • addSources($directive, $sources) は、contentSecurityPolicyAllow と同じように、設定の Content Security Policy のディレクティブにソースを追加します。
  • set($name, $value) は、設定のヘッダーを置き換えます。
  • disable($name) は、このレスポンスではそのヘッダーを送りません。

ヘッダー名は、フィルターが送る名前で書いてください。報告のみのモードでは Content-Security-Policy-Report-Only です。

注意点 ​

  • ほかのサイトのポップアップ。 Cross-Origin-Opener-Policy: same-origin では、ほかのサイトの ログインや決済のために開いたポップアップから、window.opener でページにアクセスできません。 そうした流れが動かなくなった場合は same-origin-allow-popups を使ってください。

  • ほかのサイト向けのリソース。 Cross-Origin-Resource-Policy: same-origin では、ほかのサイトが <img> や <script> でアプリケーションの画像やファイルを読み込めません。たとえば、メールや自分の 別ドメインに埋め込む画像です。アプリケーション全体で、またはコンポーネントでそのアクションだけ、 same-site か cross-origin にしてください。API の fetch() など、CORS のリクエストには影響しません。

  • エラーページ。 エラーページは別のレスポンスを使うため、フィルターは header() でもヘッダーを 送ります。あとからアプリケーションや CakePHP が同じ名前のヘッダーを送ると、そちらに置き換わります。

  • セッションのあるページ。 CakePHP 2 は、Internet Explorer 8 以前のために、PHP のキャッシュリミッターを デフォルトで must-revalidate にします。PHP はこの値を知らないのでキャッシュのヘッダーを送らず、ログイン中の ページに Cache-Control が付きません。ログアウトしたあとにブラウザーがページを再び表示したり、共有のキャッシュが ページを保存したりできます。CakePHP 5 と同じく PHP のキャッシュリミッターを使うよう、config/core.php で 設定してください。

    php
    Configure::write('Session.cacheLimiter', 'nocache');

    これで PHP は、セッションを開始するページに Cache-Control: no-store, no-cache, must-revalidate を送ります。

プラグインを使わない場合 ​

自分でヘッダーを送るには、AppController::beforeFilter() でレスポンスに追加します。この方法では、 プラグインとテーマのファイル、CacheDispatcher がキャッシュしたビューにはヘッダーが付きません。

php
/**
 * レスポンスにセキュリティヘッダーを追加する
 *
 * @return void
 */
public function beforeFilter(): void
{
    parent::beforeFilter();
    $this->response->header([
        'Content-Security-Policy' => "default-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'",
        'X-Frame-Options' => 'SAMEORIGIN',
        'X-Content-Type-Options' => 'nosniff',
        'Referrer-Policy' => 'strict-origin-when-cross-origin',
    ]);
}

または、ウェブサーバーからすべてのレスポンスに付けます。nginx の例:

nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

著作権とライセンスについては LICENSE を参照してください。Cake Software Foundation とは関係ありません。