SQL Antipatterns ch21 SQL Injection
- SQL Injection
- Objective: Write Dynamic SQL Queries
- Antipattern: Execute Unverified Input As Code
- How to Recognize the Antipattern
- Legitimate Uses of The Antipattern
- Solutinon: Trust No One
- 英語
SQL Injection
Let users input values, but never let users input code.
Objective: Write Dynamic SQL Queries
- dynamic SQL
- アプリケーションコード上で動的にSQLクエリを組み立てる
- DBとアプリケーションとの対話
- やりたいことを実現するのは容易い
- セキュリティ的にやりたくないことを禁止するのは難しい
Antipattern: Execute Unverified Input As Code
- DELETE文を仕込むやつが有名
Accidents May Happen
The Top Web Security Threat
http://bugs.example.com/setpass?password=xyzzy&userid=123 OR TRUE
- こうなる
UPDATE accounts SET password_has = SHA2('xyzzy') WHERE account_id = 123 OR TRUE
- 全ユーザのパスワードが変更されてしまう
- SQLをパースする前に動的な部分を挿入してしまうのが本質的な危険性
The Quest for a Cure
- どう守ればいいの
- ここに挙げるものはすべてのケースで適用可能なものではない
Escaping Values
- クォーテーションをエスケープする
Query Parameters
- プリペアドステートメント
- 非常に強力な防衛策だが、万能の方策ではない
- 値のリストは入れられない
IN ( ? )
- テーブルの識別子は入れられない
FROM ?
- カラムの識別子は入れられない
ORDER BY ?
ORDER BY 'date_reported'
に展開されて意図通り動かない
- SQLのキーワードは入れられない
ORDER BY date_reported ?
にDESCを入れるとか'DESC'
になっちゃう
- 【補】
LIKE '%?%'
とかも駄目
- 値のリストは入れられない
コラム: What Was My Complete Query?
Stored Procedures
Data Access Frameworks
- フレームワーク安全神話は、SQLを文字列として書かせる限り嘘
- Practice Good Hygiene
- フレームワークを使ってSQLインジェクションを防ぐのは、歯ブラシを使って虫歯を防ぐのと似ている
- 絶えず使うこと
- 安全なSQLコードを強制するフレームワークは存在しない
- 安全な機能を提供するが、簡単に迂回できる
- 【補】Laravelのクエリビルダの
whereRaw()
とか
How to Recognize the Antipattern
Legitimate Uses of The Antipattern
- ない
- 一番危険な部分がアプリケーション全体での危険性となる
- くれぐれも戦犯にならないよう
Solutinon: Trust No One
- 単一のテクニックはない
- 適宜組み合わせて
Filter Input
- アプリケーション側で有害な文字をフィルタリングする
- 数値を期待しているところにそれ以外の文字が入ってきた場合など
コラム: Rule #31: Check the Back Seat
- 怪物映画では、化物は車の運転席の後ろに隠れているもの
- 慣れ親しんだ場所にも危険は潜んでいる
- 間接的にやられる例
- ユーザ名をSELECTで取得
- それを別のdynamic SQLに挿入
Parameterize Dynamic Values
UPDATE accounts SET password_has = SHA2('xyzzy') WHERE account_id = ?
UPDATE accounts SET password_has = SHA2('xyzzy') WHERE account_id = '123 OR TRUE'
- 比較的無害
- 1行も得られないことはあっても、間違った行が得られることはない
Quoting Dynamic Values
- クエリパラメータはふつう最善の選択
- インデックス利用の最適化まわりがうまくいかないことがある
- カーディナリティが低く、値の分布が偏っている場合など
- 例: is_activeカラム
- 99%がtrue
- 1%がfalse
- falseの行を取得する場合だけインデックスが効果的
- だがプリペアドステートメントの時点ではインデックスを使用すべきかわからない
- 上記の例では値を直接SQL文に入れたほうが良い
- その場合、よく枯れている機能を利用すること
PDO::quote()
メソッド等- セキュリティを徹底的に勉強していない限り、くれぐれも自分で作らないこと
Isolate User Input From Code
コラム: Parameterizeng an IN() Predicate
- 値のリストをクエリパラメータに渡したい場合
IN( ? )
に1,2,3,4,5,6
とかを渡すのはNGIN('1,2,3,4,5,6')
になっちゃう
- こういうときは、動的に
IN(? ? ? ? ? ?)
をプリペアしてからパラメータを渡す