Closed40

Rails ActiveRecord SQLインジェクション

Koji NAKAMURAKoji NAKAMURA

Ruby on Railsには、特殊なSQL文字をフィルタするしくみが組み込まれており、「'」「"」「NULL」「改行」をエスケープします。Model.find(id)やModel.find_by_*(引数)といったクエリでは自動的にこの対策が適用されます。ただし、SQLフラグメント、特に条件フラグメント(where("..."))、connection.execute()またはModel.find_by_sql()メソッドについては手動でエスケープする必要があります。

Koji NAKAMURAKoji NAKAMURA

SQLを発行するためのI/Fを分類してみる

    1. SQLを隠蔽しているI/F e.g. User.where(name: "Joe")
    1. SQLフラグメントを指定するI/F
    • 2-1. プレースホルダーで値を割り当てる e.g. User.where("name = ?", "Joe")
    • 2-2. SQLフラグメントそのまま e.g. User.where("name = 'Joe'")

1.2-1. はエスケープ処理が確実に行われる。SQLインジェクションに対して安全。
2-2. はSQLインジェクションに対して脆弱。

Koji NAKAMURAKoji NAKAMURA

User.where("name = ?", "Joe") は静的プレースホルダなのか、動的プレースホルダなのか。おそらく後者であろうが、後で実装を確認する。

Koji NAKAMURAKoji NAKAMURA

SQLインジェクションに対する根本的解決

SQL 文の組み立ては全てプレースホルダで実装する。

1.2-1. を利用する限りは満たせる。

SQL 文の組み立てを文字列連結により行う場合は、エスケープ処理等を行うデータ
ベースエンジンの API を用いて、SQL 文のリテラルを正しく構成する。

1.2-1. を利用する限りは満たせる。

ウェブアプリケーションに渡されるパラメータに SQL 文を直接指定しない。

それはそう。

Koji NAKAMURAKoji NAKAMURA

https://rails-sqli.org/ を眺めて、SQLインジェクションのパターンを見る。SQLフラグメントを渡しているようには見えないけどSQLにそのまま文字列連結されるやつがいくつかある。

Calculate Methods

calculate(operation, column_name) のcolumn_nameはエスケープなどされずにSQLに文字列連結される。

Exists? Method

引数に文字列のみを渡す場合はエスケープされる。配列もしくはHashの場合はconditions optionとして扱われる(のでエスケープされない)

From Method

引数の値はエスケープなどされずにSQLに文字列連結される。

Group Method

引数の値はエスケープなどされずにSQLに文字列連結される。

Koji NAKAMURAKoji NAKAMURA

試しにこんなコードを書いてみて、

sql = if params[:flag] == 'on'
    "name = '#{params[:name]}'"
else
    "1 = 1"
end
Article.where(sql)

brakeman実行

Confidence: High
Category: SQL Injection
Check: SQL
Message: Possible SQL injection
Code: Article.where(("name = '#{params[:name]}'" or "1 = 1"))
File: app/controllers/articles_controller.rb
Line: 10

ちゃんとif文のtrue/falseのパス両方をチェックしてくれている。おもしろ!

このスクラップは14日前にクローズされました