2
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】BigQueryでSQLインジェクション対策:strings.ReplaceAllをやめてクエリパラメータを使った話

Posted at

はじめに(Introduction)

Go言語でBigQueryを操作する際、ユーザー入力など動的な値を用いてクエリを構築することがあります。
その際、SQLインジェクション対策として最初は strings.ReplaceAll でエスケープ処理を試みましたが、「これでは不十分(微妙)だ」とレビューでフィードバックを受け、最終的にBigQueryクライアントライブラリ標準のクエリパラメータへ移行しました。

本記事では、なぜ自前のエスケープ処理が微妙だったのか、そしてクエリパラメータを使うことで何が改善されたのかを、5W1Hの視点で振り返ります。


1. 当初の対応:strings.ReplaceAll によるエスケープ

最初は、以下のように文字列置換を使って、「シングルクォート '」をエスケープする方法を実装していました。

😢微妙だったコード(Before)

package main

import (
	"fmt"
	"strings"
)

func buildQuery(userName string) string {
	// ユーザー入力に含まれるシングルクォートをエスケープ(' -> \')
	// ※ SQLインジェクション対策のつもり
	safeName := strings.ReplaceAll(userName, "'", "\\'")
	
	// fmt.SprintfでSQLを組み立てる
	results := fmt.Sprintf("
      SELECT
        *
      FROM
        `my-project.dataset.users`
      WHERE
        name = '%s'", safeName)
	
	return results
}

⚠️必要最低限のソースコードになってます。

なぜこれが「微妙」だったのか

一見良さそうに見えますが、以下の点で不安要素(微妙な点)が残りました。

  1. ヌケモレの不安(Security): シングルクォート以外(バックスラッシュなど)のエッジケースを完全に網羅できているか自信が持てない。「自前実装」はセキュリティホールの温床になりやすい。
  2. 可読性の低下(Readability): fmt.Sprintf とエスケープ処理が混在し、SQLの構造が見えにくくなる。
  3. 型安全ではない(Type Safety): 全てを string として扱うため、日付型(TIMESTAMP)や数値型を扱う際にフォーマット変換の手間が発生する。

2. 改善後の対応:Query Parameters の使用

そこで、Google Cloud 公式のGoクライアントライブラリ(cloud.google.com/go/bigquery)が提供している Query Parameters(パラメータ化クエリ) を使用する方法に切り替えました。

😆 良かったコード(After)

package main

import (
	"context"
	"cloud.google.com/go/bigquery"
)

func runQuery(ctx context.Context, client *bigquery.Client, userName string) error {
	// プレースホルダ(@name)を使用したクエリ
	q := client.Query("
      SELECT
        *
      FROM
        `my-project.dataset.users`
      WHERE
        name = @name")
	
	// パラメータの設定
	q.Parameters = []bigquery.QueryParameter{
		{
			Name:  "name",
			Value: userName, // エスケープ処理はライブラリに任せる
		},
	}

	it, err := q.Read(ctx)
	if err != nil {
		return err
	}
	// ... 処理の続き
	return nil
}

3. 何が良かったのか(5W1Hで整理)

今回の移行で感じたメリットを、5W1Hのフレームワークで整理しました。

5W1H 内容 Before (ReplaceAll) After (Query Parameters)
Who 誰が対策するか 自分(開発者)
自前で置換処理を書く責任がある。
ライブラリ(Google)
公式SDKが安全に値を処理してくれる。
What 何を防ぐか 文字列置換で特定の文字だけを無効化。 クエリ構造とデータを分離することで、SQLインジェクションそのものを無効化。
When いつ処理されるか SQL文字列を生成する時点。 クエリが実行エンジンに渡される時点(またはその直前)。
Where どこで定義するか コード内の文字列操作関数の中。 bigquery.QueryParameter 構造体の中。
Why なぜ安全か 「危険な文字を消したから安全」という仮定に基づく。 「コード」と「データ」が明確に区別されるため、データがコードとして解釈される余地がない。
How どのように書くか fmt.Sprintf で文字列結合。 SQL内で @param を使い、Goの型をそのまま渡す。

特に「良かった」ポイント

  • 責務の分離: 「SQLの構文」と「ユーザー入力値」が明確に分かれるため、コードが直感的になりました。
  • Goの型システムの活用: Goの time.Time などをそのまま Value に渡せば、BigQuery側の TIMESTAMP 型などに適切にマッピングしてくれます。文字列変換の手間が省けました。

まとめ

strings.ReplaceAll によるエスケープは、手軽に実装できる反面、セキュリティの担保を開発者自身が背負うことになり、複雑な攻撃パターンに対して脆弱になりがち(=微妙)です。

BigQuery Clientを使用する場合は、提供されている Query Parameters 機能を使用することで、「より安全に(Security)」「より読みやすく(Readability)」「Goらしく(Type Safety)」 実装することができます。

私と同じ轍(てつ)は踏まないでくださいね

2
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
2
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?