SQL標準同様に、文字列リテラルはシングルクォートで囲み、識別子をクォートする場合は、ダブルクォートで囲むことになっています。識別子(テーブル名、列名等)しかし、マニュアルによると、SQLiteのクォーティングには例外があります。それを実例で紹介しましょぅ。a;1sqlite>続いて、以下を実行します。ところが文法違反に見えますよね。FROMの後には表の名前が続くはずです。列名だとするとそのような列は存在しないので、こちらもエラーになるはずです。SQLiteの場合、上記SQL文はエラーにならず、以下の結果となります。'a';a|1|1|1|aasqlite>これはどういうことでしょうか?その答えは、SQLiteのマニュアルにあります。引用すなわち、シングルクォートで囲まれたキーワードが、文字列リテラルが許可されていない箇所に置かれている場合、識別子として認識されます。ダブルクォートで囲まれたキーワードであるのに、該当する列名等がない場合、文字列リテラルとして認識されます。僕達の気持ちをここまでくんでくれるなんて!!というのはもちろん冗談で、こんなのは余計なお世話としか言いようがありません。開発者が間違った場合でもエラーにならず、見つけにくいバグの原因になりそうです。SQL風)や、バッククォートで囲む方式(MySQL風)もサポートしていますが、これらの場合は、自動的に文字列リテラルとして認識されることはないようです。これらを使えば...という意見もありそうですが、ISO標準にはない書き方なので、移植性がそこなわれますよね。すれば、勝手に文字列リテラルとして認識されることもないし、移植性も損なわれません。この問題に起因するセキュリティ上の問題はないか考えてみましたが、思いつきませんでした。思いついた方があれば、ぜひ教えてください。追記(20:35)この問題が原因で脆弱性となる例を考えましたので紹介します。ログイン処理で、「ログイン状態を保存」をトークンを用いて実装していたとします。SQL文は下記となります(SQLインジェクション対策はされていると想定)。一致するものがあれば、そのユーザで自動ログインするという想定です。つづりを以下のように間違えていたとします。指定するとログインできてしまうことになります。し・か・し・な・が・ら、この例では正常系がまともに動かないため、テストさえしていれば、この脆弱性が入ったままリリースされることはないでしょう...ということで、あまり現実的ではない...しかし、絶対にないとも言い切れない...脆弱性の例でした。2.1.79未満にはSQLインジェクション脆弱性があり、早急なバージョンアップを推奨する。当該脆弱性は潜在的にWordPressの内部メソッドwpdb::prepareの仕様上の問題が原因であり、他のプラグインにも類似脆弱性の残存の可能性がある。このため、この問題の影響を受けない安全な実装方針を示した。WordPressのSQLインジェクション脆弱性が報告されました。脆弱性のサマリは下記となります。脆弱性はMixin_Displayed_Gallery_Queriesクラスのget_term_ids_for_tagsメソッドにあります。当該メソッドを下記に引用します。このメソッドは、タグを$tags配列として受け取り、タグ検索のSQL文を実行します。タグ検索のSQL文のIN句は、下記の※1(赤字)にて生成しています。エスケープなどがなされていないので心配になりますが、タグ中の引用符はHTMLエスケープ(SQLエスケープではない)され、バックスラッシュはフィルタリングで除去されるので、これら記号文字によるSQLインジェクション攻撃はできません。$term_ids;}脆弱性の原因WordPressのプラグインは、一般にWordPressがフレームワークとして提供するwpdbクラスのprepareメソッドを利用してSQL文を実行します。用いる独特の構文になっています。タグとして%sを指定したら、何かおかしなことができるのではないでしょうか。指定した場合に生成されるSQL文です。wp_terms.term_idしかし、これは実行時にエラーになります。prepareメソッドからPHPのvsprintf関数が呼び出される際に、%sが3個(上記赤字)あるのに、それに対する値が一つ('ngg_tag')しかないためです。外部からのパラメータ指定で、SQL文生成のエラーになるという時点で、この箇所にバグがあることになります。何か攻撃の糸口はなんでしょうか。実は、上記に関連して、以下の記事を書いたことがあります。上記の記事の中で、%s書式が余計に出てくるためにエラーになる状況について言及しており、そのエラー回避のテクニックについて、以下のように書いています。実はこのエラーを回避する方法があります。%sの代わりに、%1$sを指定するのです。これは、書式文字列上の位置に関わらず1番目のパラメータを受けるという意味です。今度はバインドまで行われ、生成されるSQL文は下記となります。wp_terms.term_idエラーにはならない代わりに、とりたてて攻撃もできなさそうなSQL文ができてしまいました。バインド前のSQL文は下記となります。('aaa%1$%s'))これがwpdb::prepareメソッドの中で、%sをシングルクォートで囲む処理が加わります。これは、SQL文の文字列リテラルはシングルクォートで囲むルールに対応するためです。この結果、SQL文は以下のように変形されます。最後の行のみを示します。(上記赤字)に注目します。実は、これ全体で、vsprintfの書式になっています。1$は1番目のパラメータに対応するという意味、'%はパディングに%を用いるという意味です。桁指定子がないため、実際にはパティングの指定があっても何もしません。この結果、バインド結果は下記になります。シングルクォートの一つがパティング指定と解釈されたため、シングルクォートの対応が狂っています。SQLインジェクション攻撃ができそうな雰囲気が漂い始めました。それでは、いようよ攻撃です。タグとして以下の文字列を指定します。1=1#生成されるSQL文(バインド前)は下記となります。1=1#'))そして、バインド後は下記のとおりです。wp_terms.term_id赤字で示した部分が文字列リテラルをはみだし、SQL文の一部として解釈されています。SQLインジェクション攻撃の成功です。脆弱性して報告されていますが、私は、wpdbクラスの仕様に問題があるように感じました。)があってもプレースホルダとは解釈せず、文字列リテラルの一部であると解釈します。プレースホルダとは解釈されません。プレースホルダと解釈されることになります。なり、「tanaka」が文字列リテラルを「はみだす」危なっかしい状態になります。下記ではそのような現象は起こりません。user='%s'");こうなる理由は、wpdb::prepareメソッドの内部で以下の置換処理が行われているからです。文字列のプレースホルダをクォートするが、%%sはクォートしないこのあたり、いかにもアドホックで間違いが入りやすい状態と考えます。wpdb::prepareはSQL文をパースしていないわけで、同種の問題が他のプラグイン等にも潜在的に残っている可能性が高いと予想します。バージョンアップすることです。と二重にすることで対策しています。これは書式文字列攻撃としての対策で、局所的には妥当なものと考えます。まとめSucuriが発見した攻撃手法は極めて巧妙です。外部からはシングルクォートがフィルタリングされてSQL文内に指定できないところ、wpdb::prepareが%sの前後に挿入するシングルクォートを攻撃に使っています。挿入される2つのシングルクォートのうち一つを、vsprintfの書式(パディング指定)として解釈させることにより取り除いている点が巧妙です。Galleryプラグインが踏み抜いてしまったものと考えます。wpdb::prepareはどのように使えばよいか?前述のように、この問題はwpdb::prepareメソッドの仕様(あるいは実装)がイケテナイ点が潜在的な原因になっていますが、WordPressのプラグイン等を使う場合は、このメソッドを使わないわけにはいきません。プラグイン開発者はどのようにwpdb::prepareメソッドを使えばよいでしょうか?その答えは、「SQL文中に外部由来の値を混ぜない」という原則を徹底することです。そんなこと言っても、IN句の中身は可変数なのでどうすればいいのという疑問が出てきますが、Drupalが採用している方法が参考になります。Drupalでは、IN句の中身を生成する際に、可変個のプレースホルダを内部的に生成しています。それを真似ると、今回のケースでは、以下のようなSQL文を生成すればよかったことになります。%s)これに伴って、バインド値も調整することになります。このような実装にすることにより、SQL文中に外部からプレースホルダを指定させられる攻撃(一種の書式文字列攻撃)の防止を含め、SQLインジェクション脆弱性がないことを明確にできます。Galleryに対する巧妙なSQLインジェクション攻撃について説明しました。Galleryの実装に外部から%が指定される場合の考慮漏れにあります。Galleryの実装方針にも改善の余地があります。前述のように、wpdb::prepareメソッドの潜在的な問題が原因ですので、他のWordPressプラグインにも同種の問題が残っている可能性があります。今後、可能な範囲で調査してみたいところです。