SQLインジェクションって何?
Daily Blogging37日目
セキュリティ対策って大事だよね
SQLインジェクションとは
悪意のある第3者がリクエストにSQLコマンドを含ませることでDBにアクセス、操作を行いデータを不正に入手したり改ざんする攻撃のこと。
例えば、ユーザの入力情報を元にアカウント情報を取得する機能があった場合
入力値:taichi
# 入力値を元に検索する
SELECT * FROM users WHERE id = 'taichi'
こういうクエリがアプリケーション側で実行されるとする。
これが下記のような場合だとSQLインジェクションが発生する。
入力値:taichi’ or ‘1’=‘1
SELECT * FROM users WHERE id = ‘taichi’ or ‘1’=‘1’
‘1’=‘1’って常にTrueになるので、usersのレコードが全部検索条件に引っかかることになり、データが全部取得できる。
こわっ
いろんな種類があるよ
SQLインジェクションと言ってもいくつかの種類に分けられる
基本的には、入力フィールドやクエリパラメータにSQLコマンドを含めて、意図しないクエリを実行させ不正にDB操作を行う。
種類は調べれば調べるほどわからなくなってくるのでメジャーっぽそうなやつだけピックアップ
インバンド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