SSR アプリケーションのセキュリティ

サーバーレンダリングアプリケーションの保護

AdonisJSを使用してサーバーレンダリングアプリケーションを作成している場合は、CSRFXSSコンテンツスニッフィングなどの一般的なWeb攻撃からアプリケーションを保護するために、@adonisjs/shieldパッケージを使用する必要があります。

このパッケージはウェブスターターキットとして事前に設定されています。ただし、以下の手順にしたがってパッケージを手動でインストールおよび設定することもできます。

@adonisjs/shieldパッケージは@adonisjs/sessionパッケージに依存しているため、セッションパッケージを設定することを忘れないでください。

node ace add @adonisjs/shield
  1. 検出されたパッケージマネージャーを使用して@adonisjs/shieldパッケージをインストールします。

  2. adonisrc.tsファイル内に以下のサービスプロバイダーを登録します。

    {
    providers: [
    // ...other providers
    () => import('@adonisjs/shield/shield_provider'),
    ]
    }
  3. config/shield.tsファイルを作成します。

  4. start/kernel.tsファイル内に以下のミドルウェアを登録します。

    router.use([() => import('@adonisjs/shield/shield_middleware')])

CSRF保護

CSRF(クロスサイトリクエストフォージェリ)は、悪意のあるウェブサイトがユーザーに明示的な同意なしにフォームの送信を行わせる攻撃です。

CSRF攻撃に対抗するためには、ウェブサイトだけが生成および検証できるCSRFトークン値を持つ非表示の入力フィールドを定義する必要があります。したがって、悪意のあるウェブサイトによってトリガーされるフォームの送信は失敗します。

フォームの保護

@adonisjs/shieldパッケージを設定すると、CSRFトークンのないすべてのフォームの送信は自動的に失敗します。したがって、CSRFトークンを持つ非表示の入力フィールドを定義するためにcsrfFieldエッジヘルパーを使用する必要があります。

エッジヘルパー

<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="名前を入力してください">
<button type="submit"> 送信 </button>
</form>

出力されるHTML

<form method="POST" action="/">
<input type="hidden" name="_csrf" value="Q9ghWSf0-3FD9eCiu5YxvKaxLEZ6F_K4DL8o"/>
<input type="name" name="name" placeholder="名前を入力してください"/>
<button type="submit">送信</button>
</form>

フォームの送信時、shield_middlewareは自動的に_csrfトークンを検証し、有効なCSRFトークンを持つフォームの送信のみを許可します。

例外の処理

CSRFトークンが存在しないか無効な場合、ShieldはE_BAD_CSRF_TOKEN例外を発生させます。デフォルトでは、AdonisJSは例外をキャプチャし、エラーフラッシュメッセージを含んだフォームにユーザーをリダイレクトします。

Edgeテンプレート内でフラッシュメッセージにアクセスするには、次のようにします。

@error('E_BAD_CSRF_TOKEN')
<p> {{ $message }} </p>
@end
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="名前を入力してください">
<button type="submit"> 送信 </button>
</form>

また、グローバル例外ハンドラ内でE_BAD_CSRF_TOKEN例外を自己処理することもできます。

import app from '@adonisjs/core/services/app'
import { errors } from '@adonisjs/shield'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
export default class HttpExceptionHandler extends ExceptionHandler {
async handle(error: unknown, ctx: HttpContext) {
if (error instanceof errors.E_BAD_CSRF_TOKEN) {
return ctx.response
.status(error.status)
.send('ページの有効期限が切れました')
}
return super.handle(error, ctx)
}
}

設定リファレンス

CSRFガードの設定はconfig/shield.tsファイルに保存されます。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csrf: {
enabled: true,
exceptRoutes: [],
enableXsrfCookie: true,
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
},
})
export default shieldConfig

enabled

CSRFガードをオンまたはオフにします。

exceptRoutes

CSRF保護から除外するルートパターンの配列です。アプリケーションにAPI経由でフォームの送信を受け入れるルートがある場合は、除外する必要があります。

より高度な使用例では、特定のルートを動的に除外するために関数を登録することもできます。

{
exceptRoutes: (ctx) => {
// /api/で始まるすべてのルートを除外
return ctx.request.url().includes('/api/')
}
}

enableXsrfCookie

有効にすると、ShieldはXSRF-TOKENという名前の暗号化されたクッキーにCSRFトークンを保存します。これにより、Axiosなどのフロントエンドのリクエストライブラリが自動的にXSRF-TOKENを読み取り、サーバーレンダリングされたフォームなしでAjaxリクエストを行う際にヘッダーとして設定できます。

Ajaxリクエストをプログラムでトリガーしない場合は、enableXsrfCookieを無効にしておく必要があります。

methods

保護するHTTPメソッドの配列です。指定されたメソッドのすべての受信リクエストは有効なCSRFトークンを提供する必要があります。

cookieOptions

XSRF-TOKENクッキーの設定です。使用可能なオプションについては、クッキーの設定を参照してください。

CSPポリシーの定義

