0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

最速で不正アクセスされるGoコードの書き方【逆張りセキュリティ講座】

0
Posted at

PythonHaru / 逆張りセキュリティ講座 第1回(認証編)

はじめに

最近、不正アクセスのニュースが絶えません。 そこで今回は逆張りして、「最速で不正アクセスされるGoコードの書き方」を解説します。

⚠️ これは「悪い例」を教える講座です。本番環境では絶対に真似しないでください。 試すのは自分のローカル環境だけにしてください。他人のサービスへの攻撃の試行は、不正アクセス禁止法などに触れるおそれがあります。

各回の流れはこうです。

  1. わざと脆弱に書く(皮肉つき)
  2. 何が起きるかを見る
  3. 正しい書き方に直す

シリーズ一覧はこちらです。

第1回は認証編。ログイン処理を題材にします。

環境

  • Go 1.22以降
  • database/sql + SQLite(ドライバは何でもOK)
  • golang.org/x/crypto/bcrypt(修正版で使用)

① 最速で破られるログイン(やってはいけない)

まずは「最速で不正アクセスされる」ログイン処理です。

func login(w http.ResponseWriter, r *http.Request) {
	q := fmt.Sprintf("SELECT id FROM users WHERE name='%s' AND password='%s'",
		r.FormValue("name"), r.FormValue("password"))
	var id int
	if err := db.QueryRow(q).Scan(&id); err != nil {
		http.Error(w, "NG", 401)
		return
	}
	fmt.Fprintf(w, "OK: %d", id)
}

ユーザー登録側も、もちろんこう書きます。

_, err := db.Exec("INSERT INTO users(name, password) VALUES(?, ?)", name, pw)

ちゃんと動くので、一見まったく問題ありません。動くことと安全なことは別です。

皮肉ポイント1:SQLは文字列連結で組みましょう

この書き方だと、ユーザーの入力がそのままSQL文の一部になります。入力欄に細工した文字列を入れるだけで、WHERE の条件が意図しない意味になり、パスワードを知らなくてもログインを通せてしまう可能性があります(SQLインジェクション)。

皮肉ポイント2:パスワードは平文で保存しましょう

DBが漏れた瞬間に、全ユーザーのパスワードがそのまま読まれます。他のサービスと同じパスワードを使い回している人は、そちらにも被害が広がります。

② 直した版

直す点は主に2つです。

登録時:パスワードはハッシュ化して保存する

hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
if err != nil {
	http.Error(w, "error", 500)
	return
}
_, err = db.Exec("INSERT INTO users(name, password_hash) VALUES(?, ?)", name, string(hash))

ログイン時:プレースホルダで入力とSQLを分離し、bcryptで比較する

func login(w http.ResponseWriter, r *http.Request) {
	name, pw := r.FormValue("name"), r.FormValue("password")

	var id int
	var hash string
	err := db.QueryRow(
		"SELECT id, password_hash FROM users WHERE name = ?", name,
	).Scan(&id, &hash)

	if err != nil || bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) != nil {
		http.Error(w, "NG", http.StatusUnauthorized)
		return
	}
	fmt.Fprintf(w, "OK: %d", id)
}

直したポイント

悪い例 直し方
SQLを文字列連結で組む プレースホルダ(?)を使う
パスワードを平文で保存・比較 bcryptでハッシュ化して比較
(失敗の理由を細かく返す) ユーザーなし/パスワード違いを同じ応答にする

※ プレースホルダの書き方はドライバによって異なります(SQLite・MySQLは ?、PostgreSQLは $1 など)。

※ 厳密には、ユーザーが存在しない場合は bcrypt の比較が走らないため、処理時間の差からユーザー名の有無が推測される余地が残ります。この話は発展編で扱います。

まとめ:今日から使えるチェックリスト

  • SQLを fmt.Sprintf や文字列連結で組んでいないか
  • パスワードを平文で保存・ログ出力していないか
  • bcryptなど、パスワード用のハッシュ関数を使っているか
  • ログイン失敗時の応答が、理由によって変わっていないか
  • ログイン試行の回数制限(レート制限)はあるか(次回以降で扱います)

次回予告

第2回はインジェクション編。SQL以外にも潜む、OSコマンド・ファイルパス・テンプレートまわりの「やってはいけない書き方」を、また皮肉たっぷりに書いていきます。

ストックリストに入れておくと、続きを見逃しません。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?