TL;DR: 本書では、Web アプリケーション セキュリティにおける共通の脅威から守り、その影響を軽減する Web アプリケーションの包括的なセキュリティ戦略について学んでいきます。


はじめに

独自のインフラストラクチャを管理するか否かにかかわらず、堅牢なインフラストラクチャ セキュリティがあることは素晴らしいことです。ただ、クライアントのデータが Web アプリケーションを通して流出されると、やや冗長性になります。インフラストラクチャ セキュリティがあることは重要ですが、フロントエンド開発者やバックエンド開発者が 考慮しなければならない ことがあります。

現代の Web テクノロジの中で、セキュリティは一時しのぎであったり後で検討するべきことではありません。それは Web アプリケーションの計画、管理、構築のまさに基盤であるべきものです。

しかし、それだけではありません。

Web アプリケーション セキュリティは継続的に常に変化するものなので、不足する状況にはなりたくありません

貴社が セキュリティ違反の世界で別の統計 . の一部にならないように継続的に監視し取り組まなければなりません。プログラミング言語やフレームワークにかかわらず、プロジェクトの最初から従える汎用的なセキュリティ対策はたくさんあります。

本書では、Web アプリケーションの上から下までの(フロントエンドとバックエンド)セキュリティの最大のヒントをご紹介していきます。

HTTP Strict Transport Security (HSTS) ヘッダー

HSTS は Web アプリケーション全体で HTTPS を適用するブラウザーを提供できるヘッダーです。

今日、Web ブラウザーとサーバーの間のコミュニケーションを暗号化しない Web サイトに到着してしまう言い訳は通用しません。SSL 証明書は会社がどんなに安全かを証明するためのマーケティングツールとして使用されていましたが、今日の SSL 証明書は無料でかつ入手が簡単です。Let's Encrypt のような証明機関は、セキュアなインターネットは誰にとっても良いものなので 世界的に有名な大企業 の支援があります。

「今日、Web トラフィックが安全でないという言い訳は通用しません!@letsencrypt のように、無料で使用が簡単な SSL 証明書は大衆が使用できます!」

SSL 証明書を持っているだけで Web アプリケーションは安全になりません。それを通してどのようにトラフィックを強制するかをアプリケーションに伝えなければなりません。Web サイトによっては HTTP のリダイレクトで対処しているものもありますが、HSTS ヘッダーとしてそのオーバーヘッドなしで提供することも同様の効果が得られます。

次は HSTS ヘッダーの例です。

Strict-Transport-Security: max-age=630720; includeSubDomains; preload

CURL 要求がサイトに設定されているかを見るには:

curl -s -D- https://paypal.com | grep Strict-Transport-Security

Node.js 応答に設定されているかを見るには:

function requestHandler(req, res) {
  res.setHeader('Strict-Transport-Security', 'max-age=630720; includeSubDomains; preload');
}

"Web サイトが HTTP を介して同意し、HTTPS にリダイレクトする場合、訪問者は例えば、 http://www.foo.com/、または foo.com をタイプするだけで、リダイレクトされる前に、最初はそのサイトの暗号化されていないバージョンで通信するかもしれません。これによって、「中間者」攻撃の機会が作られます。このリダイレクトは元のサイトの安全なバージョンの代わりに、悪意のあるサイトに訪問者をダイレクトするために悪用される可能性があります。" – developer.mozilla.org

この HSTS ヘッダーの例には、max-ageincludeSubDomainspreload の3つの ディレクティブ があります。

  • max-age=<expire-time>

    ブラウザーは秒単位で、サイトが HTTPS のみを使ってアクセスできることを記憶します。これは、ブラウザーは max-age の有効期限が切れない限り、自動的に HTTPS を使ってサイトにアクセスすることを意味します。

  • includeSubDomains オプション

    このオプションのパラメーターが特定されると、このルールがサイトのすべてのサブドメインにも適用されます。

  • preload オプション

    Google は HSTS プリロード サービス を管理しています。ほとんどの最新のブラウザーはこのサービスをサポートしており(またはサポートする予定)、サイトが登録されていれば、安全でない HTTP 要求を通してサービスに連絡しません。これは、接続する前に、サイトが HTTPS を使用するようにします。

