CORSの適切な設定、クライアントからのリダイレクトURLの厳密な検証
ウェブアプリケーション開発において、CORS設定とリダイレクト検証は基本的なセキュリティ要件です。Go言語のnet/httpパッケージを中心に、具体的な対策方法を学びましょう。
CORS (Cross-Origin Resource Sharing) の適切な設定
CORSは、異なるオリジン(ドメイン、プロトコル、ポート)間で安全にリソースを共有するための仕組みです。
対策の基本:ホワイトリストによる厳格なオリジン制限
CORSのセキュリティ脆弱性のほとんどは、サーバーが許可すべきでない外部サイトからのアクセスを許してしまうことに起因します。対策は、許可するオリジンを厳しく限定したホワイトリストを作成することです。
Go言語によるCORSミドルウェアの実装
Goでは、http.Handlerをラップ「ミドルウェア」としてCORS処理を実装する方法を見ていきます。
package main
import (
"net/http"
"log"
)
// 許可されたオリジンのリスト(ホワイトリスト)
var allowedOrigins = []string{
"https://app.yourcompany.com",
"http://localhost:3000", // 開発・テスト環境用
}
// CORS処理を行うミドルウェア
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. リクエストのOriginヘッダーを取得
origin := r.Header.Get("Origin")
// 2. ホワイトリストに含まれているかを確認
isAllowed := false
for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin {
isAllowed = true
break
}
}
if isAllowed {
// 3. 許可されたオリジンに対してのみACAOヘッダーを設定
w.Header().Set("Access-Control-Allow-Origin", origin)
// 4. プリフライトリクエスト (OPTIONSメソッド) の処理
if r.Method == "OPTIONS" {
// 許可するHTTPメソッドとカスタムヘッダーを設定
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.WriteHeader(http.StatusOK) // 200 OKを返して終了
return
}
}
// 5. 次のハンドラ(本来の処理)を実行
next.ServeHTTP(w, r)
})
}
// 実際のAPIハンドラ(例)
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK: Data loaded securely."))
}
func main() {
// APIハンドラにCORSミドルウェアを適用
handler := corsMiddleware(http.HandlerFunc(apiHandler))
http.Handle("/api/data", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
絶対にしてはいけないこと
w.Header().Set("Access-Control-Allow-Origin", "*") と設定すること。
これにより、あらゆるサイトからのアクセスを許可し、機密情報を含むCookieや認証情報が盗まれるリスクの高まりになります。
オープンリダイレクトの対策
オープンリダイレクトは、リダイレクト先URLをユーザーからの入力(クエリパラメータなど)として受け取り、その検証を怠った場合に発生する脆弱性です。攻撃者はユーザーをフィッシングサイトに誘導できます。
対策の基本:リダイレクトURLの厳密な検証
ユーザーが入力したURLは、自社のドメイン内を指しているか、または相対パスであるかをnet/urlパッケージを使って徹底的にチェックします。
package main
import (
"net/http"
"net/url"
"strings"
"log"
)
// 自社のドメインを設定
const allowedHost = "yourcompany.com"
// リダイレクト処理を行うハンドラ
func loginRedirectHandler(w http.ResponseWriter, r *http.Request) {
redirectTo := r.URL.Query().Get("redirect_to")
safeDefaultURL := "/dashboard" // 検証失敗時やリダイレクト先がない場合の安全なパス
if redirectTo == "" {
http.Redirect(w, r, safeDefaultURL, http.StatusFound)
return
}
// 1. URLをパース(解析)
parsedURL, err := url.Parse(redirectTo)
if err != nil {
log.Printf("Error parsing URL: %v", err)
http.Redirect(w, r, safeDefaultURL, http.StatusFound)
return
}
// 2. 厳密な検証ロジック
// 2-A. スキーム(http, https, javascriptなど)のチェック
// 相対パスの場合、Schemeは空になります
if parsedURL.Scheme != "" && parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
log.Printf("Dangerous scheme blocked: %s", parsedURL.Scheme)
http.Redirect(w, r, safeDefaultURL, http.StatusFound)
return
}
// 2-B. ホスト名(ドメイン)のチェック
if parsedURL.Host != "" {
// ホスト名が許可されたドメインと完全に一致するかチェック
if !strings.EqualFold(parsedURL.Host, allowedHost) {
log.Printf("Open redirect attempt blocked: Host %s is external.", parsedURL.Host)
http.Redirect(w, r, safeDefaultURL, http.StatusFound)
return
}
}
// 3. 検証を通過した場合、安全なリダイレクトを実行
http.Redirect(w, r, redirectTo, http.StatusFound)
}
重要なポイント
net/url.Parseは、//attacker.com/path のようなプロトコル省略形でもHostにattacker.comを入れてくれます。そのため、ホストが存在し、かつそれがallowedHostと一致しない場合にブロックするロジックが非常に有効です。
まとめ
| 対策 | Go言語で使う主要なパッケージ/関数 | セキュリティの核となるロジック |
|---|---|---|
| CORS | net/http (http.Handler, r.Header.Get("Origin")) | Access-Control-Allow-Origin ヘッダーを、ホワイトリストに含まれるオリジンにのみ設定する |
| オープンリダイレクト | net/url (url.Parse, parsedURL.Host) | リダイレクト先の Host が 自社ドメイン と一致するか、または 相対パス であることを確認す |