PHP
PDOでデータベース接続と
SQLインジェクション対策
2021/03/12
TaKo
はじめに
PHPからデータベースに接続・操作する方法について紹介
おまけでSQLインジェクション対策
手段は複数あるが、PDOでおおよそ対応可能なようす...
表1. PHPのデータベース関連のモジュール
拡張モジュール名 概要
DBA dbm形式のデータベースに対応するDB抽象化レイヤ
dbx マニュアルを読んでもよくわからなかったので割愛
ODBC マニュアルを読んでもよくわからなかったので割愛
PDO
様々なDBMSに対応
軽量で高性能なインターフェイスを提供してくれる(らしい)
*ベンダー固有のモジュールも用意されているが割愛
環境情報
表2. 環境情報
バージョン
CentOS 7.9.2009
PHP 7.4.14
MySQL 8.0.23
表3. PHPモジュール
備考
PDO DB抽象化レイヤ
pdo-mysql PDOのMySQL用ドライバ
mysqlnd クライアントライブラリ
拡張モジュールPDO
PHP Data Objects(PDO)拡張モジュールとは
•PHPからDBMSを操作できるようにするモジュール
•DBMSの種類に関わらず、(ほぼ)同じ記述で接続・操作が可能
PDOの使い方:接続
try {
$dsn = '<DBMS名>:host=<ホスト名>; dbname=<データベース名>; charset=<文字エンコード>';
$user = '<ユーザ名>';
$password = '<パスワード>';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $password, $options);
} catch (PDOException $e) {
echo '接続失敗: ' . $e->getMessage() . "n";
exit(1);
}
Data Source Name (dsn)
データベースに接続するために必要な情報
「DBMS名」と「ホスト名」は必須, データベース名は基本的には必須
ドライバオプション
必須ではないが、クエリの実行エラーをどう処理するかを指定したり、
クエリのエミュレーションをDBMS側で行うように設定可能
必要な情報をや設定を引数に指定して、PDOクラスをインスタンス化することで任意のDBMSに接続可能
一般的に$pdoまたは$dbhという変数名が使われる
接続を切るときはnullを代入する
PDOの使い方:接続
表4. DBMS, DBの情報
DBMS MySQL
ホスト localhost
ユーザ pdo_user
パスワード password
DB名 pdo_test
Table名 user
try {
$dsn = 'mysql:host=localhost; dbname=pdo_test; charset=utf8mb4';
$user = 'pdo_user';
$password = 'password';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $password, $options);
} catch (PDOException $e) {
echo '接続失敗: ' . $e->getMessage() . "n";
exit(1);
}
↑の例の場合、
localhostにあるMySQLの、pdo_testというデータベースにpdo_user/passwordで接続する。
文字エンコードはutf8mb4
ドライバオプションの設定
・「クエリ実行時エラーがあった場合例外をスローする」
・「プリペアードステートメントのエミュレーションはDBMS側で行う」
PDOの使い方:クエリの実行と結果の取得
try {
$result = $pdo->query("SELECT * FROM user");
$data['users'] = $result->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOExseption $e) {
echo "クエリ実行時エラー: " . $e->getMessage() . "n";
}
クエリが固定の場合
クエリが可変の場合
$post_id = $_POST['id'];
try {
$prepare = $pdo->prepare("SELECT * FROM user WHERE id= :id");
$prepare->bindValue('id', $post_id, PDO::PARAM_INT);
$prepare->execute();
$data['user'] = $prepare->fetch(PDO::FETCH_ASSOC);
} catch (PDOExseption $e) {
echo "クエリ実行時エラー: " . $e->getMessage() . "n";
}
query関数
クエリの実行しその結果をPDOStatement
オブジェクトとして返す
クエリの実行のみ帰り値が不要な場合はexec関数を
使用
fetchAll関数
全ての結果を配列で返す
引数でカラム名を添字にしたり特定カラムのみ取得する
ようにできる
prepare関数
実行するクエリを設定する。
ユーザ入力などでクエリを変化させたい時に利用する
bindValue関数
クエリのプレースホルダに値をバインドする
引数はプレースホルダ名、値、カラムの型
execute関数:設定されたクエリを実行
fetch関数
結果から一行取得して配列で返す
実行するたびに次のレコードを取得できる
SQLインジェクション対策
SQLインジェクション攻撃とは
ウェブサイトの入力フォームで不正なSQL文を送信して、
データベースから機密情報を盗んだり、改ざんする攻撃手法のこと
不正にログインや、Webサイトウイルスをしこまれてしまったり
最悪、サーバ乗っ取りの足掛かりに...
被害事例
‘当社が運営するWebサイト(https://www.kogensha.jp/)におきまして、不正アクセスがあり、ご登録情報が一部流
出した可能性があることが判明しました。

なお、現在のところ当該情報を悪用された被害の報告は入っておりません。’

‘ログ解析の結果、攻撃手法は、 URLの末尾のパラメータと呼ばれる箇所に不正なデータベースコマンドを混入さ
せ、本来とは異なる情報を表示させようと試みる「SQLインジェクション」と判明しました。’

引用:光言社 ポータルサイト - 当社ホームページへの不正アクセス事件のご報告とお詫び, https://www.kogensha.jp/information/detail.php?id=1612, (2020/04/16)
SQLインジェクション対策
PHP7.4 : PDOモジュールを使う場合
1. 権限を絞った専用のユーザで接続する
→万が一の時、被害を抑制するため
2. 文字エンコードを必ず指定する
→プレースホルダを正常に使用するために必要
3. 例外処理で接続・操作部分をくくる
→例外をスルーするようにオプションを設定しておく
4. クエリの組み立てには静的プレースホルダを用いる
→文字列連結でクエリを組み立てるのはNG
→動的プレースホルダはSQLインジェクションができる可能性
5. プレースホルダのバインド時に型を指定する
→データベース側でどの型になるのか指定する
SQLインジェクション対策
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $password, $options);
$id = $_POST['id']
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=" . $id . ";");
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=:id;");
$prepare->bindValue('id', $id, PDO::PARAM_INT);
または↓
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=?;");
$prepare->bindParam('1', $id, PDO::PARAM_INT);
PHP7.4 : PDOモジュールを使う場合
例外をスローする設定
クエリ設定の部分推奨の例
クエリ設定の部分悪い例
SQLインジェクション対策
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $password, $options);
$id = $_POST['id']
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=" . $id . ";");
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=:id;");
$prepare->bindValue('id', $id, PDO::PARAM_INT);
または↓
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=?;");
$prepare->bindParam('1', $id, PDO::PARAM_INT);
文字エンコードの設定を忘れずに... charset=utf8mb4
PHP7.4 : PDOモジュールを使う場合
権限を絞ったユーザを作成して、かならずパスワードを設定する
たとえば...
CREATE USER 'pdo_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON pdo_test.* to 'pdo_user'@'localhost';
SQLインジェクション対策
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $password, $options);
$id = $_POST['id']
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=" . $id . ";");
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=:id;");
$prepare->bindValue('id', $id, PDO::PARAM_INT);
または↓
$prepare = $pdo->prepare("SELECT * FROM user WHERE id=?;");
$prepare->bindParam('1', $id, PDO::PARAM_INT);
静的プレースホルダを使う設定
bindValue関数またはbindParam関数で値の型(PARAM_*)を指定してをバインド
PHP 7.4 : PDOモジュールを使う場合
クエリを文字列連結で入れるのはダメ!

PDOでデータベース接続と SQLインジェクション対策