注記: HSTS を使い始めて、サイトが HTTPS証明書でセットアップされていなければ、そのサイトは到達不能になります。

HSTS browser compatibility table

X-XSS-Protection ヘッダー

XSS(クロスサイト スクリプト)はすべての Web アプリケーション攻撃で最も一般的なものです。

XSS は悪意のあるスクリプトが信頼されていた Web アプリケーションに挿入されたときに起こります。ほとんどの最新のブラウザーは XSS から守られるように設定されています。これは X-XSS-Protection ヘッダーを送信することで対応します。

注記: X-XSS-Protection ヘッダーは Firefox では使用できませんし、コンテンツ セキュリティ ポリシーによって冗長全体をレンダリングされています。この点については以下で詳細をお伝えします。

X-XSS-Protection ヘッダーの例は次の通りです。

X-XSS-Protection: 1; mode=block

Node.js 応答に設定されているかを見るには:

function requestHandler(req, res) {
  res.setHeader('X-XSS-Protection', '1; mode=block');
}

XSS ヘッダーの例には、1mode、もうひとつ、report の3つの ディレクティブ があります。

  • 1

    これは基本的にブール値で、XSS フィルター処理が有効かどうかを決定します。それを 0 に変更して無効にします。オプションの mode または report ディレクトリがなければ、ブラウザーはそのページをサニタイズしたり、影響を受けた部分を削除したりさえします。

  • mode=block オプション

    XSS フィルター処理を有効にします。ページをサニタイズするよりも、ブラウザーは攻撃が検知されると、そのページのレンダリングを実行しません。

  • report=<report url>

    XSS フィルター処理を有効にします。クロスサイト スクリプティング攻撃が検知されると、ブラウザーはページをサニタイズし、その違反をレポートします。

X-XSS-Protection browser compatibility table

X-Frame-Options ヘッダー

攻撃者が透明または不透明オブジェクトを Web アプリケーションに挿入すると、クリックジャッキングが起きます。これらは他の機能上では非表示レイヤーかもしれません。または、アプリケーションの一部として見えるように設計されているかもしれません。使用しているアプリケーションに個人詳細または支払い情報(またはその両方)を入力していると思わせるために、クリックジャッキング テキスト ボックスに同様のメソッドが使用できます。

クリックジャッキングで最も有名な例のひとつは Adobe Flash プラグイン設定ページ に対するものでした。感染されたアプリケーションが非表示のウィンドウにロードされ、セキュリティ設定を緩和させ、Web アプリケーションがカメラやマイクを使えるようにユーザーをトリックします。

クリックジャッキングを防ぐ別のヘッダーがあります!

サーバーはブラウザーに X-Frame-Options という名のヘッダプロトコルを提供します。このプロトコルにより、ユーザーがドメインを特定し、iFrames に同意できます。また、ユーザーがどのサイトに Web アプリケーションを埋め込むかを宣言できます。

X-Frame-Options ヘッダーの例は次の通りです。

X-Frame-Options: SAMEORIGIN

Node.js 応答に設定されているかを見るには:

function requestHandler(req, res) {
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
}

このヘッダーには、DENYALLOW-FROMSAMEORIGIN の3つの ディレクティブ の可能性があります。

  • DENY

    これはすべての枠組みをブロックします。

  • ALLOW-FROM

    これはドメインのリストを提供し、その中の枠組みを可能にします。

  • SAMEORIGIN

    これは、現在のドメイン内でだけ枠組みが許可されているという意味です。

X-Frame-Options browser compatibility table

コンテンツ セキュリティ ポリシー (CSP) ヘッダー

