📑

SQLインジェクションって何?

2025/01/27に公開

Daily Blogging37日目

セキュリティ対策って大事だよね

SQLインジェクションとは

悪意のある第3者がリクエストにSQLコマンドを含ませることでDBにアクセス、操作を行いデータを不正に入手したり改ざんする攻撃のこと。

例えば、ユーザの入力情報を元にアカウント情報を取得する機能があった場合
入力値:taichi

# 入力値を元に検索する
SELECT * FROM users WHERE id = 'taichi'

こういうクエリがアプリケーション側で実行されるとする。
これが下記のような場合だとSQLインジェクションが発生する。
入力値:taichi’ or ‘1’=‘1

SELECT * FROM users WHERE id = ‘taichi’ or1=1

‘1’=‘1’って常にTrueになるので、usersのレコードが全部検索条件に引っかかることになり、データが全部取得できる。
こわっ

いろんな種類があるよ

SQLインジェクションと言ってもいくつかの種類に分けられる
基本的には、入力フィールドやクエリパラメータにSQLコマンドを含めて、意図しないクエリを実行させ不正にDB操作を行う。

種類は調べれば調べるほどわからなくなってくるのでメジャーっぽそうなやつだけピックアップ
https://www.acunetix.com/websitesecurity/sql-injection2/

インバンドSQLi

一般的なやつ
さっきのtaichiの例もこれに該当する。

エラーベースSQLi

DBから直接的なデータを得るんじゃなくて、クエリを実行して出たエラーメッセージの情報を利用する。
エラーメッセージからDBの構成情報を入手して攻撃に繋げる

こういうクエリを実行させると、エラーが発生しそのメッセージにDBのバージョンが表示されたりする。

SELECT id, username FROM users WHERE username = '' OR updatexml(null, concat(0x3a, version()), null) -- ;

ユニオンSQLi

UNIONコマンドを使用した攻撃。
UNIONコマンドは2つのselect文を実行できるので、アプリケーション側で実行するselect文に加えて、攻撃者が欲しい情報を別のselect文で入手できるようにする。
UNIONは元のselect文とカラム数を同じにする必要があるので、何回かクエリを実行しカラム数を特定する。

最終的にこういうクエリを実行させる

SELECT id, username, email FROM users WHERE username = '' 
UNION SELECT null, database(), version() -- ;

これでDBのバージョンとか取れる。

ブラインドSQLi

DBからの情報じゃなくて、アプリケーションの応答、反応に応じて情報を少しずつ入手していく手法
ちょっとずつ情報を集めるのでめっちゃ時間かかる=めっちゃ攻撃してくる

Boolean-based

例えば下記のクエリを実行して、問題なくリクエストが通ればパスワードの一文字目が判明する

# パスワードの一文字目が「A」かどうか
SELECT * FROM users WHERE username = 'admin' AND ASCII(SUBSTRING(password, 1, 1)) = 65;

Time-based

5秒かかればパスワードの1文字目が「A」ということがわかる

SELECT * FROM users WHERE username = 'admin' AND password = 'user_input' OR IF(ASCII(SUBSTRING(password, 1, 1);) = 65, SLEEP(5), 0) --

対策

不正なクエリを受け付けないようにすることが大事
Railsの場合はActiveRecordを適切に使っていれば、サニタイズもされるので特別な対応はそんなに必要ない

プレースホルダーの使用

# 良い書き方:複数のパラメータを安全に使う
age = params[:age]
name = params[:name]
users = User.where("age = ? AND name = ?", age, name)

# 良い例 
User.where(username: params[:username])

# 危険な書き方:ユーザー入力をそのまま埋め込む
name = params[:name]
query = "SELECT * FROM users WHERE name = '#{name}'"
user = ActiveRecord::Base.connection.execute(query)

入力値のサニタイズ

ActiveRecordでもサニタイズされないことがあるので注意。
find_by_sqlなどで生のSQLをかくとサニタイズされないので、明示的にサニタイズする。

# SQL クエリを作成
query = "SELECT * FROM users WHERE username = ?"

# sanitize_sql_arrayを使ってユーザー入力をサニタイズ
sanitized_query = ActiveRecord::Base.send(:sanitize_sql_array, [query, params[:search_term]])

# サニタイズしたクエリを使ってデータを取得
results = User.find_by_sql(sanitized_query)

# 結果を表示
results.each do |user|
  puts user.username
end

※プレースホルダーでもいいよ

results = User.find_by_sql(["SELECT * FROM users WHERE username = ?", params[:search_term]])

LIKEにはご注意を

LIKEは通常のサニタイズでは不十分
「%%」みたいなワイルドカードのサニタイズができないので、LIKE用のサニタイズメソッドを使用する

User.where('name LIKE ?', ActiveRecord::Base.sanitize_sql_like(params[:name].to_s) + "%")

検知

SQLインジェクションの危険性があるコードを事前に検知できるよ嬉しい
RailsだとBrakemanっていうgemがある。
SQLインジェクションだけじゃなくて、他のセキュリティの観点でもコードをチェックしてくれる。
結果はcsvとかhtml形式で出力できる
→https://brakemanscanner.org/docs/options/ja.html
gem入れてコマンド実行するだけで解析できるので簡単
html出力だと、該当箇所のファイルと行数まで出してくれるのでわかりやすい

コード上だけの対策では防ぎきれないので、WAFでも必要な設定をしてSQLインジェクション対策した方が良い

まとめ

Railsありがとう

Discussion