PEAK XOOPS - News in englishin japanese

Archive | RSS |
  
Poster : GIJOE on 2009-01-07 04:37:49 (17760 reads)

in englishin japanese
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さんが指摘していました。流石としか言いようがありません

0 comments

Poster : GIJOE on 2008-11-12 04:08:39 (13108 reads)

in englishin japanese
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" ;
?>

あくまでCLI版での検証ですが、赤は青のだいたい3倍くらいかかってます。PHPバージョン依存も確認してみましたが、PHP4.4.xでも5.2.xでも、同じようなパターンです。

ただ、eval()だけが遅いのかと思いきや、var_export()単独でも、serialize()の3倍かかります。

もちろん、この検証で重要なのは絶対値です。具体的な数字は書けませんが、シリアライズ/アンシリアライズなんてもともと小さな処理です。そこが3倍かかったくらいで、全体の処理にはほとんど影響ないはずです。(もちろん、大量に処理するケースでは避けた方が無難かも知れません)

C)使いやすさ

var_export()の圧勝です。
エンコーディング変換によるトラブルがなくなるだけでなく、シリアライズ形式でデータをいじることも、serialize()によるものより、はるかに楽です。

D)結論

失われてもさほど問題にならないデータや、大量のデータなら serialize()
重要性の高いデータで、さほど量が大きくないなら var_export()

という形で使い分けるのが良い気がします。
unserialize()で復元できなかった時の痛さと言ったら…

E)余談

ここでいきなりXOOPSの話題になって恐縮ですが、拙作picoでは、追加フィールド機能やフォーム機能で、serialize()/unserialize()を使っていました。しかし、この結論から、今後は、var_export()/eval()に変更していきます。

0 comments

Poster : GIJOE on 2008-11-11 18:28:10 (11046 reads)

in englishin japanese
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' ,
)

のようなテキストにシリアライズするので、eval() によってアンシリアライズ可能です。

そして、このテキストであれば、バイト長問題は発生しません。シリアライズしたテキストをエンコーディング変換しても、変換された状態でアンシリアライズ可能です。

…と、ここで時間切れなので、そのメリット・デメリット論は次回に。

0 comments

Poster : GIJOE on 2008-10-08 15:32:43 (16102 reads)

in englishin japanese
私自身もともと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

こんな感じで、$keyと$valueの対が得られます…が(続く)

・bashには連想配列がない!
とにかくこれが辛いです。連想配列がないために、リクエストのkeyとvalueの対をスマートに保存する術がありません。私自身さんざん悩んだのですが、REQUEST_(key) という形の変数をリクエスト専用の文字空間として利用することで、擬似的に連想配列を実装(?)しています。

・簡単にセキュリティホールが出来る
PHPは穴を作りやすい開発言語と言われて久しいですが、bashに比べたら可愛いものだと言わざるを得ないでしょう。
なぜなら、bashのCGIでは、ちょっとミスしただけでも、簡単にコマンドインジェクション脆弱性が確定しますから。外部コマンドに対して、変数が展開されてから渡されるのか、それともクオーティングされたまま渡されるのか、ちゃんと把握しないと、それだけで即死です。

・HTMLエスケープするスマートな手段がない
これも相当に困ります。bashの関数は、戻り値を文字列とすることが難しいので、文字列を変換する関数が事実上作れません。
これについても、良い方法が思い浮かばなかったので、変数に '"&<> が含まれないようにする、という消極的な手段を取りました。
リクエストを受け取る時に、[a-zA-Z0-9._-] 以外のキャラクターを全部消す、という処理を共通にかけています。
逆に言えば、複雑なテキストを受け取る可能性があるWebアプリケーションはbashで作ってはいけない、ということでしょう。


…と、ここまで読む限りでは、すべてが無駄な努力のように思えますが、実はbashでCGIを作ることで、PHPアプリケーションのインストールやアップデートがとても簡単になるのです。sshの使えない共有型レンタルサーバで、大量のファイルをFTPでアップロードするのに四苦八苦していた人には朗報になるはずです。

次回を待て!


Poster : GIJOE on 2008-09-12 17:23:54 (9883 reads)

in englishin japanese
最近、こんな書籍を監訳しました。


HP SoftwareのBilly HoffmanとBryan Sullivan両氏による"Ajax Security" (Addison-Wesley) が原著です。

基本的には、渡邉さんの手による和訳文を読んで、日本語として判りづらいところや技術的におかしなところに突っ込みを入れる、という作業がほとんどでしたが、原著自体がなかなか面白く、楽しんで仕事ができました。

毎コミのぽち@さんに「宣伝しておきますね」と安請け合いしておきながらナニですが、ぶっちゃけ、この書籍高いです。
5,040円もします。(元が$49.99だということを考えれば、意外と安いとも言えます)
個人レベルではなかなか購入できないでしょう。

でも、職業としてWebプログラマーをやっている人なら、一度目を通しておいても損はしないだろう、と思えるレベルの書籍だと思います。
Web開発系会社の社長さん、ぜひ御社のプログラマーやテスタに買い与えてやってください

この書籍は特にJavaScript系の攻撃に詳しく、そういう落とし穴を事前に知っておくだけでも、今後のキャリアに役立つはずです。
「オレオレ詐欺(振込め詐欺)」だって、その手口を知っているのと知らないのとでは、引っかかってしまう危険性は雲泥の差でしょう。

ついでに、これまた正直に書いておくと、原著には結構突っ込みどころが多く存在してました。
ただ、それらについては、極力、訳註という形でフォローしてますので、トータルではそれなりのレベルには仕上がっていると思います。
もちろん、私のつけた訳註そのものが間違っている可能性もありますが、万一その場合は正誤表を載せる方向でご容赦ください

すでにAmazon様にも登録されていて、9/26発売だそうです。
(Amazonの方が監訳者よりよほど詳しい!
<姑息なアフィリエイトリンク>

# ちなみに表紙は、アヤックス(AFC Ajax)のディフェンダーです
# ちゃんと急所をおさえているとこに注目!

0 comments

(1) 2 3 4 ... 6 »
Login
Username or e-mail:

Password:

Remember Me

Lost Password?

Register now!