CSP はセキュリティのさらにモダンなレイヤーで、XSS やデータ挿入攻撃を含む1種以上の攻撃を検知したり軽減するのに役立ちます。

このヘッダーは完全に下位互換になるように設計されているので、それを無視して Web コンテンツの標準と同じ元のポリシーにデフォルトして、サポートしないブラウザーでもそれを使ってサーバーでまだ作業できます。

これはどの URL コンテンツをサイトにロードするかをブラウザーに正確に伝えることで機能します。

内部アナリティクスと Google アナリティクス だけを使用する CSP ヘッダーを含むには、Express.js サーバーで次を実行します。

const express = require('express');
const app = express();

app.use(function(req, res, next) {
    res.setHeader("Content-Security-Policy", "script-src 'self' https://analytics.google.com");
    return next();
});

app.use(express.static(__dirname + '/'));

app.listen(process.env.PORT || 3000);

ただし、すべての外部サイトに Web アップリケ―ションでスクリプトを実行してほしくないのであれば、次を単に含むだけです。

function requestHandler(req, res) {
    res.setHeader( 'Content-Security-Policy', "script-src 'self'" );
}

ここでは script-src ディレクティブは self に設定しているので、独自のドメイン内からのスクリプトのみを許可することに注意してください。コンテンツ セキュリティ ポリシーは両方とも優秀で強力ですが、注意して使用してください。HSTS のように、不適切な構成は不測の問題やコンテンツの欠落を招くことがあります。これは unsafe-inline キーワード が与えていなければ、インライン JavaScript も無効にします。そのキーワードは (‘sha256-OsJINy4ZgkXN5pDjr32TfT/PBETcXuD9koo2t1mYDzg=’) のようなハッシュ、または Nonce (‘nonce-...’) で、セキュリティにぴったりです。

CSP browser compatibility table

クロスサイト リクエスト フォージェリ (CSRF)

クロスサイト リクエスト フォージェリは、攻撃者が許可されているユーザーを偽装して、パスワードのリセットやメールアドレスの更新などを代わりにリクエストをして攻撃します。

私たちは OriginReferer のような HTTP ヘッダーをチェックして何年もの間、CSRF リクエストを軽減してきました。これらも効果がありますが、CSRF への新しい防衛策があります。それは SameSite クッキー ディレクティブです。SameSite はわずか1年ほどの比較的新しいもので、あまり知れ渡っていません。適用されると、ブラウザーがクロスサイト リクエストと共にこのクッキーを送信しないようにします。

SameSite の例は次の通りです。

Set-Cookie: sess=sessionid123; path=/; SameSite

Cookie SameSite browser compatibility table

Cookie

Cookie は Web アプリケーションの重要な機能で、通常、ユーザーのセッション識別を運ぶので、サーバーは各リクエストが分かります。SameSite ディレクティブはセッション情報を保護するには良い方法かもしれませんが、Cookie Prefixing は Cookie が確実に安全になるように利用される新しい方法です。

一部のユーザー エージェントの実装では次の Cookie プレフィックスをサポートします。

  • __Secure - セキュアなチャネルに送信される要求に Cookie だけを含むブラウザーを伝えます。
  • __Host - 確実なチャネルに送信される要求に Cookie だけを含むブラウザーを伝えるだけでなく、確実な原点の Cookie だけを使用するようにも伝え、そのスコープはサーバーによって渡されたパス属性に限られています。サーバーがパス属性を省略したら、その要求 URI の「ディレクトリ」が使用されます。Domain 属性は存在すべきでないというシグナルも送信し、それによって Cookie がその他のドメインに送信されないようにします。

実用的には __Host Cookie は使用されることが意図された場所に非常に固有ですので、定義するのに最も安全な方法として考慮されるべきです。

Cookie prefix browser compatibility table

まとめ

Web アプリケーションの安全性を最も効果的に維持する方法は脆弱性を常に更新することです。それは動的環境の動的なトピックで、攻撃者は常に一歩先を行っています。

