【実務・中級編】SQLインジェクション防御のためのデータベース権限最小化 – アプリケーションセキュリティ & 安全な開発防御ガイド

「データベースに権限をやりすぎない」という最強の防御術:SQLiを致命傷にしないための設計論

現場のエンジニア諸君、お疲れ様。今日もどこかで脆弱性が叩かれているかもしれないな。

さて、今日は「SQLインジェクション(SQLi)」の対策について話そう。教科書には「プレースホルダを使え」「入力値をサニタイズしろ」と書いてある。それは大前提として、その対策を突破された時の「最後の一線」をどう守るかについて、泥臭い話をしたい。

SQLiの真の恐怖は、アプリケーションがDBサーバーに対して「何でもできる権限」を持っていることにある。もし、Webアプリ用のDBユーザーが DROP TABLE や xp_cmdshell (SQL Serverの場合) を実行できたら? その瞬間、データは人質になり、バックアップも消し飛ぶ。

1. なぜ「権限最小化」が究極の防御なのか

攻撃者はSQLiの脆弱性を見つけると、まず UNION SELECT でテーブル構造を覗き、次に information_schema を漁り、最終的にはOSコマンドの実行やデータの全流出を試みる。

しかし、もしアプリケーションが「必要なテーブル以外触れない」「SELECTしかできない」「ストアドプロシージャしか実行できない」という状態だったらどうなるか。攻撃者がどんなに巧妙なSQLを送っても、データベース側が「お前にはその権限がない」と門前払いする。これが、防御の多層化だ。

2. 権限設計の実践:アプリケーションユーザーの「去勢」

WebアプリのDB接続ユーザーには、以下のルールを適用するのが鉄則だ。

  • DDL(DROP/ALTER/CREATE)権限は絶対不可: Webアプリが実行時にテーブル構造を変える必要は、まずない。
  • ストアドプロシージャ活用によるカプセル化: テーブルへの直接アクセスを禁じ、特定のストアドプロシージャ経由でのみ操作を許可する。これにより、SQL注入のベクトルを劇的に制限できる。

PostgreSQLでの権限付与例(SQL)

— 1. アプリ専用ロールを作成
CREATE ROLE web_app_user WITH LOGIN PASSWORD ‘strong_password’;

— 2. 特定のスキーマのみアクセスを許可
GRANT USAGE ON SCHEMA public TO web_app_user;

— 3. テーブルの直接参照を禁止し、プロシージャのみ許可
— アプリはテーブルを直接叩けない
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM web_app_user;

— 必要なプロシージャのみ実行権限を与える
GRANT EXECUTE ON FUNCTION get_user_profile(int) TO web_app_user;

3. 実装のキモ:Pythonでの安全なデータアクセス

SQLiを技術的に防ぐには、やはり「プレースホルダ(バインド変数)」が必須だ。だが、権限分離ができていれば、万が一バインドをミスしても「被害を最小限に抑える」ことができる。

Python (psycopg2) を用いたセキュアな実装例

import psycopg2

def get_user_profile(user_id):
# 接続先は必ず権限を制限されたユーザーで行う
conn = psycopg2.connect(“dbname=myapp user=web_app_user password=…”)
cur = conn.cursor()

# 悪い例: f”SELECT FROM users WHERE id = {user_id}”
# ↑絶対やるな。これがSQLiの入り口だ。

# 良い例: プレースホルダを使用する
# データベース側でクエリの構造が固定されるため、データがコマンドとして解釈されない
query = “SELECT get_user_profile(%s);”
cur.execute(query, (user_id,))

result = cur.fetchone()
cur.close()
conn.close()
return result

4. 攻撃者が諦める「見えない壁」を作る

最後に、インフラ側での防御も忘れてはならない。クラウド環境であれば、IAMやセキュリティグループでDBへのアクセス元をWebサーバーに限定するのは当然として、WAF(AWS WAF等)でSQLi特有のパターンをブロックしておく。

AWS WAFでのSQLi検出ルール(概念)

WAFは「最後の砦」だが、過信は禁物だ。あくまで「明らかな攻撃」を弾くフィルターとして使う。

  • SQL injection match conditions: UNION, SELECT, OR 1=1 といったシグネチャを監視。
  • 重要: WAFだけで守ろうとせず、バックエンドのデータベース権限が正しく設定されているかを確認する「定期的な監査スクリプト」をCI/CDに組み込むこと。

最後に:エンジニアへの提言

脆弱性を見つけるのは攻撃者の仕事だが、「攻撃されても被害を出さない」のは我々エンジニアの誇りだ。

「開発速度」を理由に root や db_owner 権限でアプリを動かすのは、今日で終わりにしよう。権限を絞れば、コードレビューで少しのミスがあっても、DB側が守ってくれる。

今日から君たちのプロジェクトのDBユーザー権限を確認してみてくれ。過剰な権限を見つけたら、それが「次期インシデント」の芽だ。今のうちに摘んでおこう。

また現場で会おう。質問があればいつでもどうぞ。

コメント

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