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

アラート ​

CakePHP 2 アプリケーションで、ZAP がベースラインスキャンとフルスキャンでよく出すアラートと、その意味、修正方法です。報告書の各ページ にも ZAP の説明と対策があり、zaproxy.org のアラートのページへのリンクが あります。

ほとんどのアラートは、アプリケーション全体に一度で修正できます。セキュリティヘッダーは AppController と Web サーバーで、 Cookie は config/core.php で、エラーページはデバッグレベルで修正します。フルスキャンのアラート (XSS、SQL インジェクションなど) は、そのページのコードで修正します。同じ箇所は pieceofcake2/security-sast のルールでも見つけられ、そちらに さらに多くの修正方法があります。

修正できたことを確かめる ​

  1. レスポンスを自分で見ます。たとえば、ページのヘッダーと Cookie です。

    bash
    curl -sI https://staging.example.com/ | grep -iE 'content-security|x-frame|x-content-type|set-cookie|x-powered'

    ブラウザの開発者ツール (Network でリクエストのヘッダー、Application で Cookie) でも同じものが見られます。

  2. もう一度スキャンします。報告書からそのアラートが消えます。

  3. 本番も確かめます。ステージングや本番に --url でベースラインスキャンをします。本番の Web サーバー、プロキシ、PHP は、 スキャンのものとは違います。

アラート IDアラートリスク修正方法
40012、40014、40026クロスサイトスクリプティング (反射型、持続型、DOM ベース)高XSS (フルスキャン)
40018、40019、40020、40021、40022、40024SQL インジェクション高SQL インジェクション (フルスキャン)
6、7パストラバーサル、リモートファイルインクルージョン高リクエストのパスとファイル (フルスキャン)
90020リモート OS コマンドインジェクション高コマンド (フルスキャン)
20019外部リダイレクト高リダイレクト (フルスキャン)
10038Content Security Policy (CSP) ヘッダーが設定されていない中セキュリティヘッダー
10020クリックジャッキング対策ヘッダーの欠如中セキュリティヘッダー
10202Anti-CSRF トークンが使用されていない中CSRF トークンのないフォーム
90003サブリソース整合性属性の欠落中ほかのサイトのスクリプト
10021X-Content-Type-Options ヘッダーがありません低セキュリティヘッダー
10063Permissions-Policy ヘッダー未設定低セキュリティヘッダー
90004Cross-Origin-Embedder/Opener/Resource-Policy ヘッダーの欠落または無効低セキュリティヘッダー
10035Strict-Transport-Security ヘッダーが設定されていない低セキュリティヘッダー
10010Cookie に HttpOnly フラグがない低Cookie
10011Secure フラグのない Cookie低Cookie
10054SameSite 属性のない Cookie低Cookie
10023情報漏洩 - デバッグエラーメッセージ低エラーページ
90022アプリケーションエラーの露見低エラーページ
10037"X-Powered-By" による情報の漏洩低ヘッダーのバージョン
10036"Server" によるバージョン情報の漏洩低ヘッダーのバージョン
10017クロスドメイン JavaScript ソースファイルのインクルージョン低ほかのサイトのスクリプト
10049保存可能でキャッシュ可能なコンテンツ、保存不可能なコンテンツ情報キャッシュ
10109、10111、10027、10096モダン Web アプリケーション、認証リクエストの特定、疑わしいコメント、タイムスタンプの開示情報情報

アラートの名前は、ZAP の日本語訳とは少し違うことがあります。ID で探してください。

XSS ​

リクエストの値 (パラメーターやフォームの欄) が、エスケープされずにページに出力されています。値に入ったスクリプトが、 ページを開いたユーザーのブラウザで、そのセッションのまま動きます。反射型はそのリクエストのレスポンスに、持続型は 保存されてから別のページに、DOM ベースはページの JavaScript によって出力されるものです。

ビューのすべての値を h() でエスケープし、フォームやリンクはエスケープするヘルパーで作ってください。

php
<!-- 見つかる書き方 -->
<p>Sent: <?php echo $message; ?></p>

<!-- 修正方法 -->
<p>Sent: <?= h($message) ?></p>

コードの中のこうした箇所は、pieceofcake2/security-sast のルール cakephp2-view-unescaped-output で見つけられます。DOM ベースの XSS は、JavaScript で innerHTML や document.write() を使って値をページに入れないでください。textContent を使います。'unsafe-inline' のない Content Security Policy (セキュリティヘッダー) も、こうしたスクリプトのほとんどを止めます。

SQL インジェクション ​

リクエストの値が、そのまま SQL に入っています。攻撃者がデータベースを読んだり変えたりできます。値をエスケープする find() の条件の配列か、query() の値の配列を使ってください。

