クリックジャッキング⚓︎
クリックジャッキングとは⚓︎
安全なウェブサイトの作り方 - 1.9 クリックジャッキング | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構 よりクリックジャッキングの定義を以下に引用します。
ウェブサイトの中には、ログイン機能を設け、ログインしている利用者のみが使用可能な機能を提供しているものがあります。該当する機能がマウス操作のみで使用可能な場合、細工された外部サイトを閲覧し操作することにより、利用者が誤操作し、意図しない機能を実行させられる可能性があります。このような問題を「クリックジャッキングの脆弱性」と呼び、問題を悪用した攻撃を、「クリックジャッキング攻撃」と呼びます。
AlesInfiny Maris でのクリックジャッキング対策⚓︎
AlesInfiny Maris では、クリックジャッキング対策としてフレーム内表示を原則としてすべて禁止します。
具体的には、以下の方針を採用します。
- 主要ブラウザー向けの対策として
Content-Security-Policyヘッダーのframe-ancestors 'none'を設定
- レガシーブラウザー向けの後方互換対策として
X-Frame-Options: DENYを併せて設定
これにより、 AlesInfiny Maris ではデフォルトで一切の埋め込み表示を許可しないセキュアな構成を実現します。 以降、各設定項目について説明します。
Content-Security-Policy : frame-ancestors⚓︎
HTTP レスポンスヘッダーに対して Content-Security-Policy ヘッダーフィールド の frame-ancestors ディレクティブ を出力します。
frame-ancestors は、どのオリジンから当該コンテンツを <frame> 要素や <iframe> 要素、 <embed> 要素、 <object> 要素で読み込めるかを指定するためのディレクティブです。 以下のような特徴があります。
- 複数オリジンの指定が可能
- ワイルドカード指定が可能(※ AlesInfiny Maris では禁止)
- 主要ブラウザーで広くサポート
以下に示す frame-ancestors の指定内容により、フレーム内表示の許可範囲が異なります。
| 設定値 | 表示できる範囲 |
|---|---|
frame-ancestors 'none'; | すべてのオリジンからのフレーム内の表示を禁止する |
frame-ancestors 'self'; | 同一オリジンからのフレーム内の表示のみを許可する |
frame-ancestors https://example.com; | 指定したオリジンからのフレーム内の表示のみを許可する |
frame-ancestors https://example.com https://sub.example.com; | 複数の指定したオリジンからのフレーム内の表示を許可する |
AlesInfiny Maris では、方針のとおり frame-ancestors 'none'; を設定します。
X-Frame-Options⚓︎
HTTP レスポンスヘッダーに対して X-Frame-Options ヘッダーフィールド を出力します。 これにより、他ドメインのサイトからの <frame> 要素や <iframe> 要素、 <embed> 要素、 <object> 要素による読み込みを制限します。
以下に示す指定内容により、フレーム内表示の許可範囲が異なります。
| 設定値 | 表示できる範囲 |
|---|---|
| DENY | すべてのドメインからのフレーム内の表示を禁止する |
| SAMEORIGIN | 同一オリジンからのフレーム内の表示のみを許可する |
| ALLOW-FROM (非推奨) | 指定したオリジンからのフレーム内の表示のみを許可する |
かつてはクリックジャッキング対策の主流でしたが、以下の制約があります。
- 指定可能なオリジンが限定的
ALLOW-FROMは主要なモダンブラウザーで互換性がない
このため、 X-Frame-Options はレガシーブラウザー向けの補助的な対策として位置づけます。
こちら に記載のとおり、より包括的な設定をする場合には Content-Security-Policy : frame-ancestors を使用するよう推奨されています。
このヘッダーで提供されるオプションよりも包括的な設定については、Content-Security-Policy ヘッダーの frame-ancestors ディレクティブを参照してください。
AlesInfiny Maris では、方針のとおり X-Frame-Options: DENY を設定します。
ブラウザーにおけるヘッダーの優先順位⚓︎
Content-Security-Policy の frame-ancestors と X-Frame-Options の両方が設定されている場合、以下のような挙動になります。
- モダンブラウザー
frame-ancestorsを優先し、X-Frame-Optionsを無視
frame-ancestors非対応のレガシーブラウザーX-Frame-Optionsにフォールバック
アプリケーションの設定⚓︎
AlesInfiny Maris では、frame-ancestors および X-Frame-Options を以下の方法で設定します。
-
CSR アプリケーション
SPA アプリケーションを配信する Web サーバーにおいて、すべてのレスポンスに対して当該ヘッダーを付与します。
ただし、 AlesInfiny Maris では、 Web API 側においても
Program.csにて API レスポンスヘッダーへ同様のヘッダーを付与します。 Web API は通常フレーム埋め込みの対象とはなりませんが、以下の理由により設定します。- セキュリティ設定の統一(標準化)
- 将来的な構成変更時の安全性確保
- セキュリティ監査対応の容易化
なお、 コードが冗長化することを避けるため、一部処理を別クラスに切り出しています。
Program.csでの HTTP レスポンスヘッダー設定例HttpSecurityHeadersMiddleware.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
using Microsoft.AspNetCore.Http; namespace Dressca.Web.Extensions; /// <summary> /// HTTP レスポンスヘッダーにセキュリティ関連の設定を追加するミドルウェアです。 /// </summary> public class HttpSecurityHeadersMiddleware { private readonly RequestDelegate next; /// <summary> /// <see cref="HttpSecurityHeadersMiddleware"/> クラスの新しいインスタンスを生成します。 /// </summary> /// <param name="next">HTTP 要求を処理できる delegate</param> public HttpSecurityHeadersMiddleware(RequestDelegate next) { this.next = next; } /// <summary> /// <see cref="HttpSecurityHeadersMiddleware"/> のメイン ロジックを実行します。 /// </summary> /// <param name="context">HTTP コンテキスト</param> /// <returns>パイプラインの次の処理</returns> public async Task InvokeAsync(HttpContext context) { context.Response.OnStarting(() => { // コンテンツタイプを誤認識しないよう、HTTPレスポンスヘッダに「X-Content-Type-Options: nosniff」の設定を追加 context.Response.Headers["X-Content-Type-Options"] = "nosniff"; // クリックジャッキング攻撃への対策として、HTTP レスポンスヘッダに、「Content-Security-Policy」を「frame-ancestors 'none'」に設定 context.Response.Headers.ContentSecurityPolicy = "frame-ancestors 'none'"; // レガシーブラウザー向けのクリックジャッキング攻撃への対策として、HTTP レスポンスヘッダに、「X-FRAME-OPTIONS」を「DENY」に設定 context.Response.Headers["X-Frame-Options"] = "DENY"; return Task.CompletedTask; }); await this.next(context); } }Program.cs (Dressca.Web.Consumer) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
using System.Text.Json; using Dressca.ApplicationCore; using Dressca.EfInfrastructure; using Dressca.Store.Assets.StaticFiles; using Dressca.Web.Configuration; using Dressca.Web.Consumer; using Dressca.Web.Consumer.Baskets; using Dressca.Web.Consumer.Mapper; using Dressca.Web.Consumer.Resources; using Dressca.Web.Controllers; using Dressca.Web.Extensions; using Dressca.Web.HealthChecks; using Dressca.Web.Http; using Dressca.Web.Runtime; using Maris.Core.Text.Json; using Microsoft.AspNetCore.CookiePolicy; using Microsoft.AspNetCore.HttpLogging; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; var builder = WebApplication.CreateBuilder(args); // アプリケーション設定ファイルの定義と型をバインドし、 DataAnnotation による検証を有効化する。 builder.Services .AddOptions<WebServerOptions>() .BindConfiguration(nameof(WebServerOptions)) .ValidateDataAnnotations() .ValidateOnStart(); builder.Services.AddSingleton<ApplicationCookieBuilder>(); // サービスコレクションに CORS を追加する。 builder.Services.AddCors(); // CookiePolicy を DI に登録(他のコードから IOptions<CookiePolicyOptions> で取得可能にする) builder.Services.AddOptions<CookiePolicyOptions>() .Configure<IOptions<WebServerOptions>>((cookiePolicy, webServerOptions) => { // アプリケーション全体の Cookie ポリシーを定義する。 cookiePolicy.HttpOnly = HttpOnlyPolicy.Always; cookiePolicy.Secure = CookieSecurePolicy.Always; cookiePolicy.MinimumSameSitePolicy = webServerOptions.Value.AllowedOrigins.Length > 0 ? SameSiteMode.None : SameSiteMode.Strict; }); builder.Services .AddControllers(options => { options.Filters.Add<BuyerIdFilterAttribute>(); if (builder.Environment.IsDevelopment()) { options.Filters.Add<BusinessExceptionDevelopmentFilter>(); } else { options.Filters.Add<BusinessExceptionFilter>(); } }) .ConfigureApiBehaviorOptions(options => { options.SuppressMapClientErrors = true; // Bad Request となった場合の処理。 var builtInFactory = options.InvalidModelStateResponseFactory; options.InvalidModelStateResponseFactory = context => { // エラーの原因をログに出力。 var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>(); logger.LogInformation(Events.ReceiveHttpBadRequest, LogMessages.ReceiveHttpBadRequest, JsonSerializer.Serialize(context.ModelState, DefaultJsonSerializerOptions.GetInstance())); // ASP.NET Core の既定の実装を使ってレスポンスを返却。 return builtInFactory(context); }; }); builder.Services.AddOpenApiDocument(config => { config.PostProcess = document => { document.Info.Version = "1.0.0"; document.Info.Title = "Dressca Consumer Web API"; document.Info.Description = "Dressca Consumer の Web API 仕様"; document.Servers.Add(new() { Description = "ローカル開発用のサーバーです。", Url = "https://localhost:5001", }); }; }); builder.Services.AddDresscaEfInfrastructure(builder.Configuration, builder.Environment); builder.Services.AddStaticFileAssetStore(); builder.Services.AddDresscaApplicationCore(); builder.Services.AddDresscaDtoMapper(); if (builder.Environment.IsDevelopment()) { builder.Services.AddHttpLogging(logging => { logging.LoggingFields = HttpLoggingFields.All; logging.RequestBodyLogLimit = 4096; logging.ResponseBodyLogLimit = 4096; }); } builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApiDescriptionProvider, HealthCheckDescriptionProvider>()); builder.Services.AddHealthChecks() .AddDresscaDbContextCheck("DresscaDatabaseHealthCheck"); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(); app.UseHttpLogging(); app.UseExceptionHandler(ErrorController.DevelopmentErrorRoute); } else { app.UseExceptionHandler(ErrorController.ErrorRoute); } app.UseHttpsRedirection(); app.UseSecuritySettings(); app.UseStaticFiles(); var options = app.Services.GetRequiredService<IOptions<WebServerOptions>>(); // アプリケーション設定にオリジンの記述がある場合のみ CORS ポリシーを追加する。 if (options.Value.AllowedOrigins.Length > 0) { app.UseCors(policy => { // Origins, Methods, Header, Credentials すべての設定が必要(設定しないと CORS が動作しない) // レスポンスの Header を フロントエンド側 JavaScript で使用する場合、 WithExposedHeaders も必須 policy .WithOrigins(options.Value.AllowedOrigins) .WithMethods("POST", "GET", "OPTIONS", "HEAD", "DELETE", "PUT") .AllowAnyHeader() .AllowCredentials() .WithExposedHeaders("Location"); }); } // DI に登録された CookiePolicyOptions を有効化する。 app.UseCookiePolicy(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks(HealthCheckDescriptionProvider.HealthCheckRelativePath); app.MapFallbackToFile("/index.html"); app.Run(); -
SSR アプリケーション
Web アプリケーションプロジェクトの
Program.csで設定します。 なお、 コードが冗長化することを避けるため、一部処理を別クラスに切り出しています。Program.csでの HTTP レスポンスヘッダー設定例Program.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
using DresscaCMS.Announcement; using DresscaCMS.Authentication; using DresscaCMS.Authentication.Infrastructures; using DresscaCMS.Web.Components; using DresscaCMS.Web.Components.Account; using DresscaCMS.Web.Extensions; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.HttpLogging; using Microsoft.FluentUI.AspNetCore.Components; var builder = WebApplication.CreateBuilder(args); var maxRequestBodySizeBytes = builder.Configuration.GetValue<long?>("MaxRequestSize:MaxRequestBodySizeBytes"); if (maxRequestBodySizeBytes.HasValue) { // Kestrel サーバーのリクエストボディサイズの上限を設定 builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = maxRequestBodySizeBytes; }); } var multipartBodyLengthLimit = builder.Configuration.GetValue<long?>("MaxRequestSize:MultipartBodyLengthLimit"); if (multipartBodyLengthLimit.HasValue) { // フォームオプションのマルチパートボディサイズの上限を設定 builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = multipartBodyLengthLimit.Value; }); } // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddFluentUIComponents(); builder.Services.AddRazorPages(); builder.Services.AddInMemoryStateStore(); // お知らせメッセージに関するサービス一式を登録 builder.Services.AddAnnouncementsServices( builder.Configuration, builder.Environment); // 認証に関するサービス一式を登録 builder.Services.AddAuthenticationServices( builder.Configuration, builder.Environment); // 入れ子になったオブジェクトのバリデーションをサポートするためのサービスを登録 builder.Services.AddValidation(); if (builder.Environment.IsDevelopment()) { builder.Services.AddHttpLogging(logging => { // どのデータをどのくらいの量出力するか設定。 // 適宜設定値は変更する。 logging.LoggingFields = HttpLoggingFields.All; logging.RequestBodyLogLimit = 4096; logging.ResponseBodyLogLimit = 4096; }); } // Blazor に依存した認証に関するサービスを登録 builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped<IdentityRedirectManager>(); builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { // HTTP 通信ログを有効にする。 app.UseHttpLogging(); await AuthenticationDbContextSeed.SeedAsync(app.Services); } if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/ServerError", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseHttpsRedirection(); app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorPages(); app.UseAuthorization(); // クリックジャッキング攻撃への対策として、 CSP frame-ancestors を設定 app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'"); // HTTP レスポンスヘッダーにセキュリティ関連の設定を追加するミドルウェアを使用 app.UseSecuritySettings(); app.Run();HttpSecurityHeadersMiddleware.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
namespace DresscaCMS.Web.Extensions; /// <summary> /// HTTP レスポンスヘッダーにセキュリティ関連の設定を追加するミドルウェアです。 /// </summary> public class HttpSecurityHeadersMiddleware { private readonly RequestDelegate next; /// <summary> /// <see cref="HttpSecurityHeadersMiddleware"/> クラスの新しいインスタンスを生成します。 /// </summary> /// <param name="next">HTTP 要求を処理できる delegate</param> public HttpSecurityHeadersMiddleware(RequestDelegate next) { this.next = next; } /// <summary> /// <see cref="HttpSecurityHeadersMiddleware"/> のメイン ロジックを実行します。 /// </summary> /// <param name="context">HTTP コンテキスト</param> /// <returns>パイプラインの次の処理</returns> public async Task InvokeAsync(HttpContext context) { context.Response.OnStarting(() => { // コンテンツタイプを誤認識しないよう、HTTPレスポンスヘッダに「X-Content-Type-Options: nosniff」の設定を追加 context.Response.Headers["X-Content-Type-Options"] = "nosniff"; // レガシーブラウザー向けのクリックジャッキング攻撃への対策として、HTTP レスポンスヘッダに、「X-FRAME-OPTIONS」を「DENY」に設定 context.Response.Headers["X-Frame-Options"] = "DENY"; return Task.CompletedTask; }); await this.next(context); } }
制限変更の方法⚓︎
前述のとおり、 AlesInfiny Maris ではクリックジャッキング対策としてデフォルトでフレーム内表示をすべて禁止する方針を採用しています。 ただし、業務要件上正当な理由で <iframe> 要素等の埋め込みが必要となる場合に限り、以下のような制限の変更を検討します。
同一オリジン内での埋め込みが必要な場合⚓︎
- 同一オリジン内において、複数の Web リソースをフレーム要素等で構成する設計が採用されている場合
- 同一オリジン内の別パスに配置されたコンテンツを、フレーム要素等を用いて表示する必要がある場合
このような場合には、同一オリジンからの埋め込みのみを許可します。
| ヘッダー | 設定値 |
|---|---|
| Content-Security-Policy | frame-ancestors 'self'; |
| X-Frame-Options | SAMEORIGIN |
特定の外部オリジンからの埋め込みが必要な場合⚓︎
- 信頼境界内にある特定の外部オリジンから、フレーム要素等を用いた表示を許可する必要がある場合
- 信頼された別オリジンの Web リソースと、画面統合する設計が採用されている場合
このような場合には、許可するオリジンを明示的に列挙します。
| ヘッダー | 設定値 |
|---|---|
| Content-Security-Policy | frame-ancestors https://example.com; |
| X-Frame-Options | DENY |
X-Frame-Options : ALLOW-FROM はブラウザー互換性の問題があるため使用せず、 X-Frame-Options : DENY に設定します。 これにより、 Content-Security-Policy に対応のブラウザーは frame-ancestors により埋め込みが許可され、非対応のブラウザーには埋め込みを許さないようになります。 なお、ワイルドカードによる埋め込み許可は禁止します。