個人開発でシステムを作る場合の注意点その2(CSP、HSTS、CORS、CSRF)

Higtyの開発日記

概要

その1の記事では基本的なHTTPの仕組み、クッキーを使用した認証について解説しました。

https://www.higlabo.ai/book/higty-dev/page/fd62e1e0-8a29-6e8b-dcc3-3a1d9f1f781e

その2では通信やAPIの保護方法について紹介していきます。


HTTPに関係するセキュリティの対策を紹介した動画です。

それぞれについては後のセクションで詳しく解説していきます。



CSP

コンテント・セキュリティ・ポリシーはJavaScriptやCSSなどのコンテントをロードする場所のホワイトリストを設定できます。またいろいろなことができてしまうobjectタグの使用を禁止することも可能です。


ASP.NETでCSPヘッダーをつける方法はいかになります。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();

var app = builder.Build();

app.Use(async (context, next) =>
{
    // まずは Report-Only 推奨(違反を収集してから本番適用)
    context.Response.Headers["Content-Security-Policy-Report-Only"] =
        "default-src 'self'; " +
        "base-uri 'self'; " +
        "object-src 'none'; " +
        "frame-ancestors 'none'; " +
        "img-src 'self' data:; " +
        "font-src 'self' data:; " +
        "style-src 'self' 'unsafe-inline'; " +   // 後で nonce / hash 化推奨
        "script-src 'self'; " +                  // 後で nonce / hash 化推奨
        "connect-src 'self'; " +
        "report-to csp-endpoint;";

    // Report-To(ブラウザ対応に差あり。まずは受け口を用意するのが先)
    context.Response.Headers["Report-To"] =
        "{\"group\":\"csp-endpoint\",\"max_age\":10886400," +
        "\"endpoints\":[{\"url\":\"/csp-report\"}]}";

    await next();
});

app.UseStaticFiles();
app.MapDefaultControllerRoute();
app.Run();

pythonのFastAPIの場合は以下になります。

from fastapi import FastAPI, Request
from fastapi.responses import Response

app = FastAPI()

CSP = (
    "default-src 'self'; "
    "base-uri 'self'; "
    "object-src 'none'; "
    "frame-ancestors 'none'; "
    "img-src 'self' data:; "
    "font-src 'self' data:; "
    "style-src 'self'; "
    "script-src 'self'; "
    "connect-src 'self'; "
    "upgrade-insecure-requests"
)

@app.middleware("http")
async def add_csp(request: Request, call_next):
    response: Response = await call_next(request)
    response.headers["Content-Security-Policy"] = CSP
    return response

上記は一例です。実際には自分のサイトのニーズに合わせて設定する必要があります。


HSTS

HTTSではhttpsの暗号化された通信のみを許可します。httpによる通信では内容が暗号化されておらず、中間者攻撃による情報の窃盗が可能です。

https://www.cloudflare.com/ja-jp/learning/security/threats/on-path-attack/


HSTSの設定をしておくことでこういった攻撃から情報を守ることができます。


実際に設定する方法です。


ASP.NETでHSTSを有効にする方法

using System;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHsts(options =>
{
    options.MaxAge = TimeSpan.FromDays(365); // 例: 1年
    options.IncludeSubDomains = true;        // サブドメインにも適用するなら
    // options.Preload = true;               // preload申請する場合だけ(後述)
    // options.ExcludedHosts.Add("example.com"); // 例外ホストが必要なら
});

builder.Services.AddControllers();

var app = builder.Build();

// 開発環境では HSTS を出さない(ブラウザに長期キャッシュされ事故りやすい)
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

// HTTPS リダイレクト(AzureでHTTPS Onlyにしてても入れてOK)
app.UseHttpsRedirection();

app.MapControllers();
app.Run();


pythonのFastAPIの場合は以下になります。

from fastapi import FastAPI, Request
from fastapi.responses import Response

app = FastAPI()

HSTS_VALUE = "max-age=31536000; includeSubDomains"  # 1年

@app.middleware("http")
async def add_hsts_header(request: Request, call_next):
    response: Response = await call_next(request)
    # HTTPS のときだけ付けたい場合の簡易判定(必要なら調整)
    if request.url.scheme == "https" or request.headers.get("x-forwarded-proto") == "https":
        response.headers["Strict-Transport-Security"] = HSTS_VALUE
    return response


CORS

CORSを使うとスクリプトからのアクセス可能なドメインや

HTTPメソッドの種類を制限することができます。


ASP.NETでは以下のように設定します。

※「ChatGPTにASP.NETでのCORSの設定方法」と訊く

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowFrontend", policy =>
    {
        policy
            .WithOrigins("https://example.com") // フロントのURL
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

builder.Services.AddControllers();

var app = builder.Build();
// httpの場合、httpsにリダイレクト
app.UseHttpsRedirection();
// ★ UseRouting の後 / UseEndpoints の前
app.UseCors("AllowFrontend");

app.UseAuthorization();
app.MapControllers();

app.Run();


FastAPIの場合は以下のようになります。

※「ChatGPTにFastAPIでのCORSの設定方法」と訊く

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "https://example.com",          # 本番フロント
    "http://localhost:3000",        # 開発フロント
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,          # ["*"] は credentials と併用不可
    allow_credentials=True,         # Cookie/認証情報を送るなら True
    allow_methods=["*"],            # ["GET","POST","OPTIONS"] などでもOK
    allow_headers=["*"],            # Authorization, Content-Type など
    expose_headers=["Content-Disposition"],  # 添付DLなら重要
)

@app.get("/api/hello")
def hello():
    return {"ok": True}


CSRF

CSRFという攻撃方法があります。この攻撃はCORSでは防ぐことができません。CSRFトークンを活用することで防ぐことが可能です。


なぜCSRFが発生するのかはやや複雑な仕組みです。

詳しく知りたい人は以下の記事は参考になります。