はじめに
RISSの資格取得に向けて「体系的に学ぶ安全なWebアプリケーションの作り方第2版(以下、徳丸本)」を読んでいます。
その中で、GETメソッドには代表的な3つの危険性があることが分かりました。
それらについて、基本的な事象をはじめ、C#やGoといったプログラム単位での危険性について、まとめました。
GETメソッドに潜む3つの危険性
まず、同著で取り上げている危険性は以下の3つです。
-
URL上に指定されたパラメータがRefere経由で外部に漏れる
-
URL上に指定されたパラメータがアクセスログに残る
-
URL上のパラメータがブラウザのアドレスバーに表示され、他人に覗かれる
実装レベルでの対応策は「重要な情報はURLに入れず、ボディに入れる」が最適と思われます。
これについて、C#とGoに分けて、BadパターンとGoodパターンの観点でまとめます。
実装レベルにおけるGETメソッドの危険性
■ 実装シナリオ:パスワード再設定
ユーザーにメールでリンクを送り、クリックするとパスワード再設定画面が開く機能を例に挙げてみます。
ここでは 「認証用トークン」 をどのようにサーバーに渡すかがキモになります。
❌ Badパターン:GETメソッド(パラメータ渡し)
URL: `https://example.com/reset-password?token=SECRET_12345`
この状態でユーザーが画面を開き、もしそのページ内に外部サイト(例: 広告や解析ツール)へのリンクや画像埋め込みがあったと仮定します。
ここでは、以下の2つが脆弱性の要因と考えられます。
- Referer漏洩:外部サイトへのリクエストヘッダー
Refererに?token=SECRET_12345が丸ごと乗って送信される。外部サイトの管理者はそのトークンを使ってなりすましが可能になる。 - ログ: AWS ALBやIISのアクセスログに
GET /reset-password?token=SECRET_12345と残り、インフラ担当者全員にトークンがバレる。
✅ Good Pattern:POSTメソッド(ボディ渡し)
画面表示はGETで行いますが、重要な処理は フォーム送信(POST) で行います。
C#の場合
❌ 危険な実装:GETで秘密情報を受け取る
// [GET] /api/login?password=MyPassword123
[HttpGet("login")]
public IActionResult Login([FromQuery] string password)
{
// ログに "GET /api/login?password=MyPassword123" と残る
// ブラウザ履歴にもパスワードが残る
// 外部サイトへ遷移すると Referer でバレる
var user = _authService.Authenticate(password);
return Ok(user);
}
✅ 安全な実装:POSTでBodyで受け取る
public class LoginRequest
{
public string Password { get; set; }
}
// [POST] /api/login
// ボディ: { "password": "MyPassword123" }
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
// ログには "POST /api/login" としか残らない(ボディは記録されない)
// URLにも出ない
var user = _authService.Authenticate(request.Password);
return Ok(user);
}
Goの場合
❌ 危険な実装
// GET /api/[email protected]&token=SECRET
func UpdateEmail(c *gin.Context) {
token := c.Query("token") // URLパラメータから取得
// 危険!
}
✅ 安全な実装
type UpdateEmailRequest struct {
NewEmail string `json:"new_email"`
Token string `json:"token"`
}
// POST /api/update_email
func UpdateEmail(c *gin.Context) {
var req UpdateEmailRequest
// JSONボディからバインドする
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
// 安全に処理
}
どうしてもGETが必要な場合の「保険」(Referrer-Policy)
検索キーワードなど、仕様上どうしてもGETパラメータを使いたい(URLをシェアさせたい)場合があります。
その場合、「Refererによる漏洩」だけはHTTPヘッダーで防ぐことができます。
Referrer-Policy ヘッダー
レスポンスヘッダーに以下を設定します。
Referrer-Policy: strict-origin-when-cross-origin
この対応により、以下の効果が得られます。
- 自分のサイト内(Same Origin)ではReferer(URL全体)を送信する。
- 外部サイト(Cross Origin)へ行くときは、ドメインだけ送信し、パスやクエリパラメータ(
?token=...)は削除して送信する。
ASP.NET Coreなら Program.cs や Middleware で設定しますし、GoならGinのミドルウェアで設定します。
まとめ
普段、何気なくGETしている処理も「本当にGETメソッドでいいか?」と考えるきっかけになりました。
これに限らず、なぜその処理を行うのか?という実装のその先にある目的を明確にすることで、ただのコードから意味のあるコードになると感じました。