はじめに
開発において、データベースクエリの安全性は非常に重要です。この記事では、プレースホルダーの使用意義と、固定値を扱う際にそれをどのように適用すべきかについて説明します。具体的な例として、Railsアプリケーションにおけるカスタムバリデーションのコードを見ていきましょう。
カスタムバリデーションの例
以下は、一人の顧客が未来の日付に一つの企業に対して一つの予約のみを持てるよう制限するカスタムバリデーションです。
class Reservation < ApplicationRecord
validate :unique_future_reservation_per_customer_and_company
private
def unique_future_reservation_per_customer_and_company
return if date.blank? || customer_id.blank? || company_id.blank?
future_reservations = Reservation.where(customer_id: customer_id, company_id: company_id).where('date >= ?', Date.today)
future_reservations = future_reservations.where(deleted_at: nil)
future_reservations = future_reservations.where.not(id: id) unless new_record?
if future_reservations.exists?
errors.add(:base, "You can only have one future reservation per company.")
end
end
end
このバリデーションでは、Date.today
を使用して、今日もしくはそれ以降の日付に対する予約の存在をチェックしています。
プレースホルダーの意味
SQLクエリ内でプレースホルダー(?
)を使用する主な理由は、SQLインジェクション攻撃からアプリケーションを守ることです。プレースホルダーを使うことで、SQLクエリに挿入される値が適切にエスケープされ、安全なクエリが構築されます。
固定値を扱う際のプレースホルダー使用
Date.today
のような固定値を使用する場合、SQLインジェクションの危険性は基本的にはありません。なぜなら、この値はアプリケーション内で直接生成され、外部からの不正な入力によって操作されることがないからです。
あえてプレースホルダーを用いる理由
- 一貫性: SQLクエリを構築する際に一貫してプレースホルダーを使用することで、コードの読みやすさと保守のしやすさが向上します。
- 将来的な変更への備え: 当初は固定値を使用していたコードが将来的に外部からの入力を受け取るように変更されることがあります。初めからプレースホルダーを使用しておくことで、そのような変更が容易になります。
- ベストプラクティスの遵守: SQLインジェクション攻撃を防ぐためのベストプラクティスを遵守することは、セキュリティ意識の高い開発文化を促進します。
[補足]具体的なSQLインジェクションのリスク
上記の通り、Date.todayのようにアプリケーション内で生成された値を使用する場合、SQLインジェクションのリスクはほぼありません。問題となるのは、外部からの入力を直接クエリに組み込むような場合です。例えば、以下のようなコードは危険です。
# 危険なコード例
date = params[:date] # ユーザー入力を直接使用
Reservation.where("date >= '#{date}'")
このコードでは、ユーザーがフォームを通じて送信したdateパラメータをそのままSQLクエリに組み込んでいます。攻撃者がdateパラメータに'2023-01-01'; DROP TABLE reservations;
のような値を送信した場合、意図しないSQL命令が実行される可能性があります。
まとめ
プレースホルダーの使用は、SQLインジェクション攻撃からアプリケーションを守るために使用します。現時点では使用する必要がない場合でも、一貫性や拡張性を考慮して原則としては記述する方が好ましいと考えられます。
参考記事