1
0
mirror of https://github.com/php/doc-ja.git synced 2026-03-23 22:52:11 +01:00
Files
archived-doc-ja/security/database.xml
2026-02-24 23:42:11 +09:00

467 lines
24 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: takagi Status: ready -->
<!-- CREDITS: hirokawa,shimooka,mumumu -->
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>データベースのセキュリティ</title>
<simpara>
今日、ダイナミックなコンテンツを提供するウェブアプリケーションにおいてはデータベースは欠く事のできないコンポーネントとなっています。
そういったデータベースには重要な、そして秘密にすべき情報が格納されることになるので、それらをいかにして保護するかについて十分に考慮する必要があります。
</simpara>
<simpara>
情報を取り出したり格納するためにはデータベースに接続する必要があります。
そして適切なクエリを送信し、結果を受け取り、切断します。クエリに使用される言語は Structured Query Language (SQL) が一般的です。アタッカーがどのように<link linkend="security.database.sql-injection">SQLに
干渉する</link>かについて参照ください。
</simpara>
<simpara>
皆さんがお気づきの様に、<acronym>PHP</acronym>それ自体は貴方のデータベースを保護することは
ありません。以下のセクションは<acronym>PHP</acronym>スクリプトからどのようにデータベースに
アクセスし操作すればいいのか、ということに関する非常に基本的な導入です。
</simpara>
<simpara>
このシンプルなルールを覚えて置いてください:それは「多重防衛」です。
より多くの箇所で、より多くの保護を行うことにより、アタッカーが攻撃に
成功して機密情報が漏洩する可能性は減っていきます。データベースと
アプリケーションを正しくデザインすることで貴方の心配を取り除くことが
できます。
</simpara>
<sect1 xml:id="security.database.design">
<title>データベースのデザイン</title>
<simpara>
他人が用意した既存のものを使用するのでない限り、最初に行うのはデータベースの作成です。
データベースが作成されると、そのデータベースのオーナーは作成コマンドを
実行したユーザーになります。通常、オーナー(とスーパーユーザー)のみが
そのデータベースに対して操作を行うことが出来ます。他のユーザーがデータベースを
使用するには適切な権利が与えられている必要があります。
</simpara>
<simpara>
アプリケーションはデータベースにオーナー、もしくはスーパーユーザーとして接続することは絶対にしてはなりません。
なぜならこれらのユーザーは
例えばスキーマの変更(テーブルの削除等)や全コンテンツの削除、といったあらゆるクエリーを実行することが出来るからです。
</simpara>
<simpara>
貴方が作成するアプリケーションがデータベースに対して行う操作の各方面ごとに、
操作対象となるオブジェクトに対して、出来る限り少ない権限を持った複数の
ユーザーを作成した方が良いでしょう。ユーザーに対しては、最低限必要な権限のみを
与え、関係の無いデータへのアクセスを許可しないようにします。これは、
万が一侵入者がそのユーザーの権限を以ってデータベースにアクセスした際に、
アプリケーションと関係の無いデータにまでアクセスされることを防ぐためです。
</simpara>
</sect1>
<sect1 xml:id="security.database.connection">
<title>データベースへの接続</title>
<simpara>
更なるセキュリティのために、クライアント/サーバー間の通信においてSSLを用いた
暗号化を行った方が良いでしょう。もしくはsshを使用することも出来ます。
どちらかの手段を講じた後、トラフィックをモニタリングしてみれば
ここから何らかの情報を得ることが困難だという事が分かると思います。
</simpara>
<!--simpara>
If your database server native SSL support, consider to use <link
linkend="ref.openssl">OpenSSL functions</link> in communication between
PHP and database via SSL.
</simpara-->
</sect1>
<sect1 xml:id="security.database.storage">
<title>ストレージの暗号化</title>
<simpara>
SSL/SSHによってクライアント/サーバー間で通信されるデータは保護されますが、
データベースに保存されたデータは保護されません。SSLはあくまで通信上の
プロトコルなのです。
</simpara>
<simpara>
一旦アタッカーがデータベースへ(ウェブサーバーを通さずに)アクセスできてしまうと、
そこに格納されているデータ自体が暗号化されていない限り、自由に閲覧され、
使用されてしまいます。データを暗号化することによって、この脅威を減らすことが
できますが、この機能をサポートしているデータベースは僅かです。
</simpara>
<simpara>
この問題への最も簡単な対応策は、まず自分専用の暗号化パッケージを作成し、
それをあなたの<acronym>PHP</acronym>スクリプトから使用することです。<acronym>PHP</acronym><link
linkend="book.openssl">OpenSSL</link>, <link linkend="book.sodium">Sodium</link>
といった幾つかの拡張モジュールは、様々な暗号化アルゴリズムをサポート
しているので役に立つでしょう。データ格納時に暗号化を行い、取得時に
復号化します。この方法についてはリファレンスを参照ください。
</simpara>
<sect2 xml:id="security.database.storage.hashing">
<title>ハッシュ</title>
<simpara>
もし完全にデータを隠したい場合や、元のデータ自体は必要ない場合(つまり
表示されない場合)は、ハッシュも考慮に入れたほうが良いでしょう。
ハッシュの良く知られた使用方法は、パスワードをそのまま格納せずに、
その暗号学的ハッシュ値を格納する方法です。
</simpara>
<simpara>
PHP は <link linkend="ref.password">password</link> 関数を提供しており、
機密データをハッシュしたりそのハッシュを扱ったりする便利な仕組みが用意されています。
</simpara>
<simpara>
<function>password_hash</function> は、その時点で最も強力なアルゴリズムを使って、
与えられた文字列をハッシュします。また <function>password_verify</function>
は、与えられたパスワードのハッシュがデータベース内のハッシュと一致するかどうかを調べます。
</simpara>
<example>
<title>ハッシュされたパスワードフィールド</title>
<programlisting role="php">
<![CDATA[
<?php
// ハッシュされたパスワードを格納する
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
pg_escape_string($username),
password_hash($password, PASSWORD_DEFAULT));
$result = pg_query($connection, $query);
// パスワードが正しいかどうか問い合わせる
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
pg_escape_string($username));
$row = pg_fetch_assoc(pg_query($connection, $query));
if ($row && password_verify($password, $row['pwd'])) {
echo 'Welcome, ' . htmlspecialchars($username) . '!';
} else {
echo 'Authentication failed for ' . htmlspecialchars($username) . '.';
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<sect1 xml:id="security.database.sql-injection">
<title>SQLインジェクション</title>
<simpara>
SQLインジェクションは、
攻撃者が動的なSQLクエリを組み立てる責任があるアプリケーションコードの欠陥を突く手法です。
攻撃者は、アプリケーションの権限が必要な部分にアクセスでき、
データベースからすべての情報を引き出し、既存のデータを改ざんしたり、
危険なシステムレベルのコマンドをデータベースのホスト上で実行できてしまいます。
こうした脆弱性は、開発者が任意の入力をSQL文に結合したり、挿入したりすることで発生します。
</simpara>
<para>
<example>
<title>
表示するデータを分割し ... そしてスーパーユーザーを作成します。(PostgreSQLの例)
</title>
<simpara>
以下の例では、ユーザーからの入力が直接SQLクエリに挿入されているため、
攻撃者がデータベースの superuser アカウントを取得できてしまいます。
</simpara>
<programlisting role="php">
<![CDATA[
$offset = $_GET['offset']; // 入力チェックが行われていません!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
]]>
</programlisting>
</example>
通常のユーザーは、<varname>$offset</varname><acronym>URL</acronym> に埋め込まれている
'次へ'または'前へ'リンクをクリックします。スクリプトは、受け取った
<varname>$offset</varname> が数字であることを期待します。しかしながら、
以下のような値を <acronym>URL</acronym> に追加して攻撃を試みるとどうなるでしょうか?
<informalexample>
<programlisting>
<![CDATA[
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
]]>
</programlisting>
</informalexample>
このようなことが行われると、スクリプトは攻撃者にスーパーユーザー権限での
アクセスを提供してしまいます。<literal>0;</literal>が正しいオフセットを
指していると同時に、クエリをそこで終端させていることに気をつけてください。
</para>
<note>
<para>
SQLパーサーにクエリの残りの部分を無視させるために開発者によく使わ
れる技法として、SQLのコメント記号である<literal>--</literal>があ
ります。
</para>
</note>
<para>
パスワードを取得する恐るべき手段に、サイトの検索結果のページを欺く
というものがあります。攻撃者が必要な作業は、投稿された変数
の中でSQL命令で使用される際に正しく扱われていないものがあるかどう
かを確かめることだけです。これらのフィルタは、通常、
<literal>SELECT</literal> 文の <literal>WHERE, ORDER BY,
LIMIT</literal> 及び <literal>OFFSET</literal> 句をカスタマイズするた
めに前に置かれる形で設定されます。使用するデータベースが
<literal>UNION</literal>構造をサポートしている場合、
攻撃者は元のクエリに任意のテーブルからパスワードのリストを取得する
クエリを追加しようとするかもしれません。
パスワードそのものではなく、セキュアなハッシュ化されたパスワードだけを保存することを強く推奨します。
<example>
<title>
記事...そして(全てのデータベースサーバーの)いくつかのパスワード
のリストを表示する
</title>
<programlisting role="php">
<![CDATA[
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
]]>
</programlisting>
</example>
クエリの静的な部分は、以下のように全てのパスワードを外部にもらす別の
<literal>SELECT</literal>文と組み合わせることができます。
<informalexample>
<programlisting>
<![CDATA[
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
]]>
</programlisting>
</informalexample>
</para>
<para>
<literal>UPDATE</literal><literal>INSERT</literal> 文も、
データベースを攻撃するために使用されます。
<example>
<title>
パスワードのリセットから ... (全てのデータベースサーバーで)より多
くの権限を得るまで
</title>
<programlisting role="php">
<![CDATA[
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
]]>
</programlisting>
</example>
もし悪意のあるユーザーが管理者のパスワードを変更するために
<literal>' or uid like'%admin%</literal>
<varname>$uid</varname> に代入するか、または、より多くの権限を得
るために、単純に<varname>$pwd</varname>
<literal>hehehe', trusted=100, admin='yes</literal>と設定すると、
このクエリは以下のように改謬されてしまいます。
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
]]>
</programlisting>
</informalexample>
</para>
<simpara>
攻撃者がデータベースの構造に関して最低限の知識を持っていないと攻撃は成功しないのが明らかです。
しかし、その手の情報はたいてい、とても簡単に入手できます。
たとえば、コードの一部がオープンソースソフトウェアの一部のため、
公開されている可能性があります。
こうした情報は、クローズドソースの場合でも漏洩する可能性があります -
エンコードされたり、難読化されたり、コンパイルされていてもです。 -
さらに、自作のコードであっても、エラーメッセージを表示することで漏れてしまう可能性があるのです。
他の方法としては、ありがちなテーブルやカラムの名前を使うことが挙げられます。
たとえば、'id', 'username', 'password' カラムを持つ 'users' テーブルを使うログインフォームが挙げられます。
</simpara>
<para>
<example>
<title>データベースホストのオペレーティングシステムを攻撃する
(MSSQLサーバー)</title>
<simpara>
恐ろしい例として、
いくつかのデータベースホストで、
オペレーティングシステムレベルのコマンドがアクセスできる方法を示します。
</simpara>
<programlisting role="php">
<![CDATA[
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
]]>
</programlisting>
</example>
攻撃者が、値
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
<varname>$prod</varname>に投稿した場合、
<varname>$query</varname> は以下のようになります。
<informalexample>
<programlisting role="php">
<![CDATA[
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$result = mssql_query($query);
]]>
</programlisting>
</informalexample>
MSSQLサーバーは、新規ユーザーをローカルアカウント用データベースに追
加するコマンドを含むSQL命令をバッチ実行します。
このアプリケーションが<literal>sa</literal>で実行され、
MSSQLSERVERサービスが充分な権限で実行されていた場合、攻撃者は
このマシンにアクセスする権限を有することになります。
</para>
<note>
<para>
上記の例は、データベースサーバーの種類に依存しています。
しかし、他の製品に対して同様な攻撃ができないことを意味するもので
はありません。使用しているデータベースが他の手段で攻撃できる可能性もあります。
</para>
</note>
<para>
<mediaobject>
<alt>SQL インジェクションで発生する面白い問題の例</alt>
<imageobject>
<imagedata fileref="ja/security/figures/xkcd-bobby-tables.png" format="PNG"/>
</imageobject>
<caption>
<simpara>
この画像は <link xlink:href="&url.xkcd;327">xkcd</link> から提供いただいたものです。
</simpara>
</caption>
</mediaobject>
</para>
<sect2 xml:id="security.database.avoiding">
<title>回避策</title>
<para>
SQLインジェクションを回避するおすすめの方法は、
すべてのデータをプリペアドステートメント経由でバインドすることです。
パラメータ化されたクエリは、
SQLインジェクションをすべて防ぐのに十分ではありませんが、
SQL文への入力を与える一番簡単かつ安全な方法です。
<literal>WHERE</literal>,
<literal>SET</literal>, <literal>VALUES</literal>
句に与えるすべての動的なデータリテラルは、
すべてプレースホルダーで置き換えなければいけません。
すべての実データは実行中にバインドされ、SQLコマンドとは別に送信されます。
</para>
<para>
パラメータのバインドは、データに対してのみ使えます。
SQLクエリの他の動的な部分については、
許される値の既知の値でフィルタしなければいけません。
</para>
<para>
<example>
<title>PDO のプリペアドステートメントを使い、SQLインジェクションを回避する</title>
<programlisting role="php">
<![CDATA[
<?php
// SQL の動的な部分は、期待する値になるように検証されています
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// SQL はプレースホルダを使って準備します。
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// 値は LIKE ワイルドカードを使って与えられます
$stmt->execute(["%{$productId}%"]);
?>
]]>
</programlisting>
</example>
</para>
<simpara>
プリペアドステートメントは
<link linkend="pdo.prepared-statements">PDO</link>
<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link>
そして他のデータベースライブラリでも使えます。
</simpara>
<simpara>
SQLインジェクションによる攻撃は、
セキュリティを考慮して書かれていないコードを攻撃する方法です。
特にクライアント側から入力されるあらゆる種類の入力を決して信用しないでください。これは、select ボックス や hidden input フィールド、Cookie の場合も同様です。最初に示した例は、簡単なクエリが破滅をもたらしうることを示しています。
</simpara>
<para>
攻撃を防ぐ戦略として、以下に示すいくつかのコーディングプラクティスに従うことが挙げられます:
<itemizedlist>
<listitem>
<simpara>
データベースにスーパーユーザーまたはデータベースの所有者として接続しないでください。
非常に制限された権限を有するカスタマイズされたユーザーを常に使用してください。
</simpara>
</listitem>
<listitem>
<simpara>
指定された入力が期待するデータ型であることを確認してください。
<acronym>PHP</acronym>は、
多くの種類の入力検証用関数を有しており、
<link linkend="ref.var">変数関連の関数</link>
<link linkend="ref.ctype">文字型関数</link> にある簡単な関数
(例: それぞれ、<function>is_numeric</function>, <function>ctype_digit</function>) や、<link linkend="ref.pcre">Perl互換の正規表現</link>のサポートまであります。
</simpara>
</listitem>
<listitem>
<simpara>
アプリケーションが数値入力を期待している場合、
<function>ctype_digit</function> を使ってデータを検証するか、
<function>settype</function> で暗黙の型変換を行うか、
<function>sprintf</function> で数値表現を使用することを検討してみてください。
</simpara>
</listitem>
<listitem>
<simpara>
データベースがバインド変数をサポートしていない場合は、
データベースに渡される数値以外のユーザー入力を
データベース固有の文字列エスケープ関数
(e.g.
<function>mysql_real_escape_string</function>,
<function>sqlite_escape_string</function> など)
を使ってクォートしてください。
<function>addslashes</function> のような汎用関数が使える場面は、
とても限定された環境に限られます。
(MySQL をシングルバイト文字セットで使っていて、かつ <varname>NO_BACKSLASH_ESCAPES</varname> を無効にしている場合など)
よって、この関数は使わないほうが良いです。
</simpara>
</listitem>
<listitem>
<simpara>
正しい手段でも、そうでなくても、データベース固有の情報、特にスキーマに関する情報は出力してはいけません。
<link
linkend="security.errors">エラー出力</link> および <link
linkend="ref.errorfunc">エラー処理およびログ関数</link>
も参照ください。
</simpara>
</listitem>
</itemizedlist>
</para>
<simpara>
これらのケースにおいて、スクリプトまたはサポートされている場合はデータベース自体でクエリのログをとることが有益です。
明らかにログは破壊的な行為を防止することはできませんが、
攻撃されたアプリケーションを追跡する際には有効です。ログ自体は有益ではありませんが、含まれている情報は有益です。通常、より詳細なログをとる方が良いでしょう。
</simpara>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->