Next.jsアプリケーションのセキュリティ対策
Next.jsというフレームワークを使って、趣味でWebアプリケーションを作っています。
必要なセキュリティ対策について調べて、自分が実践したものについて書きます。今回はNext.jsのコードに組み込まれるセキュリティ対策のみ扱います。たとえば、インフラストラクチャレベルで行われるDDos対策などは今回は扱いません。
フレームワークのバージョン:
"dependencies": {
"next": "14.0.4",
"react": "^18",
"react-dom": "^18",
"sass": "^1.69.5"
},
HTTPヘッダーをカスタムしてセキュアにする
next.config.js
というファイルを編集すると、レスポンスのHTTPヘッダーをカスタマイズすることができます。これはパスごとに設定することができるようです。
今回講じたセキュリティ対策は、コードベースで見るとnext.config.js
の編集でほぼ完結しました。カスタマイズしたHTTPヘッダーは以下の4つです。
- Strict-Transport-Security
- X-Frame-Options
- Referrer-Policy
- Content-Security-Policy
next.config.js
のサンプルは次のようになります。
/** @type {import('next').NextConfig} */
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
object-src 'none';
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`;
const nextConfig = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: cspHeader.replace(/\n/g, ""),
},
],
},
];
},
};
module.exports = nextConfig;
Strict-Transport-Securityの設定
Strict-Transport-SecurityというHTTPヘッダーは、HSTS(HTTP Strict Transport Security)と略されることもあります。Webサイトがブラウザに対し、HTTPSを用いた通信を強制するためのものです。
HTTPで接続を受け付けたとき、HTTPSにリダイレクトされるようになっていると、ユーザはリダイレクトされる前に暗号化されていないサイトと通信する可能性があります。Strict-Transport-Securityを使用すると、ユーザが誤ってHTTP接続を試みた場合は、Webサイトを読み込まず、自動的にHTTPSリクエストに変換するように指示することができます。
Strict-Transport-Securityの設定方法は、以下のページに説明があります。
Strict-Transport-Securityを設定すると、以下の攻撃の対策になります。
- 中間者攻撃(Man-in-the-Middle Attack): 攻撃者がユーザとサーバの間に入り込み、データの盗聴や改竄を行う
- SSLストリッピング: HTTPS接続を強制的にHTTP接続にダウングレードする
X-Frame-Optionsの設定
X-Frame-OptionsというHTTPヘッダーは、ブラウザがページを <iframe>
等のHTML要素内に表示することを許可するかどうかを示すために使用されます。ただし、ユーザがX-Frame-Optionsに対応したブラウザを使用していないと有効にはならないようです。
Next.jsでこのHTTPヘッダーを設定する方法は、以下のページに説明があります。
X-Frame
とは、<frame>
や<iframe>
などのHTML要素を総称しているのだと思います。自分は知らなかったのですが、これらはクリックジャッキングという攻撃の標的にされるようです。
クリックジャッキングとは、ユーザを意図しない操作(クリック)に誘導して、退会処理や投稿の公開範囲の変更などをさせる攻撃です。WebサイトAが、悪意ある他のWebサイトBの<iframe>
から読み込まれ、ユーザはWebサイトBを操作しているつもりでも、実はWebサイトAで重要な操作を実行してしまう、というものです。
以下のIPAのページに詳しい説明がありました。
X-Frame-OptionsをSAMEORIGIN
に設定すると、自身と同じオリジンのフレーム内でのみ読み込みを許可できます。これにより、他のWebサイト上で勝手にページが表示されることを防ぐことができるため、クリックジャッキング攻撃の防止になります。
Referrer-Policyの設定
Referrer-Policyは、HTTPヘッダーのひとつであるRefererの送信を、どの範囲で許可するかを指示するためのHTTPヘッダーです。
refererというのは流入元のURLのことです。たとえば、メールアドレスとパスワードを含むhttps://example.com/auth?email=example@gmail.com&password=abcd1234
のようなURLがあるとすると、こういったものが外部のWebサイトに遷移したときにrefererとして送信されてしまうことはリスクになります。
Referrer-Policyに設定可能なオプションは、こちらのページにありました。
Next.jsで設定する方法はこちらのページで説明されています。
strict-origin-when-cross-origin
という設定をするのがベストプラクティスである、という説明がありました。この設定にしておくと、次のような挙動になります。
- 遷移先が同一オリジンの場合のみ完全なURLを送信する
- 遷移先が外部サイトの場合、HTTPS接続であればURLのオリジン部分のみを送信する
- 遷移先が外部サイトでHTTP接続の場合、refererは送信しない
余談ですが、流入元を示すReferer
とReferrer-PolicyのReferrer
のスペルが違うことが気になって調べました。どうやらReferer
はスペルミスで、Referrer-Policy
はスペルミスしなかったので、不一致が生じているようです。
Content-Security-Policyの設定
コンテンツセキュリティポリシーはCSPとも略されます。このHTTPヘッダーは、 ブラウザがスクリプトやスタイルシートなどのリソースを読み込むときに、許可するオリジンを指定するために使用します。たとえば、外部サイトのスクリプトの読み込みをブロックしたりすることができます。
Next.jsでコンテンツセキュリティポリシーを設定する場合、nonceを使用してインラインスクリプトの正当性を確認するのが良さそうです。nonceというのは、サーバーサイドで生成したランダムな文字列で、これをヘッダーとインラインスクリプトの両方に埋め込むことで検証します。
nonceを使用する場合の実装については、以下のページに詳しい説明がありました。
コンテンツセキュリティポリシーの設定は、クロスサイトスクリプティングやクリックジャッキング、その他のインジェクション攻撃に対するセキュリティ対策として有効です。
インラインスクリプトや外部ドメインのスクリプトの読み込みを禁止することで、クロスサイトスクリプティングのリスクを減らすことができます。また、frame-ancestors
ディレクティブを使用することで、Webページが外部サイトのiframeに埋め込まれるのを防ぐことができ、これはクリックジャッキング攻撃のリスクを減らすことができます。
スタイルシートの読み込みを制限しなければならない理由は知らなかったのですが、expression()
関数を使用してスタイルシートからスクリプトを埋め込むことも可能なようです。コンテンツセキュリティポリシーでスタイルシートの読み込みを制限することで、その脆弱性の解消にもつながります。
その他のセキュリティ対策
最後にHTTPヘッダーのカスタマイズ以外のセキュリティ対策について軽く書きます。
- クロスサイトスクリプティング対策
- dangerouslySetInnerHTMLの使用を避ける
- CSRF(クロスサイトリクエストフォージェリ)対策
- CSRFトークンでフォームの正当性を検証する
- SQLインジェクション対策
- SQLを組み立てるときは文字列連結ではなくプレースホルダを使用する
CSRF対策やSQLインジェクション対策は、Next.jsに特別な話ではないので省略します。
dangerouslySetInnerHTML
については今回調べるまで自分は知りませんでした。
Reactはデフォルトでクロスサイトスクリプティング対策が講じられているのですが、それをバイパスする手段がdangerouslySetInnerHTML
らしいです。Reactは通常JSXに埋め込まれた値をエスケープしますが、次のように書くとHTMLとして解釈できるようになります。
※動作確認はしていません
import React from 'react';
function DangerousComponent({ htmlContent }) {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}
export default DangerousComponent;
このdangerouslySetInnerHTML
の使用は極力避けたほうが良いとされています。
Discussion
素晴らしい記事をありがとうございます!
とても参考になりました。
一点、Referrer-Policyの説明でリンク先が両方MDNになっているようでしたのでお伝えしておきます。
コメントいただきありがとうございます!初めてなので、とても励みになります。
リンクのご指摘もありがとうございます🙇失礼いたしました、修正しておきます。