2015年8月27日

PostgreSQL用SQLファイアーウォール「sql_firewall」リリース

先日、sql_firewallというPostgreSQLの拡張モジュール(EXTENSION)をリリースしました。 このモジュールは、PostgreSQL上で実行可能なSQLを制限することで、SQLインジェクションを防ぐことを目的としたものです。

今回はこの sql_firewall について、その仕組みと使い方を簡単にご紹介します。

■sql_fiewallの仕組み


sql_firewallには、以下の3つの動作モードがあります。
  • 学習モード(learning)
  • 警告モード(permissive)
  • 防御モード(enforcing)

学習モードではPostgreSQL上で実行可能なSQL文を学習させることができます。

アプリケーションから発行されるSQL文をすべて人間が把握するのは難しい面がありますので、試験期間やカットオーバー直後の短期間に、アプリケーションから発行されるSQL文を学習させておく、といったことができます。

学習が完了したら、警告モードないし防御モードに切り替えます。学習させたSQL以外が実行されると、警告モードの場合はSQLの実行はできるものの警告が発生します。防御モードの場合はエラーが発生してSQLの実行はできなくなります。

このようにして、本当にアプリケーションの実行に必要なSQL文だけを実行許可することによって、SQLインジェクションを防止します。

■sql_fiewallの導入方法


現在、Githubでコードを公開しており、バージョン0.8のタグ「v0_8」を打ってありますので、そちらのコードを使って導入してみます。ここではPostgreSQLはバージョン9.4系を使います。

まず、Githubからタグv0_8を指定して、ZIP形式でダウンロードして展開します。
[snaga@devvm04 tmp]$ wget https://codeload.github.com/uptimejp/sql_firewall/zip/v0_8
--2015-08-27 08:31:10--  https://codeload.github.com/uptimejp/sql_firewall/zip/v0_8
(...snip...)