php
// 見つかる書き方
$this->Post->query("SELECT * FROM posts WHERE title LIKE '%" . $q . "%'");

// 修正方法
$this->Post->find('all', ['conditions' => ['Post.title LIKE' => '%' . $q . '%']]);
$this->Post->query('SELECT * FROM posts WHERE title LIKE ?', ['%' . $q . '%']);

pieceofcake2/security-sast のルール cakephp2-query-concatenation と cakephp2-conditions-string で、 こうした箇所を見つけられます。

リクエストのパスとファイル ​

リクエストの値で、読み込むファイルや表示するビューを指定しています。値に ../ を入れると、ほかのファイルに届きます。 値をパスとして使わず、許可した名前の一覧に対応させてください。アップロードされたファイルの名前は basename() で 確かめます。ルール cakephp2-render-request もご覧ください。

コマンド ​

リクエストの値が、システムのコマンド (exec()、shell_exec()、system()、バッククォート) に入っています。攻撃者が サーバーでコマンドを実行できます。コマンドを使わないようにするか、値を 1 つずつ escapeshellarg() で渡してください。

リダイレクト ​

リクエストの値をリダイレクト先の URL にしています。サイトのリンクから、ログインページの偽物など、別のサイトに ユーザーを送れてしまいます。ルートの配列などでアプリケーションのパスにだけリダイレクトするか、URL を一覧と照らして ください。ルール cakephp2-redirect-request もご覧ください。

セキュリティヘッダー ​

ページにできることを、ブラウザに制限させるヘッダーです。実行してよいスクリプト (CSP)、ほかのサイトがフレームに 入れてよいか (クリックジャッキング)、ファイルの種類を推測してよいか、使ってよい機能やほかのオリジンのリソースを 指定します。

1 か所だけで付けてください。

  • Web サーバーで (推奨): webroot/ のファイルや Web サーバー自体のエラーも含め、すべてのレスポンスに付きます。 nginx と Apache の完全な設定は Web サーバーの設定 をご覧ください。
  • AppController で: Web サーバーを変えられないときです。CakePHP のすべてのページに付き、エラーページ (404、500) にも付きます (CakeErrorController は AppController を継承しています)。webroot/ のファイルには付きません。
php
// src/Controller/AppController.php (app/Controller/AppController.php)
public function beforeFilter()
{
    parent::beforeFilter();
    $this->response->header([
        'Content-Security-Policy' => "default-src 'self'; frame-ancestors 'self'; form-action 'self'",
        'X-Frame-Options' => 'SAMEORIGIN',
        'X-Content-Type-Options' => 'nosniff',
        'Referrer-Policy' => 'strict-origin-when-cross-origin',
        'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()',
        'Cross-Origin-Opener-Policy' => 'same-origin',
        'Cross-Origin-Resource-Policy' => 'same-origin',
    ]);
    if ($this->request->is('ssl')) {
        $this->response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    }
}

両方では付けないでください。X-Frame-Options が 2 つになるなど、2 回送ったヘッダーは、ブラウザによっては無効に なります。

  • CSP: default-src 'self' は、アプリケーション自身のスクリプト、スタイル、画像だけを許します。インラインの スクリプト (<script>...</script>、$this->Html->scriptBlock()、onclick="...") やほかのサイトのファイルを使う ページでは、足りません。スクリプトを webroot/js/ のファイルに移すか、script-src 'self' https://cdn.example.com のようにサイトをポリシーに足してください。ポリシーは段階的に導入します。
    1. Content-Security-Policy-Report-Only で送ります。何もブロックせず、ブロックするはずのものをブラウザの コンソールに出します。
    2. ページを一通り開き (End-to-End テストが開きます)、インラインのスクリプトとスタイルをファイルに移すか、ページに 必要なサイトを足します。
    3. コンソールに何も出なくなったら、Content-Security-Policy で送ります。
  • Cross-Origin-Embedder-Policy (require-corp) は、許可していないほかのサイトの画像やスクリプトをブロックします。 そうしたリソースを使わないページでだけ付けてください。このアラートのリスクは低です。
  • Strict-Transport-Security は HTTPS のときだけ付けます。ブラウザは max-age 秒のあいだ覚えているので、先に すべてのページが HTTPS で動くことを確かめてください。

CSRF トークンのないフォーム ​

データを変えるフォーム (method="post") にトークンがないと、ほかのサイトがログイン中のユーザーになりすまして送信 できます (CSRF)。AppController で SecurityComponent を読み込み、フォームを FormHelper で作ってください。 $this->Form->create() と $this->Form->end() がトークン (data[_Token][key]) を入れます。ZAP はこの名前を知って います。

