ただ見て:
(出典: https://xkcd.com/327/ )
この SQL は何をしますか:
Robert'); DROP TABLE STUDENTS; --
私は両方を知って'
おり、コメント用ですが、同じ行の一部であるため--
、単語もコメントされませんか?DROP
ただ見て:
(出典: https://xkcd.com/327/ )
この SQL は何をしますか:
Robert'); DROP TABLE STUDENTS; --
私は両方を知って'
おり、コメント用ですが、同じ行の一部であるため--
、単語もコメントされませんか?DROP
学生のテーブルを削除します。
学校のプログラムの元のコードは、おそらく次のようになります
q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";
これはクエリにテキスト入力を追加する単純な方法であり、非常にまずい方法です。
名、ミドルネームのテキストボックスFNMName.Text ( Robert'); DROP TABLE STUDENTS; --
) と姓のテキストボックスLName.Text ( と呼びましょうDerper
) の値がクエリの残りの部分と連結された後、結果は実際には で区切られた2 つのクエリになります。ステートメント ターミネータ(セミコロン)。2 番目のクエリが最初のクエリに挿入されました。コードがデータベースに対してこのクエリを実行すると、次のようになります。
INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')
これは、平易な英語で、大まかに次の 2 つのクエリに変換されます。
名前の値が「Robert」の新しいレコードを Students テーブルに追加します。
と
学生テーブルを削除する
2 番目のクエリ以降はすべてコメントとしてマークされます。 --', 'Derper')
学生の'
名前の はコメントではなく、終了文字列の区切り文字です。生徒の名前は文字列であるため、仮想クエリを完了するには構文上必要です。インジェクション攻撃は、注入した SQL クエリが有効な SQL になる場合にのみ機能します。
dan04の鋭いコメントに従って再度編集
名前が変数で使用されたとしましょう$Name
。次に、次のクエリを実行します。
INSERT INTO Students VALUES ( '$Name' )
コードは、ユーザーが変数として指定したものを誤って配置しています。SQL を次のようにしたいとします。
学生の値に挿入 ( ' Robert Tables` )
しかし、賢明なユーザーは、必要なものを何でも提供できます。
学生の値に挿入 ( ' Robert'); DROP TABLE Students; --' )
あなたが得るものは次のとおりです。
INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' )
行の--
残りの部分のみがコメントされます。
他の誰もがすでに指摘しているように');
、元のステートメントを閉じてから、2番目のステートメントが続きます。PHPなどの言語を含むほとんどのフレームワークには、1つのSQL文字列で複数のステートメントを許可しないデフォルトのセキュリティ設定があります。たとえば、PHPでは、mysqli_multi_query
関数を使用して1つのSQL文字列で複数のステートメントを実行することしかできません。
ただし、2番目のステートメントを追加しなくても、SQLインジェクションを介して既存のSQLステートメントを操作できます。この単純な選択でユーザー名とパスワードをチェックするログインシステムがあるとしましょう。
$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);
ユーザーpeter
名とsecret
パスワードを指定すると、結果のSQL文字列は次のようになります。
SELECT * FROM users WHERE username='peter' and (password='secret')
すべて順調。ここで、この文字列をパスワードとして指定するとします。
' OR '1'='1
その場合、結果のSQL文字列は次のようになります。
SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1')
これにより、パスワードを知らなくても任意のアカウントにログインできるようになります。したがって、SQLインジェクションを使用するために、2つのステートメントを使用できる必要はありませんが、複数のステートメントを指定できる場合は、より破壊的な処理を実行できます。
いいえ、'
SQL のコメントではなく区切り文字です。
お母さんは、データベース プログラマーが次のようなリクエストをしたと仮定しました。
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');
(たとえば) 新しい生徒を追加するには、$xxx
変数の内容が HTML フォームから直接取り出され、形式をチェックしたり、特殊文字をエスケープしたりしません。
したがって、データベースプログラムが$firstName
含まれている場合Robert'); DROP TABLE students; --
、DB で次のリクエストを直接実行します。
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');
すなわち。挿入ステートメントを早期に終了し、クラッカーが望む悪意のあるコードを実行し、残りのコードをコメントアウトします。
うーん、私は遅すぎます。オレンジ色の帯で私の前にすでに 8 つの回答が表示されています... :-) 人気のあるトピックのようです。
-- アプリケーションは、入力 (この場合は「Nancy」) を試行せずに受け入れます。 -- 特殊文字をエスケープするなどして、入力をサニタイズします school=> INSERT INTO 生徒の値 ('Nancy'); 挿入 0 1 -- データベース コマンドへの入力が操作されると、SQL インジェクションが発生します。 -- データベース サーバーに任意の SQL を実行させる school=> INSERT INTO 生徒の値 ('Robert'); DROP TABLE 学生; --'); 挿入 0 1 ドロップテーブル -- 生徒の記録が消えてしまった - もっとひどいことになっていたかもしれない! 学校=> SELECT * FROM 生徒; エラー: リレーション「学生」は存在しません 行 1: SELECT * FROM 学生; ^
(この回答のすべてのコード例は、PostgreSQL 9.1.2 データベース サーバーで実行されました。 )
何が起こっているのかを明確にするために、名前フィールドのみを含む単純なテーブルでこれを試し、1 行追加してみましょう。
school=> CREATE TABLE の生徒 (名前 TEXT PRIMARY KEY); 注意: CREATE TABLE / PRIMARY KEY は、テーブル "students" の暗黙的なインデックス "students_pkey" を作成します テーブルを作成 school=> INSERT INTO 生徒の値 ('John'); 挿入 0 1
アプリケーションが次の SQL を使用してテーブルにデータを挿入するとします。
INSERT INTO 学生の値 ('foobar');
foobar
学生の実際の名前に置き換えます。通常の挿入操作は次のようになります。
-- 入力: ナンシー school=> INSERT INTO 生徒の値 ('Nancy'); 挿入 0 1
テーブルをクエリすると、次のようになります。
学校=> SELECT * FROM 生徒; 名前 ------- ジョン ナンシー (2行)
Little Bobby Tables の名前をテーブルに挿入するとどうなりますか?
-- 入力: ロバート'); DROP TABLE 学生; -- school=> INSERT INTO 生徒の値 ('Robert'); DROP TABLE 学生; --'); 挿入 0 1 ドロップテーブル
ここでの SQL インジェクションは、学生の名前がステートメントを終了させ、別のDROP TABLE
コマンドを含めた結果です。入力の末尾にある 2 つのダッシュは、エラーの原因となる残りのコードをコメント アウトすることを目的としています。出力の最後の行は、データベース サーバーがテーブルを削除したことを示しています。
操作中INSERT
、アプリケーションは特殊文字の入力をチェックしないため、任意の入力を SQL コマンドに入力できることに注意してください。これは、悪意のあるユーザーが、通常はユーザー入力用のフィールドに、引用符などの特殊記号を任意の SQL コードとともに挿入して、データベース システムに実行させることができることを意味します。したがって、SQL インジェクション.
結果?
学校=> SELECT * FROM 生徒; エラー: リレーション「学生」は存在しません 行 1: SELECT * FROM 学生; ^
SQL インジェクションは、オペレーティング システムまたはアプリケーションでリモートで任意のコードが実行される脆弱性に相当するデータベースです。SQL インジェクション攻撃が成功した場合の潜在的な影響を過小評価することはできません。データベース システムとアプリケーションの構成によっては、攻撃者がこれを使用してデータを損失したり (この場合のように)、データへの不正アクセスを取得したり、ホスト マシン自体の任意のコード。
XKCD コミックで指摘されているように、SQL インジェクション攻撃から保護する 1 つの方法は、特殊文字をエスケープするなどしてデータベース入力をサニタイズすることです。これにより、基になる SQL コマンドを変更できず、したがって、任意の SQL コードが実行されなくなります。これはアプリケーション レベルで行うことができ、パラメーター化されたクエリの一部の実装は、入力をサニタイズすることによって動作します。
ただし、アプリケーション レベルで入力をサニタイズしても、より高度な SQL インジェクション手法を阻止できない場合があります。たとえば、 PHP 関数を回避する方法がありますmysql_real_escape_string
。保護を強化するために、多くのデータベース システムは準備済みステートメントをサポートしています。バックエンドに適切に実装されている場合、準備されたステートメントは、データ入力を残りのコマンドから意味的に分離して扱うことにより、SQL インジェクションを不可能にすることができます。
次のような学生作成メソッドを素朴に書いたとします。
void createStudent(String name) {
database.execute("INSERT INTO students (name) VALUES ('" + name + "')");
}
そして誰かが名前を入力しますRobert'); DROP TABLE STUDENTS; --
データベースで実行されるのは、次のクエリです。
INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --')
セミコロンは挿入コマンドを終了し、別の挿入コマンドを開始します。-- は行の残りをコメントアウトします。DROP TABLE コマンドが実行されます...
これが、バインド パラメータが優れている理由です。
一重引用符は、文字列の開始と終了です。セミコロンはステートメントの終わりです。したがって、彼らが次のような選択を行っていた場合:
Select *
From Students
Where (Name = '<NameGetsInsertedHere>')
SQL は次のようになります。
Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
-- ^-------------------------------^
一部のシステムでは、select
最初に が実行され、その後にdrop
ステートメントが続きます。メッセージは次のとおりです。 SQL に値を埋め込まないでください。代わりにパラメーターを使用してください。
は');
クエリを終了し、コメントを開始しません。次に、学生テーブルを削除し、実行されるはずだった残りのクエリにコメントを付けます。
この場合、'
はコメント文字ではありません。文字列リテラルを区切るために使用されます。漫画家は、問題の学校が次のような動的な SQL をどこかに持っているという考えに頼っています。
$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";
その'
ため、プログラマーが予期する前に文字が文字列リテラルを終了します。ステートメントを終了する文字と組み合わせると、;
攻撃者は任意の SQL を追加 (注入) できるようになります。最後の--
コメントは、元のステートメントに残っている SQL が、サーバーでのクエリのコンパイルを妨げないようにするためのものです。
FWIW、問題のコミックには重要な詳細が間違っていると思います.データベース入力をサニタイズすると、コミックが示唆するように、それでも間違っています. 代わりに、データベース入力を隔離するという観点から考える必要があります。これを行う正しい方法は、パラメーター化されたクエリ/準備されたステートメントを使用することです。
SQLの'
文字は、文字列定数に使用されます。この場合、コメントではなく、文字列定数を終了するために使用されます。
データベースの作成者はおそらく
sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);
student_name が指定されている場合、「Robert」という名前で選択が行われ、テーブルが削除されます。「--」部分は、指定されたクエリの残りをコメントに変更します。
これがどのように機能するかです: 管理者が生徒の記録を探しているとしましょう。
Robert'); DROP TABLE STUDENTS; --
admin アカウントには高い権限があるため、このアカウントからテーブルを削除することは可能です。
リクエストからユーザー名を取得するコードは
クエリは次のようになります(学生テーブルを検索するため)
String query="Select * from student where username='"+student_name+"'";
statement.executeQuery(query); //Rest of the code follows
結果のクエリは次のようになります
Select * from student where username='Robert'); DROP TABLE STUDENTS; --
ユーザー入力はサニタイズされていないため、上記のクエリは 2 つの部分に操作されています
Select * from student where username='Robert');
DROP TABLE STUDENTS; --
二重ダッシュ (--) は、クエリの残りの部分をコメント アウトするだけです。
これは、存在する場合、パスワード認証を無効にする可能性があるため危険です。
最初のものは通常の検索を行います。
2 つ目は、アカウントに十分な権限がある場合、テーブル Student を削除します (通常、学校の管理者アカウントはそのようなクエリを実行し、上記の権限を持ちます)。