CSP(コンテンツセキュリティポリシー)は、JavaScript、CSS、フォント、画像などの信頼できるソースを定義することによって、XSS攻撃からアプリケーションを保護します。

CSPガードはデフォルトで無効になっています。ただし、有効にし、ポリシーディレクティブをconfig/shield.tsファイル内で設定することをオススメします。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csp: {
enabled: true,
directives: {
// ポリシーディレクティブをここに記述
},
reportOnly: false,
},
})
export default shieldConfig

enabled

CSPガードをオンまたはオフにします。

directives

CSPディレクティブを設定します。使用可能なディレクティブのリストはhttps://content-security-policy.com/で確認できます。

const shieldConfig = defineConfig({
csp: {
enabled: true,
directives: {
defaultSrc: [`'self'`],
scriptSrc: [`'self'`, 'https://cdnjs.cloudflare.com'],
fontSrc: [`'self'`, 'https://fonts.googleapis.com']
},
reportOnly: false,
},
})
export default shieldConfig

reportOnly

reportOnlyフラグが有効になっている場合、CSPポリシーはリソースをブロックしません。代わりに、違反をreportUriディレクティブで設定されたエンドポイントに報告します。

const shieldConfig = defineConfig({
csp: {
enabled: true,
directives: {
defaultSrc: [`'self'`],
reportUri: ['/csp-report']
},
reportOnly: true,
},
})

また、違反レポートを収集するためにcsp-reportエンドポイントを登録します。

router.post('/csp-report', async ({ request }) => {
const report = request.input('csp-report')
})

Nonceの使用

インラインのscriptタグとstyleタグを許可するには、それらにnonce属性を定義できます。nonce属性の値は、cspNonceプロパティを使用してEdgeテンプレート内でアクセスできます。

<script nonce="{{ cspNonce }}">
// インラインJavaScript
</script>
<style nonce="{{ cspNonce }}">
/* インラインCSS */
</style>

また、ディレクティブの設定内で@nonceキーワードを使用してnonceベースのインラインスクリプトとスタイルを許可します。

const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@nonce'],
},
},
})

Vite Devサーバーからアセットを読み込む

Viteの統合を使用している場合、Vite Devサーバーが提供するアセットを許可するために次のCSPキーワードを使用できます。

  • @viteDevUrlはVite DevサーバーのURLを許可リストに追加します。
  • @viteHmrUrlはVite HMRウェブソケットサーバーのURLを許可リストに追加します。
const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
connectSrc: ['@viteHmrUrl']
},
},
})

Viteのバンドル出力をCDNサーバーにデプロイしている場合は、@viteDevUrl@viteUrlキーワードに置き換えて、開発サーバーとCDNサーバーの両方からのアセットを許可する必要があります。

directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
defaultSrc: [`'self'`, '@viteUrl'],
connectSrc: ['@viteHmrUrl']
},

Viteによって注入されるスタイルにNonceを追加する

現在、ViteはDOM内に注入されるstyleタグにnonce属性を定義することを許可していません。これに関してはオープンなPRがあり、近々解決されることを期待しています。

HSTSの設定

Strict-Transport-Security(HSTS)レスポンスヘッダーは、ブラウザに常にHTTPSを使用してウェブサイトを読み込むように指示します。

config/shield.tsファイルを使用してヘッダーディレクティブを設定できます。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
hsts: {
enabled: true,
maxAge: '180 days',
includeSubDomains: true,
},
})

enabled

HSTSガードをオンまたはオフにします。

maxAge

max-age属性を定義します。値は秒単位の数値または文字列形式の時間表現である必要があります。

{
// 10秒間記憶する
maxAge: 10,
}
{
// 2日間記憶する
maxAge: '2 days',
}

includeSubDomains

includeSubDomainsディレクティブを定義して、サブドメインに設定を適用します。

X-Frame保護の設定

X-Frame-Optionsヘッダーは、ブラウザがiframeframeembedobjectタグ内に埋め込まれたウェブサイトをレンダリングすることが許可されているかどうかを示すために使用されます。

もしCSPを設定している場合は、代わりにframe-ancestorsディレクティブを使用し、xFrameガードを無効にできます。

config/shield.tsファイルを使用してヘッダーディレクティブを設定できます。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
xFrame: {
enabled: true,
action: 'DENY'
},
})

enabled

xFrameガードをオンまたはオフにします。

action

actionプロパティはヘッダーの値を定義します。DENYSAMEORIGIN、またはALLOW-FROMのいずれかを指定できます。

{
action: 'DENY'
}

ALLOW-FROMの場合、domainプロパティも定義する必要があります。

{
action: 'ALLOW-FROM',
domain: 'https://foo.com',
}

MIMEスニッフィングの無効化

X-Content-Type-Optionsヘッダーは、ブラウザに対してcontent-typeヘッダーに従い、HTTPレスポンスの内容を検査してMIMEスニッフィングを行わないよう指示します。

このガードを有効にすると、ShieldはすべてのHTTPレスポンスに対してX-Content-Type-Options: nosniffヘッダーを定義します。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
contentTypeSniffing: {
enabled: true,
},
})