大きな整数を使用することによる脆弱性
次の記事の内容を抜粋した記事です。
bigint
を超える整数を使用すると、クエリの速度が著しく落ちて、攻撃手段に使用できる場合があるというものです。
発生条件
次の 4 つの質問の回答がYes
の場合、脆弱な可能性があります
- PostgreSQL を使用していますか?
-
bigint
範囲外の整数をサポートするプログラミング言語を使用していますか(ほとんどの動的型付けプログラミング言語はサポート)? - ユーザー入力から得られた整数値を、範囲内にあるかどうかを検証せずに受け入れてますか?
- 引用符、明示的なキャスト、またはバインドされた変数を使用せずに、クエリ内で文字通り整数値を使用していますか?
※ PostgreSQLが条件に入っているのは、次の仕様によるものです。
小数点も指数も含まない数値定数は、その値が型
integer
(32 ビット) に適合する場合、最初はinteger
型であると推定されます。それ以外の場合、その値が型bigint
(64 ビット)に適合する場合はbigint
型であるとみなされます。それ以外の場合は、numeric
型とみなされます。
準備
データベース作成
postgres@masami-L ~> createdb -U postgres sample
postgres@masami-L ~> psql -U postgres -d sample
テーブル作成
sample=# CREATE TABLE a AS SELECT * FROM generate_series(1,1000000) AS a(id);
CREATE INDEX ON a(id);
SELECT 1000000
CREATE INDEX
事象
bigint
の最大値 9223372036854775807 で 検索
sample=# EXPLAIN ANALYZE SELECT id FROM a WHERE id = 9223372036854775807;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Index Only Scan using a_id_idx on a (cost=0.42..4.44 rows=1 width=4) (actual time=0.111..0.112 rows=0 loops=1)
Index Cond: (id = '9223372036854775807'::bigint)
Heap Fetches: 0
Planning Time: 0.672 ms
Execution Time: 0.150 ms
(5 rows)
インデックススキャンが使用されます
bigint
を超える値で検索します
sample=# EXPLAIN ANALYZE SELECT id FROM a WHERE id = 9223372036854775808;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..12175.00 rows=5000 width=4) (actual time=102.700..106.299 rows=0 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on a (cost=0.00..10675.00 rows=2083 width=4) (actual time=97.138..97.138 rows=0 loops=3)
Filter: ((id)::numeric = '9223372036854775808'::numeric)
Rows Removed by Filter: 333333
Planning Time: 0.469 ms
Execution Time: 106.355 ms
(8 rows)
シーケンシャルスキャンに代わり、動作が 60 倍以上遅くなりました。
numeric で検索しているため、インデックスが効かないようです。
対策
基本的にはクライアントで対策が本筋になります。
- バインド変数を使用する
- パラメータチェックする
ActiveRecord
等いくつかのデータベースライブラリは、ブログ主の報告をもって、
対策したようですが、自前でチェックしたほうがいいでしょう
その他回避策
何らかの理由でクライアント側で対策が取れない場合、 回避策も提案されています
- 予期していない型になるパラメータはエラーにする
sample=# EXPLAIN ANALYZE SELECT id FROM a WHERE id = 9223372036854775808::bigint;
ERROR: bigint out of range
-
numeric
のインデックスを張る方法
sample=# CREATE INDEX ON a((id::numeric));
CREATE INDEX
sample=# EXPLAIN ANALYZE SELECT id FROM a WHERE id = 9223372036854775808;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on a (cost=95.17..4805.55 rows=5000 width=4) (actual time=0.108..0.109 rows=0 loops=1)
Recheck Cond: ((id)::numeric = '9223372036854775808'::numeric)
-> Bitmap Index Scan on a_id_idx1 (cost=0.00..93.92 rows=5000 width=0) (actual time=0.104..0.105 rows=0 loops=1)
Index Cond: ((id)::numeric = '9223372036854775808'::numeric)
Planning Time: 0.562 ms
Execution Time: 0.147 ms
(6 rows)
ちなみにブログ参照先の Reddit にもあるように整数以外でも当然インデックススキャンはされません。
sample=# EXPLAIN ANALYZE SELECT id FROM a WHERE id = 0.11;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..12175.00 rows=5000 width=4) (actual time=97.910..101.180 rows=0 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on a (cost=0.00..10675.00 rows=2083 width=4) (actual time=92.100..92.101 rows=0 loops=3)
Filter: ((id)::numeric = 0.11)
Rows Removed by Filter: 333333
Planning Time: 0.320 ms
Execution Time: 101.214 ms
(8 rows)