はじめに
本記事ではGo言語とMySQLを使用しています。
Go言語のdatabase/sqlパッケージのチュートリアルを読んでて、プレースホルダを使う意味について分からなかったのですが、42Tokyoで一緒に勉強している学生から教えてもらえました。
You should, in general, always prepare queries to be used multiple times. The result of preparing the query is a prepared statement, which can have placeholders (a.k.a. bind values) for parameters that you’ll provide when you execute the statement. This is much better than concatenating strings, for all the usual reasons (avoiding SQL injection attacks, for example).
一般に、複数回使用するクエリは常に準備する必要があります。このステートメントには、実行時に指定するパラメータのプレースホルダ (別名バインド値) を持たせることができます。これは、文字列を連結するよりもずっとよい方法です。通常の理由(たとえば、SQL インジェクション攻撃の回避など)でもそうです。
stmt, err := db.Prepare("select id, name from users where id = ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
rows, err := stmt.Query(1)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
// ...
}
if err = rows.Err(); err != nil {
log.Fatal(err)
}
?
を使うこととSQLインジェクションの回避がどう関係してるんだ??となりました。
用語解説
本題に入る前に用語を整理します。
SQLインジェクション
SQLインジェクションとは、データベースと連動したWebアプリケーションなどに対する攻撃手法の一つで、検索文字列など外部から指定するパラメータの一部にSQL文の断片などを混入させ不正な操作を行うもの。また、そのような攻撃を可能にする脆弱性。 “injection” は「注入」の意。
プリペアードステートメント
プリペアドステートメントとは、プログラム上で動的にSQL文を生成する必要があるとき、可変部分を変数のようにしたSQL文をあらかじめ作成しておき、値の挿入は処理系に行わせる方式。
プリペアドステートメントはSQL文の特定の箇所を変数のように後から変更できる状態(プレースホルダ)で記述された一種の雛形(テンプレート)で、事前にコンパイルされ高速に実行できる。値の挿入はSQL文の解釈とは別に言語処理系によって行われるため、入力文字列の一部を誤ってSQL文の一部として解釈する危険はない。
プレースホルダ
プレースホルダとは、実際の内容を後から挿入するために、とりあえず仮に確保した場所のこと。また、そのことを示す標識などのこと。
なぜプレースホルダを使うといいのか
用語解説が答えになっている気がしますが、具体例を使って解説します。
下記のように、クエリを作るときにfmt.Sprintf()
を使用する場合とプレースホルダを使用する場合を比較します。
idは、リクエストパラメータから取得します。
id := r.FormValue("id")
db.Exec(fmt.Sprintf("SELECT * FROM table WHERE id = %s", id))
id := r.FormValue("id")
db.Exec("SELECT * FROM users WHERE id = ?", id)
fmt.Sprintf()
でクエリを作った場合
リクエストパラメータから文字列操作でクエリを作成します。
idはユーザーが自由に入力できる値なので、リクエストパラメータに
10;DELETE FROM users
が入力された場合、db.Exec
に渡される文字列は
"SELECT * FROM users WHERE id = 10;DELETE FROM users"
となりusers
テーブルの全レコードが削除されてしまいます。
怖い。
プレースホルダを使った場合
-
"SELECT * FROM users WHERE id = ?"
というプリペアードステートメントがデータベースに送られて、構文が確定する。 -
?
で確保された場所にリクエストパラメータに入力されたidが代入される。
という2段階で実行されるようです。
このとき、?
の部分に代入される値はidに固定され、構文は変化しないのでリクエストパラメータに10;DELETE FROM users
が入力されたとしてもこの文字列はidの値として不正なのでSQLがエラーを吐いてくれます。
まとめ
プレースホルダはSQLインジェクションを防ぐために使用することが分かりました。