php
public $components = ['Security', 'Session'];

ZAP が指摘するのは、HTML で書いたフォーム (<form method="post">) や、FormHelper を使わずに作ったフォームです。 FormHelper で作ってください。Webhook のように、ほかのサイトからのリクエストを受けるアクションは、そのアクション だけを $this->Security->unlockedActions に入れます。pieceofcake2/security-sast のルール cakephp2-security-csrf-disabled もご覧ください。

  • HttpOnly: ないと、JavaScript から Cookie を読めて、XSS で盗まれます。セッションの Cookie にはデフォルトで 付きます (session.cookie_httponly)。CookieComponent では $this->Cookie->httpOnly = true; にします。

  • Secure: ないと、HTTP でも Cookie が送られます。セッションの Cookie には HTTPS のときデフォルトで付きます (session.cookie_secure)。CookieComponent では、HTTPS のとき $this->Cookie->secure = true; にします。

  • SameSite: ほかのサイトからのリクエストで送る Cookie を制限します。セッションでは config/core.php で設定します。

    php
    Configure::write('Session', [
        'defaults' => 'php',
        'ini' => ['session.cookie_samesite' => 'Lax'],
    ]);

    CookieComponent にはこのオプションがありません。Web サーバーで付けます。Apache の Header always edit Set-Cookie か、ほかのサーバーの前に置いた nginx の proxy_cookie_flags です (Web サーバーの設定)。

session.cookie_httponly や session.cookie_secure を false にしないでください。pieceofcake2/security-sast が設定で 検査します (cakephp2-config-session-cookie)。Cookie は curl -sI か開発者ツールで確かめます。セッションの各 Set-Cookie に、HttpOnly、Secure (HTTPS のとき)、SameSite=Lax が付いています。

エラーページ ​

スタックトレース、SQL、ファイルのパスを出すエラーページは、アプリケーションのしくみを攻撃者に教えます。本番では debug を 0 にします (APP_DEBUG を設定しないか 0)。CakePHP は、詳細を出さない templates/Errors/ (app/View/Errors/) のページを表示します。

組み込みウェブサーバーは APP_ENV=test で動き、debug はふつう 0 です。スキャンで多くのページに「アプリケーション エラーの露見」が出るときは、それらのページがエラー 500 になっています。ログ (logs/error.log) を確かめてください。 テストのデータベースにテーブルがない、などです。

ヘッダーのバージョン ​

  • X-Powered-By: PHP/8.x は PHP が付けます。本番の php.ini で expose_php = Off にします。
  • Server: Apache/2.4.x (Ubuntu) は Web サーバーが付けます。ServerTokens Prod (Apache) か server_tokens off; (nginx) にします。nginx の後ろの PHP-FPM では、fastcgi_hide_header X-Powered-By; で PHP のバージョンも隠せます (Web サーバーの設定)。

スキャンの組み込みウェブサーバーは CI のマシンの php.ini で動くので、本番では出ないのにスキャンでは X-Powered-By が出ることがあります。本番のヘッダーを確かめ、本番を設定したならスキャンでは --ignore=10037 で除外してください。

ほかのサイトのスクリプト ​

ほかのサイト (CDN) のスクリプトは、ページと同じ権限で動きます。そのサイトが攻撃されると、アプリケーションも 攻撃されます。ファイルを webroot/js/ から返すか、integrity でハッシュを指定してください (サブリソース整合性)。 ブラウザがハッシュを確かめます。

php
echo $this->Html->script('https://cdn.example.com/lib.min.js', [
    'integrity' => 'sha384-...',
    'crossorigin' => 'anonymous',
]);

ハッシュは CDN のファイルのページにあるか、openssl dgst -sha384 -binary lib.min.js | openssl base64 -A で作れます。

キャッシュ ​

「保存可能でキャッシュ可能なコンテンツ」は情報です。プロキシがページを保存するかもしれません。ログイン中のユーザー のページでは、コントローラーで $this->response->disableCache() を呼んで Cache-Control: no-store を送ってください。 ログイン中なら beforeFilter() で呼ぶ、などです。

情報 ​

次のアラートは情報で、ほとんどのアプリケーションで修正する必要はありません。

  • モダン Web アプリケーション: ページが JavaScript を使っています。--ajax-spider=1 でそのリンクもクロールします。
  • 認証リクエストの特定: ZAP がログインフォームを見つけました。
  • 疑わしいコメント: HTML や JavaScript のコメントに「TODO」「password」「admin」などの語があります。秘密にすべき ことが書かれていないか確かめてください。
  • タイムスタンプの開示: レスポンスの数字がタイムスタンプに見えます。ふつうは問題ありません。

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