SQLインジェクションのリスクを回避する

SQLパラメーターの値をsqlパッケージ関数の引数として提供することで、SQLインジェクションのリスクを回避できます。sqlパッケージの多くの関数は、SQLステートメントのパラメーターと、そのステートメントのパラメーターで使用される値のパラメーターを提供します(その他の関数は、プリペアドステートメントとパラメーターのパラメーターを提供します)。

次の例のコードは、?シンボルをidパラメーターのプレースホルダーとして使用しています。このパラメーターは関数の引数として提供されます。

// Correct format for executing an SQL statement with parameters.
rows, err := db.Query("SELECT * FROM user WHERE id = ?", id)

データベース操作を実行するsqlパッケージ関数は、提供された引数からプリペアドステートメントを作成します。実行時に、sqlパッケージはSQLステートメントをプリペアドステートメントに変換し、パラメーターとは別に送信します。

注:パラメーターのプレースホルダーは、使用しているDBMSとドライバーによって異なります。例えば、Postgres用のpqドライバーは、?の代わりに$1のようなプレースホルダー形式を受け入れます。

fmtパッケージの関数を使って、パラメーターを含むSQLステートメントを文字列として組み立てたくなるかもしれません — このように

// SECURITY RISK!
rows, err := db.Query(fmt.Sprintf("SELECT * FROM user WHERE id = %s", id))

これは安全ではありません!これを実行すると、GoはDBMSに完全なステートメントを送信する前に、%sフォーマット動詞をパラメーター値に置き換えて、SQLステートメント全体を組み立てます。これはSQLインジェクションのリスクを伴います。なぜなら、コードの呼び出し元が予期しないSQLスニペットをid引数として送信する可能性があるためです。そのスニペットは、アプリケーションにとって危険な予測不可能な方法でSQLステートメントを完了させる可能性があります。

例えば、特定の%s値を渡すことで、次のような結果になる可能性があり、データベース内のすべてのユーザーレコードが返される可能性があります。

SELECT * FROM user WHERE id = 1 OR 1=1;