XOOPSに限らず、いろいろなPHPアプリケーションで、今でもちょくちょくSQL Injection脆弱性が出てきます。それもケアレスミスです。
もちろん、ケアレスミスは人力では根絶不可能であり、SQLを文字列として生成すること自体をやめて、パラメータ方式で呼び出すべきだ、という方が本質的な議論でしょう。
ただ、オープンソースの世界では、自分の考えを他者に押しつけることはできませんし、SQL Injectionの可能性ある「誰か」の書いたコードを一切利用しない、というのも現実的ではありません。
事実として、SQL InjectionはXSSとは比べものにならないくらい恐い攻撃です。もし私がオープンソースアプリケーションで構築された特定のサイトを攻撃するとしたら、最初に狙うのはSQL Injectionでしょう。なぜならXSSやCSRF、セッション関連の攻撃と違い、即座に直接攻撃可能だからです。(さすがにコマンドインジェクションとかRFIはない、という前提で)
そういうわけで、Protectorでは、SQL Injection対策っぽいものも入っているのですが、私自身、あまり役に立っている気がしません。攻撃者がProtectorの対策コードを読めば、明らかに抜け道だらけです。(本当に役に立ったとすれば「最後にidのつくリクエスト変数名について強制的にintval()をかける」くらいでしょうが、これも副作用が大きすぎます)
強力なSQL Injection対策ができない理由は簡単で、本当に必要なリクエストかどうか区別がつかないからです。「UNIONをUNI-ONにする」なんてのも、本当はあってはならないことです。UNIONという単語を使ったまともな投稿が勝手に書き換わってしまいます。
ただ、入口であるリクエスト層だけで判断している以上は、ここが限界となりますが、大きな傘anti-XSSシステムと同様、入口と出口の両方を押さえれば、かなり有力な手段となることにふと気がつきました。これはいけそうです。
anti-XSSでは、出口はobフィルターでした。XSSになる可能性があるリクエストがある時のみob_start()をかけて、出力にリクエストがそのまま含まれるようならXSSとして停止する。
これをanti-SQL-Injectionにあてはめると、出口は当然クエリ関数(メソッド)となります。SQL Injectionになる可能性があるリクエストがある時のみ、DBレイヤーを乗っ取って、クエリ本文(クオーテーション外)にリクエストが含まれるようならSQL Injectionとして停止する…
…とまあ、文章として書くのは簡単なのですが、SQL Injection用の出口処理は結構面倒です。SQLをちゃんとパースする必要がありますから。
また、この仕掛けをXOOPS用Protectorとして採用する場合、もう一つ問題があります。それは、現状のDBレイヤーは、途中で乗っ取ることが不可能、ということです。この点は、コア側に対応してもらう必要があるため、別エントリにしたいと思います。
ちなみに、今回私が考え出した(と思っていた)方法ですが、元ネタがあったのを思い出しました。
「本質的なSQL Injection対策のためにはDBレイヤーを乗っ取るしかない」
と、もう4年も前にJM2さんが指摘していました。流石としか言いようがありません
serialize()/unserialize()
var_export()/eval()
A)安全性
この2つのペアを一見して判るのが、後者でeval()を使う怖さです。当たり前ですが、var_export()の出力であることが保証されているテキストでなければ、アンシリアライズしてはいけません。
もっとも、unserialize()についても、ちょっと古いPHPであれば、バッファオーバー脆弱性があったわけで、シリアライズテキストがリクエスト依存、という状況はいずれにせよあり得ないでしょう。
B)速度
結果から書いてしまうと、serialize()/unserialize() の方がだいぶ速いようです。
こんな感じのベンチプログラムで検証してみました。
#!/usr/local/bin/(php-cli binaries)
<?php
function getmicrotime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$sec + (float)$usec);
}
function var_import( $data ) {
eval( '$ret='.$data.';' ) ;
return $ret ;
}
$data = ( big array ) ;
$time_start = getmicrotime();
for( $i = 0 ; $i < $_SERVER['argv'][1] ; $i ++ ) {
$serialized_data = serialize( $data ) ;
$restored_data = unserialize( $serialized_data ) ;
$serialized_data = var_export( $data , true ) ;
$restored_data = var_import( $serialized_data ) ;
}
$time_end = getmicrotime() ;
echo $time_end - $time_start , "sec. \n" ;
?>
PHPで配列やオブジェクトをシリアライズする際、一般に利用されているのはserialize()です。
しかし、serialize()されたテキスト形式が、結構やっかいです。
具体的には日本語などのマルチバイト処理と極めて相性が悪いのです。
もし、serialize()された状態で、テキストエンコーディングをEUC-JPからUTF-8に変更したりすると、もうunserialize()は効きません。フォーマットがバイト長に依存しているからです。
これだけなら、プログラミングレベルでカバーも出来るのですが、もっと厄介なのが、DBの自動エンコーディング変換とからむケースです。
たとえばXOOPSでは、セッションをDBに保存しています。そのセッションに日本語テキストをserialize()された状態で入れてしまうと、DBの自動エンコーディング変換で、テキストが元と別物になってしまうことがあります。MySQL4.1以上だと、内部的には常にUTF-8でデータを保持しています。EUC-JPのテキストに対しては、常にUTF-8への変換行われるので、EUC-JPとして取り出したとしても、実際には、
EUC-JP -> UTF-8 -> EUC-JP
という変換がかかっているのと同じです。そして、その際にどこかでバイト長がずれれば、unserialize()が効かなくなり、セッションが失われるわけです。
ここでクローズアップされるのが、PHPビルトイン関数だけで可能な、もう一つのシリアライズ方法です。
var_export()であれば、
array(
'index' => 'value' ,
)
私自身もともとPrimitiveな環境での実験が好きなのですが、今回、Webアプリケーション(というかちょっとしたCGI)をbashで書くことをいろいろと試してみました。(もちろん、Webサーバはapache)
結論を先に書くと、「そんな馬鹿なことはやめなさい」で終わりです。
perlがどれだけ素晴らしいツールであるか、身を持って知った気がします。
ただ、あえてbashで書く、というのにはそれなりの動機付けがあります。
perlだと、モジュールの有無でいろいろと動作環境問題が出がちですが、bashであれば、普通の*nixにはほぼ100%インストールされてますし、環境問題も出づらいでしょう。(もちろん、使うコマンドにもよりますが)
環境差によるハマリをさけるため、使って良いコマンドも極力、絞ります。
比較的高機能なコマンドはsedくらいでしょうか。
'perl -ne' などが使えたら相当に楽なのですが、あえてperlは一切使わずに頑張ります。
以下、bashでCGIを書くときの注意点と対策を列挙します。
・必ずHTTPレスポンスを返す
HTTPレスポンスを返さないと、500エラーとして扱われます。
ヘッダの各行は、基本的にechoで直書きします。
もちろん、ヘッダとボディは空行で区切ります。
・echo -e オプションを忘れないこと
これがないと、コードが書きづらいでしょう。
・リクエスト受取方法
GET変数は$QUERY_STRINGを分解することで得られます。POST変数は標準入力をいったん変数に受け取ってから分解します。分解はbashのパターン照合を駆使します。
query="$QUERY_STRING&"
until [ -z "$query" ] ; do
k_v=${query%%&*}
query=${query#*&}
key=${k_v%%=*}
value=${k_v#*=}
done
最近、こんな書籍を監訳しました。
HP SoftwareのBilly HoffmanとBryan Sullivan両氏による"Ajax Security" (Addison-Wesley) が原著です。
基本的には、渡邉さんの手による和訳文を読んで、日本語として判りづらいところや技術的におかしなところに突っ込みを入れる、という作業がほとんどでしたが、原著自体がなかなか面白く、楽しんで仕事ができました。
毎コミのぽち@さんに「宣伝しておきますね」と安請け合いしておきながらナニですが、ぶっちゃけ、この書籍高いです。
5,040円もします。(元が$49.99だということを考えれば、意外と安いとも言えます)
個人レベルではなかなか購入できないでしょう。
でも、職業としてWebプログラマーをやっている人なら、一度目を通しておいても損はしないだろう、と思えるレベルの書籍だと思います。
Web開発系会社の社長さん、ぜひ御社のプログラマーやテスタに買い与えてやってください
この書籍は特にJavaScript系の攻撃に詳しく、そういう落とし穴を事前に知っておくだけでも、今後のキャリアに役立つはずです。
「オレオレ詐欺(振込め詐欺)」だって、その手口を知っているのと知らないのとでは、引っかかってしまう危険性は雲泥の差でしょう。
ついでに、これまた正直に書いておくと、原著には結構突っ込みどころが多く存在してました。
ただ、それらについては、極力、訳註という形でフォローしてますので、トータルではそれなりのレベルには仕上がっていると思います。
もちろん、私のつけた訳註そのものが間違っている可能性もありますが、万一その場合は正誤表を載せる方向でご容赦ください
すでにAmazon様にも登録されていて、9/26発売だそうです。
(Amazonの方が監訳者よりよほど詳しい! )
<姑息なアフィリエイトリンク>
# ちなみに表紙は、アヤックス(AFC Ajax)のディフェンダーです
# ちゃんと急所をおさえているとこに注目!