SQL データベースはパフォーマンスが高く、瞬時に破壊されても安全だと思いますか? まあ、SQL インジェクションは同意しません!
はい、私たちが話しているのは即時破壊です。なぜなら、この記事を「セキュリティの強化」や「悪意のあるアクセスの防止」などのよくある不適切な用語で始めたくないからです。 SQL インジェクションは、本の中で非常に古いトリックであるため、すべての開発者が SQL インジェクションについてよく知っており、それを防ぐ方法をよく知っています。 彼らが失敗したときのその1つの奇妙な時間を除いて、結果は壊滅的なものになる可能性があります.
SQL インジェクションが何であるかを既に知っている場合は、この記事の後半まで読み飛ばしてください。 しかし、Web 開発の分野に足を踏み入れたばかりで、より上級の役割を担うことを夢見ている人には、いくつかの紹介が必要です。
SQL インジェクションとは何ですか?
SQL インジェクションを理解する鍵は、その名前にあります。SQL + インジェクションです。 ここでの「注射」という言葉には、医学的な意味合いはありませんが、動詞「注射する」の用法です。 これら 2 つの単語を組み合わせると、SQL を Web アプリケーションに組み込むという考えが伝わります。
SQL を Web アプリケーションに入れる。 . . うーん。 . . とにかくそれが私たちがしていることではありませんか? はい。ただし、攻撃者がデータベースを操作することは望ましくありません。 例を使ってそれを理解しましょう。
たとえば、ローカルの e コマース ストア用に典型的な PHP Web サイトを構築しているとします。そのため、次のようなお問い合わせフォームを追加することにします。
<form action="record_message.php" method="POST"> <label>Your name</label> <input type="text" name="name"> <label>Your message</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Send"> </form>
そして、ファイル send_message.php がすべてをデータベースに保存して、ストアのオーナーが後でユーザーのメッセージを読めるようにすると仮定しましょう。 次のようなコードが含まれる場合があります。
<?php $name = $_POST['name']; $message = $_POST['message']; // check if this user already has a message mysqli_query($conn, "SELECT * from messages where name = $name"); // Other code here
したがって、最初に、このユーザーに未読のメッセージがあるかどうかを確認しようとしています。 クエリ SELECT * from messages where name = $name は十分に単純に思えますよね?
違う!
無実の私たちは、データベースの即時破壊への扉を開きました。 これが発生するには、攻撃者は次の条件を満たしている必要があります。
- アプリケーションが SQL データベースで実行されている (現在、ほとんどすべてのアプリケーションが実行されています)
- 現在のデータベース接続には、データベースに対する「編集」および「削除」権限があります
- 重要なテーブルの名前は推測できます
3 番目のポイントは、攻撃者があなたが e コマース ストアを運営していることを知ったので、注文データを orders テーブルに保存している可能性が非常に高いことを意味します。 これらすべてを備えた攻撃者が行う必要があるのは、これを名前として提供することだけです。
ジョー; 注文を切り捨てる;? かしこまりました! PHP スクリプトによって実行されると、クエリがどのようになるかを見てみましょう。
SELECT * FROM メッセージ WHERE name = Joe; 注文を切り捨てます。
さて、クエリの最初の部分には構文エラーがあります (「Joe」を引用符で囲んでいません) が、セミコロンにより、MySQL エンジンは新しいものの解釈を開始するように強制されます。 そんなこんなで、一気に注文履歴が消えてしまいました!
SQL インジェクションがどのように機能するかがわかったので、今度はそれを止める方法を見てみましょう。 SQL インジェクションを成功させるために満たす必要がある 2 つの条件は次のとおりです。
PHP での SQL インジェクションの防止
さて、データベース接続、クエリ、およびユーザー入力が生活の一部であることを考えると、SQL インジェクションをどのように防ぐことができるでしょうか? ありがたいことに、これは非常に簡単で、2 つの方法があります。1) ユーザー入力をサニタイズする方法と、2) 準備済みステートメントを使用する方法です。
ユーザー入力のサニタイズ
古いバージョンの PHP (5.5 以下、これは共有ホスティングでよく発生します) を使用している場合は、すべてのユーザー入力を mysql_real_escape_string() という関数を介して実行することをお勧めします。 基本的に、文字列内のすべての特殊文字を削除して、データベースで使用すると意味が失われるようにします。
たとえば、I’m a string のような文字列がある場合、攻撃者は単一引用符 (‘) を使用して、作成中のデータベース クエリを操作し、SQL インジェクションを引き起こすことができます。 mysql_real_escape_string() を介して実行すると、I’m a string が生成され、一重引用符にバックスラッシュが追加されてエスケープされます。 その結果、クエリ操作に参加する代わりに、文字列全体が無害な文字列としてデータベースに渡されるようになりました。
このアプローチには欠点が 1 つあります。これは、PHP の古い形式のデータベース アクセスに使用される、非常に古い手法です。 PHP 7 の時点で、この関数は存在しなくなったため、次の解決策に進みます。
準備済みステートメントを使用する
プリペアド ステートメントは、データベース クエリをより安全かつ確実に作成する方法です。 生のクエリをデータベースに送信する代わりに、まず、送信するクエリの構造をデータベースに伝えるという考え方です。 これが、ステートメントを「準備する」という意味です。 ステートメントが準備されると、パラメータ化された入力として情報を渡します。これにより、データベースは、前に送信したクエリ構造に入力を差し込むことで「ギャップを埋める」ことができます。 これにより、入力が持つ可能性のある特別な力が取り除かれ、プロセス全体で単なる変数 (または必要に応じてペイロード) として扱われます。 準備されたステートメントは次のようになります。
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // prepare and bind $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // set parameters and execute $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "New records created successfully"; $stmt->close(); $conn->close(); ?>
準備されたステートメントに慣れていない場合、プロセスが不必要に複雑に聞こえることはわかっていますが、この概念は努力する価値があります。 こちらです それへの素晴らしい紹介。
PHP の PDO 拡張機能に既に精通しており、それを使用して準備済みステートメントを作成している方に、ちょっとしたアドバイスがあります。
警告: PDO を設定するときは注意してください
データベース アクセスに PDO を使用すると、誤った安心感に陥ってしまうことがあります。 「ああ、私は PDO を使っています。 今は他のことを考える必要はありません」 — これが私たちの一般的な考え方です。 PDO (または MySQLi のプリペアド ステートメント) があらゆる種類の SQL インジェクション攻撃を防ぐのに十分であることは事実ですが、設定するときは注意が必要です。 チュートリアルや以前のプロジェクトからコードをコピーして貼り付けて先に進むのが一般的ですが、この設定ではすべてを元に戻すことができます。
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
この設定が行うことは、データベースの準備済みステートメント機能を実際に使用するのではなく、準備済みステートメントをエミュレートするように PDO に指示することです。 したがって、コードが準備済みステートメントを作成し、パラメーターを設定しているように見えても、PHP は単純なクエリ文字列をデータベースに送信します。 つまり、以前と同じように SQL インジェクションに対して脆弱です。 🙂
解決策は簡単です。このエミュレーションが false に設定されていることを確認してください。
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
現在、PHP スクリプトは、データベース レベルで準備済みステートメントを使用することを強制され、あらゆる種類の SQL インジェクションを防止しています。
WAF の使用を防止する
WAF (Web アプリケーション ファイアウォール) を使用して、SQL インジェクションから Web アプリケーションを保護することもできることをご存知ですか?
まあ、SQL インジェクションだけでなく、クロスサイト スクリプティング、壊れた認証、クロスサイト フォージェリ、データ漏えいなど、他の多くのレイヤー 7 の脆弱性もあります。次のように、Mod Security のような自己ホスト型またはクラウドベースのいずれかを使用できます。
SQL インジェクションと最新の PHP フレームワーク
SQL インジェクションは非常に一般的で、非常に簡単で、非常に苛立たしく、非常に危険であるため、最新のすべての PHP Web フレームワークには対策が組み込まれています。 たとえば、WordPress には $wpdb->prepare() 関数がありますが、MVC フレームワークを使用している場合は、面倒な作業はすべてこの関数によって行われ、SQL インジェクションの防止について考える必要さえありません。 WordPress でステートメントを明示的に準備しなければならないのは少し面倒ですが、それは WordPress のことです。 🙂
とにかく、私が言いたいのは、現代の Web 開発者は SQL インジェクションについて考える必要がなく、その結果、その可能性にさえ気付いていないということです。 そのため、アプリケーションに 1 つのバックドアを開いたままにしても (おそらく、それは $_GET クエリ パラメーターであり、ダーティ クエリを実行するという古い習慣が始まります)、結果は壊滅的なものになる可能性があります。 そのため、時間をかけて基礎を深く掘り下げることをお勧めします。
結論
SQL インジェクションは、Web アプリケーションに対する非常に厄介な攻撃ですが、簡単に回避できます。 この記事で説明したように、ユーザー入力を処理する際には注意が必要です (ちなみに、ユーザー入力の処理がもたらす脅威は SQL インジェクションだけではありません)。 とはいえ、私たちは常に Web フレームワークのセキュリティに取り組んでいるわけではないため、この種の攻撃に注意し、それに陥らないようにすることをお勧めします。