【実務・中級編】最小権限の原則:データベースユーザーの権限分離 – アプリケーションセキュリティ & 安全な開発防御ガイド

「DBユーザーの権限分離」をサボるな。その横着が、システムの命取りになる。

こんにちは。現場の最前線でインシデント対応をしていると、つくづく思うことがあります。「なぜ、Webアプリの接続ユーザーに db_owner や root をあてがったのか?」という問いに対して、多くのエンジニアが「開発中、権限エラーで躓くのが面倒だったから」と答える。

ハッカーはそこを突いてくる。SQLインジェクション(SQLi)が成功した瞬間、攻撃者はあなたのWebアプリの権限を乗っ取り、そのDBサーバー上の全テーブルを奪取し、さらにはOSコマンド実行の踏み台にする。

今日は、教科書的な「最小権限の原則」を、現場で確実に実行するための「泥臭い実装と設定」について話そう。

—

1. 権限分離をしないことが、どれほどの「悪夢」を生むか

まず、現実を直視しよう。SQLiの脆弱性が一つあったとき、DB接続ユーザーの権限が適切でないと、攻撃者は以下のように動く。

— 攻撃者の思考:もし権限がフルアクセスなら…
— 1. 全テーブルをリストアップし、個人情報テーブルを特定
SELECT table_name FROM information_schema.tables;

— 2. データを全抽出して外部へ送信
SELECT FROM users INTO OUTFILE ‘/tmp/dump.txt’;

— 3. 証拠隠滅のためにテーブルを削除
DROP TABLE users;

これが SELECT と UPDATE だけしかできないユーザーであれば、被害は「データの盗難」に限定される可能性が高い。しかし、DROP や GRANT、あるいは FILE 権限(MySQLの SELECT ... INTO OUTFILE など)があれば、インシデントは「システムの全壊」へと直結する。

—

2. 権限設計の「黄金律」

DBの権限分離は、以下の3階層で考えるのが鉄則だ。

1. Read Only ユーザー: 参照のみ。検索機能などに使用。
2. App ユーザー: アプリのメイン機能用。SELECT, INSERT, UPDATE, DELETE のみ。
3. Migration ユーザー: デプロイ時のみ使用。DDL(CREATE, ALTER, DROP)を実行可能。

絶対にやってはいけないこと:

  • root や super 権限をWebアプリから使わせる。
  • 複数のデータベースを同一ユーザーでアクセスさせる。

—

3. 実践:MySQLで「最強のAppユーザー」を作る

まずは、Webアプリケーション専用のユーザーを作成しよう。ここでのポイントは、不要な権限を徹底的に削ぎ落とすことだ。

— アプリ用ユーザーを作成(IP制限も忘れずに行う)
CREATE USER ‘app_user’@’10.0.x.x’ IDENTIFIED BY ‘強固なパスワード’;

— 必要なテーブルへの最小限の権限のみを付与
— ‘my_app_db’ データベース内のテーブルに対してのみ許可
GRANT SELECT, INSERT, UPDATE, DELETE ON my_app_db. TO ‘app_user’@’10.0.x.x’;

— 不要な権限は明示的に禁止(デフォルトで付与されていないことを確認)
REVOKE ALL PRIVILEGES, GRANT OPTION FROM ‘app_user’@’10.0.x.x’;

FLUSH PRIVILEGES;

これで、攻撃者がSQLiで DROP TABLE を実行しようとしても、DBエンジン側が「Access Denied」を返し、攻撃をそこで食い止めることができる。

—

4. コードレベルでの対策(PHP/PDOの例)

権限分離と合わせて、プリペアドステートメントは絶対だ。これらを組み合わせることで、多層防御(Defense in Depth)が完成する。

PDO::ERRMODE_EXCEPTION,
// プリペアドステートメントをエミュレートせず、DBエンジンに処理させる
PDO::ATTR_EMULATE_PREPARES => false,
]);

// セキュアなクエリ実行
$stmt = $pdo->prepare(“SELECT username FROM users WHERE id = :id”);
$stmt->execute([‘id’ => $_GET[‘id’]]); // 外部入力値は必ずバインドする
$user = $stmt->fetch();

} catch (PDOException $e) {
// 本番環境では詳細なエラーメッセージを画面に出さないこと!
error_log($e->getMessage());
exit(‘システムエラーが発生しました。’);
}

—

5. チーフからのアドバイス:運用で詰まないために

「権限を絞りすぎて、機能追加のたびにDB設定を変更するのが面倒」という声が聞こえてきそうだ。そんな時は、Infrastructure as Code (IaC) を導入せよ。

TerraformやAnsibleを使って、DBのユーザー権限設定をGitで管理すれば、権限の変更もプルリクエスト経由でレビューを通せるようになる。「手動でDBをいじった結果、権限がぐちゃぐちゃになる」という、現場で一番多いインシデントの元凶を物理的に排除するんだ。

最後に、インシデントは「脆弱性」があるから起きるのではない。「守りが甘いと踏んだ場所」で起きる。今日紹介した権限分離は、攻撃者に「この家は鍵がしっかりかかっているな」と思わせるための最低限の施策だ。

明日からの開発で、DB接続設定を見直してみることを強く勧める。それが、君たちのシステムと、何より君たちの平穏な生活を守ることにつながるはずだ。

コメント

タイトルとURLをコピーしました