データベースのセキュリティ 今日、ダイナミックなコンテンツを提供するウェブアプリケーションにおいてはデータベースは欠く事のできないコンポーネントとなっています。 そういったデータベースには重要な、そして秘密にすべき情報が格納されることになるので、それらをいかにして保護するかについて十分に考慮する必要があります。 情報を取り出したり格納するためにはデータベースに接続する必要があります。 そして適切なクエリを送信し、結果を受け取り、切断します。クエリに使用される言語は Structured Query Language (SQL) が一般的です。アタッカーがどのようにSQLに 干渉するかについて参照ください。 皆さんがお気づきの様に、PHPそれ自体は貴方のデータベースを保護することは ありません。以下のセクションはPHPスクリプトからどのようにデータベースに アクセスし操作すればいいのか、ということに関する非常に基本的な導入です。 このシンプルなルールを覚えて置いてください:それは「多重防衛」です。 より多くの箇所で、より多くの保護を行うことにより、アタッカーが攻撃に 成功して機密情報が漏洩する可能性は減っていきます。データベースと アプリケーションを正しくデザインすることで貴方の心配を取り除くことが できます。 データベースのデザイン 他人が用意した既存のものを使用するのでない限り、最初に行うのはデータベースの作成です。 データベースが作成されると、そのデータベースのオーナーは作成コマンドを 実行したユーザーになります。通常、オーナー(とスーパーユーザー)のみが そのデータベースに対して操作を行うことが出来ます。他のユーザーがデータベースを 使用するには適切な権利が与えられている必要があります。 アプリケーションはデータベースにオーナー、もしくはスーパーユーザーとして接続することは絶対にしてはなりません。 なぜならこれらのユーザーは 例えばスキーマの変更(テーブルの削除等)や全コンテンツの削除、といったあらゆるクエリーを実行することが出来るからです。 貴方が作成するアプリケーションがデータベースに対して行う操作の各方面ごとに、 操作対象となるオブジェクトに対して、出来る限り少ない権限を持った複数の ユーザーを作成した方が良いでしょう。ユーザーに対しては、最低限必要な権限のみを 与え、関係の無いデータへのアクセスを許可しないようにします。これは、 万が一侵入者がそのユーザーの権限を以ってデータベースにアクセスした際に、 アプリケーションと関係の無いデータにまでアクセスされることを防ぐためです。 データベースへの接続 更なるセキュリティのために、クライアント/サーバー間の通信においてSSLを用いた 暗号化を行った方が良いでしょう。もしくはsshを使用することも出来ます。 どちらかの手段を講じた後、トラフィックをモニタリングしてみれば ここから何らかの情報を得ることが困難だという事が分かると思います。 ストレージの暗号化 SSL/SSHによってクライアント/サーバー間で通信されるデータは保護されますが、 データベースに保存されたデータは保護されません。SSLはあくまで通信上の プロトコルなのです。 一旦アタッカーがデータベースへ(ウェブサーバーを通さずに)アクセスできてしまうと、 そこに格納されているデータ自体が暗号化されていない限り、自由に閲覧され、 使用されてしまいます。データを暗号化することによって、この脅威を減らすことが できますが、この機能をサポートしているデータベースは僅かです。 この問題への最も簡単な対応策は、まず自分専用の暗号化パッケージを作成し、 それをあなたのPHPスクリプトから使用することです。PHPOpenSSL, Sodium といった幾つかの拡張モジュールは、様々な暗号化アルゴリズムをサポート しているので役に立つでしょう。データ格納時に暗号化を行い、取得時に 復号化します。この方法についてはリファレンスを参照ください。 ハッシュ もし完全にデータを隠したい場合や、元のデータ自体は必要ない場合(つまり 表示されない場合)は、ハッシュも考慮に入れたほうが良いでしょう。 ハッシュの良く知られた使用方法は、パスワードをそのまま格納せずに、 その暗号学的ハッシュ値を格納する方法です。 PHP は password 関数を提供しており、 機密データをハッシュしたりそのハッシュを扱ったりする便利な仕組みが用意されています。 password_hash は、その時点で最も強力なアルゴリズムを使って、 与えられた文字列をハッシュします。また password_verify は、与えられたパスワードのハッシュがデータベース内のハッシュと一致するかどうかを調べます。 ハッシュされたパスワードフィールド ]]> SQLインジェクション SQLインジェクションは、 攻撃者が動的なSQLクエリを組み立てる責任があるアプリケーションコードの欠陥を突く手法です。 攻撃者は、アプリケーションの権限が必要な部分にアクセスでき、 データベースからすべての情報を引き出し、既存のデータを改ざんしたり、 危険なシステムレベルのコマンドをデータベースのホスト上で実行できてしまいます。 こうした脆弱性は、開発者が任意の入力をSQL文に結合したり、挿入したりすることで発生します。 表示するデータを分割し ... そしてスーパーユーザーを作成します。(PostgreSQLの例) 以下の例では、ユーザーからの入力が直接SQLクエリに挿入されているため、 攻撃者がデータベースの superuser アカウントを取得できてしまいます。 通常のユーザーは、$offsetURL に埋め込まれている '次へ'または'前へ'リンクをクリックします。スクリプトは、受け取った $offset が数字であることを期待します。しかしながら、 以下のような値を URL に追加して攻撃を試みるとどうなるでしょうか? このようなことが行われると、スクリプトは攻撃者にスーパーユーザー権限での アクセスを提供してしまいます。0;が正しいオフセットを 指していると同時に、クエリをそこで終端させていることに気をつけてください。 SQLパーサーにクエリの残りの部分を無視させるために開発者によく使わ れる技法として、SQLのコメント記号である--があ ります。 パスワードを取得する恐るべき手段に、サイトの検索結果のページを欺く というものがあります。攻撃者が必要な作業は、投稿された変数 の中でSQL命令で使用される際に正しく扱われていないものがあるかどう かを確かめることだけです。これらのフィルタは、通常、 SELECT 文の WHERE, ORDER BY, LIMIT 及び OFFSET 句をカスタマイズするた めに前に置かれる形で設定されます。使用するデータベースが UNION構造をサポートしている場合、 攻撃者は元のクエリに任意のテーブルからパスワードのリストを取得する クエリを追加しようとするかもしれません。 パスワードそのものではなく、セキュアなハッシュ化されたパスワードだけを保存することを強く推奨します。 記事...そして(全てのデータベースサーバーの)いくつかのパスワード のリストを表示する クエリの静的な部分は、以下のように全てのパスワードを外部にもらす別の SELECT文と組み合わせることができます。 UPDATEINSERT 文も、 データベースを攻撃するために使用されます。 パスワードのリセットから ... (全てのデータベースサーバーで)より多 くの権限を得るまで もし悪意のあるユーザーが管理者のパスワードを変更するために 値 ' or uid like'%admin%$uid に代入するか、または、より多くの権限を得 るために、単純に$pwdhehehe', trusted=100, admin='yesと設定すると、 このクエリは以下のように改謬されてしまいます。 ]]> 攻撃者がデータベースの構造に関して最低限の知識を持っていないと攻撃は成功しないのが明らかです。 しかし、その手の情報はたいてい、とても簡単に入手できます。 たとえば、コードの一部がオープンソースソフトウェアの一部のため、 公開されている可能性があります。 こうした情報は、クローズドソースの場合でも漏洩する可能性があります - エンコードされたり、難読化されたり、コンパイルされていてもです。 - さらに、自作のコードであっても、エラーメッセージを表示することで漏れてしまう可能性があるのです。 他の方法としては、ありがちなテーブルやカラムの名前を使うことが挙げられます。 たとえば、'id', 'username', 'password' カラムを持つ 'users' テーブルを使うログインフォームが挙げられます。 データベースホストのオペレーティングシステムを攻撃する (MSSQLサーバー) 恐ろしい例として、 いくつかのデータベースホストで、 オペレーティングシステムレベルのコマンドがアクセスできる方法を示します。 攻撃者が、値 a%' exec master..xp_cmdshell 'net user test testpass /ADD' --$prodに投稿した場合、 $query は以下のようになります。 MSSQLサーバーは、新規ユーザーをローカルアカウント用データベースに追 加するコマンドを含むSQL命令をバッチ実行します。 このアプリケーションがsaで実行され、 MSSQLSERVERサービスが充分な権限で実行されていた場合、攻撃者は このマシンにアクセスする権限を有することになります。 上記の例は、データベースサーバーの種類に依存しています。 しかし、他の製品に対して同様な攻撃ができないことを意味するもので はありません。使用しているデータベースが他の手段で攻撃できる可能性もあります。 SQL インジェクションで発生する面白い問題の例 この画像は xkcd から提供いただいたものです。 回避策 SQLインジェクションを回避するおすすめの方法は、 すべてのデータをプリペアドステートメント経由でバインドすることです。 パラメータ化されたクエリは、 SQLインジェクションをすべて防ぐのに十分ではありませんが、 SQL文への入力を与える一番簡単かつ安全な方法です。 WHERE, SET, VALUES 句に与えるすべての動的なデータリテラルは、 すべてプレースホルダーで置き換えなければいけません。 すべての実データは実行中にバインドされ、SQLコマンドとは別に送信されます。 パラメータのバインドは、データに対してのみ使えます。 SQLクエリの他の動的な部分については、 許される値の既知の値でフィルタしなければいけません。 PDO のプリペアドステートメントを使い、SQLインジェクションを回避する prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}"); // 値は LIKE ワイルドカードを使って与えられます $stmt->execute(["%{$productId}%"]); ?> ]]> プリペアドステートメントは PDO や MySQLi、 そして他のデータベースライブラリでも使えます。 SQLインジェクションによる攻撃は、 セキュリティを考慮して書かれていないコードを攻撃する方法です。 特にクライアント側から入力されるあらゆる種類の入力を決して信用しないでください。これは、select ボックス や hidden input フィールド、Cookie の場合も同様です。最初に示した例は、簡単なクエリが破滅をもたらしうることを示しています。 攻撃を防ぐ戦略として、以下に示すいくつかのコーディングプラクティスに従うことが挙げられます: データベースにスーパーユーザーまたはデータベースの所有者として接続しないでください。 非常に制限された権限を有するカスタマイズされたユーザーを常に使用してください。 指定された入力が期待するデータ型であることを確認してください。 PHPは、 多くの種類の入力検証用関数を有しており、 変数関連の関数 や 文字型関数 にある簡単な関数 (例: それぞれ、is_numeric, ctype_digit) や、Perl互換の正規表現のサポートまであります。 アプリケーションが数値入力を期待している場合、 ctype_digit を使ってデータを検証するか、 settype で暗黙の型変換を行うか、 sprintf で数値表現を使用することを検討してみてください。 データベースがバインド変数をサポートしていない場合は、 データベースに渡される数値以外のユーザー入力を データベース固有の文字列エスケープ関数 (e.g. mysql_real_escape_string, sqlite_escape_string など) を使ってクォートしてください。 addslashes のような汎用関数が使える場面は、 とても限定された環境に限られます。 (MySQL をシングルバイト文字セットで使っていて、かつ NO_BACKSLASH_ESCAPES を無効にしている場合など) よって、この関数は使わないほうが良いです。 正しい手段でも、そうでなくても、データベース固有の情報、特にスキーマに関する情報は出力してはいけません。 エラー出力 および エラー処理およびログ関数 も参照ください。 これらのケースにおいて、スクリプトまたはサポートされている場合はデータベース自体でクエリのログをとることが有益です。 明らかにログは破壊的な行為を防止することはできませんが、 攻撃されたアプリケーションを追跡する際には有効です。ログ自体は有益ではありませんが、含まれている情報は有益です。通常、より詳細なログをとる方が良いでしょう。