現状に満足しきることは敵です。リラックスして自分は安心だと思った瞬間に、Web アプリケーションのセキュリティを向上させるチャンスを逃すことになります。

本書のアドバイスに従い、最新情報を入手し、システムについての深い知識を得るときに、攻撃を軽減するためにできることをしていると安心できます。 開発者や組織の多くは全体のセキュリティを気にしません。本書に記載の変更を実装するためには開発チーム全体を要するというものではなく、データの保護に向けて取り組んでいるということを識別する時間と洗練に向けた特定の投資を要します。

ですから、Strict Transport Security で HTTPS を強化することからコンテンツ セキュリティ ポリシー ヘッダーで Web アプリケーションをセキュアにすることまで、私たちは Web アプリケーションのセキュリティを確実にするために順調に前進しています。

補足:JavaScript で Auth0 認証

Auth0 では顧客が パスワードのリセット、ユーザーの作成、プロビジョニング、ブロッキング、削除などユーザー ID を管理 するのに役立るためにフルスタックの JavaScript を大いに活用しました。Auth0 Extend と呼ばれるサーバーなしのプラットフォームも作り、顧客が任意の JavaScript 関数を確実に実行できるようにしました。ですから、JavaScript Web アプリで ID 管理プラットフォームを使用するのが簡単なのは全く驚くことではありません。

先進認証を始めるために Auth0 では無料レベルを提供しています。詳細をご確認いただくか、無料 Auth0 アカウントをこちらから登録して ください!

Auth0 Login Page

auth0-jsjwt-decode 次のようなノード モジュールをインストールするのと同じように簡単です。

npm install jwt-decode auth0-js --save

それから次を JS アプリで実装してください。

const auth0 = new auth0.WebAuth({
  clientID: "YOUR-AUTH0-CLIENT-ID", // E.g., you.auth0.com
  domain: "YOUR-AUTH0-DOMAIN",
  scope: "openid email profile YOUR-ADDITIONAL-SCOPES",
  audience: "YOUR-API-AUDIENCES", // See https://auth0.com/docs/api-auth
  responseType: "token id_token",
  redirectUri: "http://localhost:9000" //YOUR-REDIRECT-URL
});

function logout() {
  localStorage.removeItem('id_token');
  localStorage.removeItem('access_token');
  window.location.href = "/";
}

function showProfileInfo(profile) {
  var btnLogin = document.getElementById('btn-login');
  var btnLogout = document.getElementById('btn-logout');
  var avatar = document.getElementById('avatar');
  document.getElementById('nickname').textContent = profile.nickname;
  btnLogin.style.display = "none";
  avatar.src = profile.picture;
  avatar.style.display = "block";
  btnLogout.style.display = "block";
}

function retrieveProfile() {
  var idToken = localStorage.getItem('id_token');
  if (idToken) {
    try {
      const profile = jwt_decode(idToken);
      showProfileInfo(profile);
    } catch (err) {
      alert('There was an error getting the profile: ' + err.message);
    }
  }
}

auth0.parseHash(window.location.hash, (err, result) => {
  if (err || !result) {
     // Handle error
    return;
  }

  // You can use the ID token to get user information in the frontend.
  localStorage.setItem('id_token', result.idToken);
  // You can use this token to interact with server-side APIs.
  localStorage.setItem('access_token', result.accessToken);
  retrieveProfile();
});

function afterLoad() {
  // buttons
  var btnLogin = document.getElementById('btn-login');
  var btnLogout = document.getElementById('btn-logout');

  btnLogin.addEventListener('click', function() {
    auth0.authorize();
  });

  btnLogout.addEventListener('click', function() {
    logout();
  });

  retrieveProfile();
}

window.addEventListener('load', afterLoad);

このコードを使った完全な例 を取得してください。

クイック スタート チュートリアル で、アプリで異なる言語やフレームワークを使用して認証の実装の仕方を学びましょう。