SQL インジェクションとは何か、またどう防ぐのか
SQL インジェクション攻撃の実践的な分析、実例、および本番コードで実際に機能する具体的な対策です。
SQL インジェクションは 1990 年代後半から存在しており、今なお web アプリケーションが侵害される最も一般的な方法の一つです。バグは単純です。攻撃者が送信した入力がプレーンなデータではなく SQL コードとして解釈され、データベースが想定していない動作を実行します。
攻撃が実際にどう機能するのか
PHP でこのようなクエリを構築するログインフォームを想像してください。
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
アプリが入力をサニタイズしなければ、攻撃者はユーザー名フィールドに admin' -- と入力します。クエリは以下のようになります。
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
-- はその行以降をコメントアウトするため、パスワードチェックは実行されません。これが古典的な認証バイパスです。攻撃者は UNION SELECT を使って他のテーブルからデータを抽出したり、セミコロンでクエリをスタックして DROP TABLE users; のような破壊的な処理を実行したりします(ドライバーが複数ステートメントに対応している場合)。
ブラインド SQL インジェクションもあります。ここではアプリがクエリ結果を直接表示しません。攻撃者はタイミング(MySQL の SLEEP(5))またはブール応答(true 条件と false 条件でページの動作が異なるか)を通じて情報を推測します。脆弱なパラメータが見つかれば、sqlmap のようなツールがこの種の抽出を自動化します。
文字列連結がルートの問題である理由
この攻撃のあらゆる変形は一つのことに帰着します。コードとデータを同じ文字列内で混在させることです。データベースは正当な値と注入されたシンタックスの違いを認識できません。両者が同じチャネルで到着するからです。クォート(引用符)のエスケープは場合によっては役に立ちますが、脆弱です。異なるエンコーディング、二次インジェクション(データが一度保存され、その後安全でない形で再利用される)、およびドライバー固有の癖はすべてバイパスのパスを生み出します。エスケープはパッチであり、修正ではありません。
パラメータ化されたクエリが真の修正です
修正は SQL コードとユーザーデータをドライバーレベルで分離することです。パラメータ化されたクエリ(プリペアドステートメントとも呼ばれる)を使用します。データベースはクエリ構造を最初に受け取り、その後で値をバインドするため、ユーザー入力はクエリの意味を変更できません。
psycopg2 を使った Python では。
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
mysql2 を使った Node.js では。
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
JDBC を使った Java では。
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
パターンに注目してください。プレースホルダー(%s、?)はデータの場所を確保し、実際の値は別に渡されます。文字列連結もない、手動のエスケープも必要ありません。これは通常のアプリケーションで書くクエリの大部分で機能します。
テーブル名またはカラム名が動的な場合はどうするのか
パラメータ化は値を処理しますが、識別子は処理しません。テーブル名をパラメータとしてバインドすることはできません。アプリが動的にテーブルを選択する必要がある場合(稀で、しばしば設計的な問題を示唆する)、ユーザー入力を直接信頼するのではなく、許可された値をハードコードされたリストに対してホワイトリストします。
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
ユーザー入力から識別子名を文字列フォーマットで構築しないでください。エスケープを使用しても同じです。
クエリ自体を超えた層状の防御
パラメータ化されたクエリが主要なコントロールですが、他にもいくつか重要なことがあります。
- データベースアカウントに最小権限を設定します。 アプリの DB ユーザーが
DROP、ALTER、または無関連なスキーマへのアクセスを持つべきではありません。インジェクションが検出されても、制限された権限は損害を制限します。 - ORM はデフォルトで役に立ちます。 Django の ORM、SQLAlchemy、および Hibernate はすべて、標準的なクエリ構築メソッドを使用するときにクエリを自動的にパラメータ化します。開発者が生の SQL にドロップしたり、
.extra()/text()呼び出しで文字列補間を使用したりするときにリスクが再び現れます。これらの箇所を特に監査してください。 - 入力検証は二次的な層であり、代替ではありません。 メールフィールドがメールのように見えるかチェックするのは良い慣行ですが、インジェクションを単独では防ぎません。攻撃者は緩い検証をまだ通すクリエイティブなペイロードを見つけます。
- WAF は既知の攻撃パターンを検知できますが、 これは基盤となるコードの修正ではなく、検出層です。
自分のコードでこのバグをテストする
クエリを静的分析ツール(Python の Bandit、SQL インジェクションルールセットを使った Semgrep)から実行します。CI の一部として。手動テストでは、すべての入力フィールドに単一のクォート(')を注入し、応答に漏れる SQL エラーメッセージを見守ってください。これはしばしば、クエリがパラメータ化されていない最初の兆候です。
これについてより深く知りたければ、Korra Studio の Web Security トラックは XSS と認証バイパスと共にインジェクションをカバーし、Databases セグメントはこのクラスのバグを避けるクエリ設計パターンを詳細に説明します。
この記事は AI の支援を受けて執筆し、Korra Studio の Michal Pilch(CISSP)が確認のうえ公開しました。
これは Korra Studio ナレッジベースの 1 つのノートです。プラットフォームはすべてのトピックと 1 対 1 メンタリングをペアで提供します。
無料で始めるarrow_forward