2015-08-27 08:31:11 (176 KB/s) - `v0_8' saved [29537/29537]

[snaga@devvm04 tmp]$ mv v0_8 sql_firewall_v0_8.zip
[snaga@devvm04 tmp]$ unzip sql_firewall_v0_8.zip
Archive:  sql_firewall_v0_8.zip
29b77bd43ee3d234470d3ac28c220087fc72a481
   creating: sql_firewall-0_8/
  inflating: sql_firewall-0_8/Makefile
  inflating: sql_firewall-0_8/README.sql_firewall
  inflating: sql_firewall-0_8/sql_firewall--0.8.sql
  inflating: sql_firewall-0_8/sql_firewall.c
  inflating: sql_firewall-0_8/sql_firewall.control
[snaga@devvm04 tmp]$ cd sql_firewall-0_8/
[snaga@devvm04 sql_firewall-0_8]$ ls
Makefile             sql_firewall--0.8.sql  sql_firewall.control
README.sql_firewall  sql_firewall.c
[snaga@devvm04 sql_firewall-0_8]$ 
ソースの展開ができたら、通常のPostgreSQLのEXTENSIONと同様に USE_PGXS=1 を指定してビルド、インストールします。

この時、pg_configコマンドへのPATHが通っている必要があることに注意してください。(以下の例では明示的にPATHを指定しています)
[snaga@devvm04 sql_firewall-0_8]$ env USE_PGXS=1 PATH=/usr/pgsql-9.4/bin:$PATH make
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -I/usr/include/et -fpic -I. -I./ -I/usr/pgsql-9.4/include/server -I/usr/pgsql-9.4/include/internal -I/usr/include/et -D_GNU_SOURCE -I/usr/include/libxml2  -I/usr/include  -c -o sql_firewall.o sql_firewall.c
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -I/usr/include/et -fpic -shared -o sql_firewall.so sql_firewall.o -L/usr/pgsql-9.4/lib -L/usr/lib64 -Wl,--as-needed
[snaga@devvm04 sql_firewall-0_8]$ sudo env USE_PGXS=1 PATH=/usr/pgsql-9.4/bin:$PATH make install
/bin/mkdir -p '/usr/pgsql-9.4/lib'
/bin/mkdir -p '/usr/pgsql-9.4/share/extension'
/bin/mkdir -p '/usr/pgsql-9.4/share/extension'
/usr/bin/install -c -m 755  sql_firewall.so '/usr/pgsql-9.4/lib/sql_firewall.so'
/usr/bin/install -c -m 644 sql_firewall.control '/usr/pgsql-9.4/share/extension/'
/usr/bin/install -c -m 644 sql_firewall--0.8.sql '/usr/pgsql-9.4/share/extension/'
[snaga@devvm04 sql_firewall-0_8]$
インストールができたら、postgresql.confに設定を追加し、PostgreSQLを再起動をします。(sql_firewallは、そのモジュールの性質上、設定変更・動作モードの変更などを行うためにはpostmasterプロセスの再起動が必要になります。)
[snaga@devvm04 sql_firewall-0_8]$ sudo vi /var/lib/pgsql/9.4/data/postgresql.conf
postgresql.confに以下の設定を追加します。
shared_preload_libraries = 'sql_firewall'
sql_firewall.firewall = 'disabled'
postgresql.confの設定ができたらサービスの再起動を行います。
[snaga@devvm04 sql_firewall-0_8]$ sudo service postgresql-9.4 restart
Stopping postgresql-9.4 service:                           [  OK  ]
Starting postgresql-9.4 service:                           [  OK  ]
[snaga@devvm04 sql_firewall-0_8]$
最後に、データベースに対してCREATE EXTENSIONを実行して、sql_fiewallに含まれるビューやSQL関数を有効化します。
[snaga@devvm04 sql_firewall-0_8]$ psql -U postgres testdb
psql (9.4.4)
Type "help" for help.

testdb=# create extension sql_firewall;
CREATE EXTENSION
testdb=# 
これでsql_firewallを使う準備ができました。

ついでに、動作確認に使うテストテーブルも作成しておきます。
testdb=# \! cat t.sql
create table k1 (
  uid integer primary key,
  uname text not null
);

insert into k1 values (1, 'Park Gyu-ri');
insert into k1 values (2, 'Nicole Jung');
insert into k1 values (3, 'Goo Ha-ra');
insert into k1 values (4, 'Han Seung-yeon');
insert into k1 values (5, 'Kang Ji-young');

testdb=# \i t.sql
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
testdb=#

■SQLを学習させる(学習モード)


まず、PostgreSQLを学習モードに切り替えます。

postgresql.conf で sql_firewall.firewall の設定を以下のように learning に変更し、サービスを再起動します。
sql_firewall.firewall = 'learning'
この状態で、学習させたいSQL文を実行します。
testdb=# select * from k1 where uid = 1;
 uid |    uname
-----+-------------
   1 | Park Gyu-ri
(1 row)

testdb=# select * from sql_firewall.sql_firewall_statements;
 userid |  queryid  |              query              | calls
--------+-----------+---------------------------------+-------
     10 | 227323429 | select * from k1 where uid = ?; |     1
(1 row)

testdb=# \q
[snaga@devvm04 sql_firewall-0_8]$
ここでは uid カラムで指定してユーザ情報を1件取得するSQLを学習させます。

また、sql_firewall で学習させたクエリは sql_firewall.sql_firewall_statements というビューから確認することができますので、それを確認するSQLも学習させておきます。

この状態でpsqlプロンプトを抜けます。

■想定外のSQLの実行時に警告を出力する(警告モード)


次に sql_firewall を警告(permissive)モードに切り替えます。

postgresql.conf で sql_firewall.firewall パラメータを以下のように変更して PostgreSQL を再起動します。
sql_firewall.firewall = 'permissive'
この状態で、先ほどと同じSQLを実行してみます。
testdb=# select * from k1 where uid = 1;
 uid |    uname
-----+-------------
   1 | Park Gyu-ri
(1 row)

testdb=#
すると、当然ながら問題なく動作していることが分かります。

また、「uid = 1」の「1」を変更しても問題なく動作することが分かります。
testdb=# select * from k1 where uid = 2;
 uid |    uname
-----+-------------
   2 | Nicole Jung
(1 row)

testdb=# select * from k1 where uid = 3;
 uid |   uname
-----+-----------
   3 | Goo Ha-ra
(1 row)

testdb=#
一方、文法的に異なる条件を加えると警告メッセージが出ます。

ここでは、SQLインジェクションで一般的と言われている「or 1=1」のような条件を追加してみます。
testdb=# select * from k1 where uid = 3 or 1 = 1;
WARNING:  Prohibited SQL statement
 uid |     uname
-----+----------------
   1 | Park Gyu-ri
   2 | Nicole Jung
   3 | Goo Ha-ra
   4 | Han Seung-yeon
   5 | Kang Ji-young
(5 rows)

testdb=#
現時点では警告モードなのでSQL文自体は実行されるのですが、「WARNING: Prohibited SQL statement」という警告メッセージが同時に出力されます。

このように、警告モードでは学習させたSQL以外が実行されると警告メッセージが出力されます。

■想定外のSQLの実行を防ぐ(防御モード)


次に防御(enforcing)モードで動作させてみます。

sql_firewall.firewall パラメータを enforcing に切り替えて、PostgreSQLを再起動します。
sql_firewall.firewall = 'enforcing'
先ほどと同じように、uidを指定して1件レコードを取得するクエリを実行してみます。
testdb=# select * from k1 where uid = 1;
 uid |    uname
-----+-------------
   1 | Park Gyu-ri
(1 row)

testdb=# select * from k1 where uid = 2;
 uid |    uname
-----+-------------
   2 | Nicole Jung
(1 row)

testdb=# select * from k1 where uid = 3;
 uid |   uname
-----+-----------
   3 | Goo Ha-ra
(1 row)

testdb=# 
permissiveモードの時と特に動作は変わっていないことが分かります。学習させたクエリは問題なく実行できています。

それでは、次に学習させていないSQLを実行してみます。
testdb=# select * from k1 where uid = 3 or 1 = 1;
ERROR:  Prohibited SQL statement
testdb=#
permissiveモードの時は警告が表示されるだけでしたが、enforcingモードになるとエラーとなり実行することができなくなりました。

これがpermissiveモードとenforcingモードの動作の違いになります。

この時、学習させたクエリをsql_firewall_statementsビューで確認してみると、以下のクエリしか学習(=許可)されていないことが分かります。
testdb=# select * from sql_firewall.sql_firewall_statements;
 userid |  queryid  |                        query                        | calls
--------+-----------+-----------------------------------------------------+-------
     10 | 472133445 | select * from sql_firewall.sql_firewall_statements; | 1
     10 | 227323429 | select * from k1 where uid = ?;                     | 4
(2 rows)

testdb=#

■まとめ


以上、簡単ではありますが sql_firewall の仕組みと使い方をご紹介しました。

SQLインジェクションは、非常によく知られた攻撃である一方、その防御は必ずしも簡単ではなく、SQLインジェクションが原因と思われるセキュリティインシデントも後を絶ちません。

攻撃者が狙っているものがデータベースであることを考慮すると、データベース側で一括して対策をするのも一つの有効な方法なのではないかと思います。

SQLインジェクション対策で心配・苦労している方は、一度試してみてはいかがでしょうか。

では、また。

0 件のコメント:

コメントを投稿