ただ見て:
(出典: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つのクエリに変換されます。
Nameテーブルに 'Robert'の新しいレコードをStudentテーブルに追加します。
そして
学生テーブルを削除する
2番目のクエリを過ぎたものはすべて コメントとしてマークされ。 --', 'Derper')
'
学生の名前には、それが閉じます、コメントではない文字列の区切り文字。学生の名前は文字列であるため、仮想クエリを完了するには構文的に必要です。インジェクション攻撃は、それらがインジェクトするSQLクエリの結果が有効なSQLになる場合にのみ機能します。
編集後、再びあたりとしてdan04 s「は抜け目のないコメント
INSERT
場合、括弧はより意味があります。また、データベース接続が読み取り専用モードにならない理由についても説明します。
INSERT
。逆に考えると、SELECT
テーブル内のリトルボビーテーブルの挿入はすでにテーブルを削除しているため、は実行されません。
Students
は、1つの列(元の/正しいステートメントが2つの列を提供する)だけではないためです。とはいえ、2列目の存在は、コメントが必要な理由を示すのに役立ちます。そして、ボビーの名前を変更することはできないので、脚注としてこの観察以外はほとんどそのままにしておくのがおそらく最善です。
名前が変数で使用されたとしましょう$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つの答えを見ています... :-)人気のあるトピックのようです。
-アプリケーションがしようとせずに、「ナンシー」は、この場合には、入力を受け付け、そのような特殊文字のエスケープによると、サニタイズ入力- 学校=> INSERT INTOの学生のVALUES (「ナンシー」を;)挿入0 1 -データベースコマンドへの入力はに操作されたときにSQLインジェクションが発生します-任意のSQLを実行するために、データベース・サーバー原因 学校=> INSERT INTOの学生のVALUES (「ロバート」を)。ドロップテーブル学生; -'); 挿入0 1 ドロップテーブル -学生の記録がなくなりました-さらに悪いことになりました! 学校=> SELECT * FROM 学生; エラー: リレーション「学生」は 存在ません LINE 1 :SELECT * FROM 学生; ^
(この回答のすべてのコード例は、PostgreSQL 9.1.2データベースサーバーで実行されました。)
何が起こっているのかを明確にするために、名前フィールドのみを含む単純なテーブルでこれを試して、1つの行を追加します。
学校=> CREATE TABLE 生徒(名前テキストプライマリキー); 注意:CREATE TABLE / PRIMARY KEYをします作成する暗黙のインデックス"students_pkeyを" ためのテーブル"学生が" CREATE TABLEの 学校を=> INSERT INTO 学生のVALUES ('ジョン' ); 挿入0 1
アプリケーションが次のSQLを使用してテーブルにデータを挿入するとします。
INSERT INTO 学生のVALUES ('foobarに' );
交換する foobar
学生の実際の名前にます。通常の挿入操作は次のようになります。
-入力:ナンシーの 学校=> INSERT INTO 学生のVALUES ('ナンシー' ); 挿入0 1
テーブルをクエリすると、次のようになります。
学校=> SELECT * FROM 学生 ; 名前 ------- ジョン ナンシー (2 列)
リトルボビーテーブルの名前をテーブルに挿入するとどうなりますか?
-入力:Robert '); DROP TABLEの学生。- 学校=> INSERT INTOの学生のVALUES ('ロバート' ); ドロップテーブル学生; -'); 挿入0 1 ドロップテーブル
ここでのSQLインジェクションは、ステートメントを終了し、別のDROP TABLE
コマンドを含む学生の名前の結果です。入力の最後にある2つのダッシュは、エラーが発生する残りのコードをコメント化するためのものです。出力の最後の行は、データベースサーバーがテーブルを削除したことを示しています。
INSERT
操作中、アプリケーションは特殊文字の入力をチェックしていないため、SQLコマンドに任意の入力を入力できることに注意することが重要です。つまり、悪意のあるユーザーが、通常はユーザー入力を目的としたフィールドに、引用符などの特別な記号と任意のSQLコードを挿入して、データベースシステムに実行させ、SQL インジェクションを実行させることができます 。
結果?
学校=> SELECT * FROM 学生; ERROR : リレーション「学生は」いません 存在ん LINE 1 :SELECT * FROM 学生; ^
SQLインジェクションは、オペレーティングシステムまたはアプリケーションにおけるリモートの任意コード実行の脆弱性に相当するデータベースです。成功したSQLインジェクション攻撃の潜在的な影響を過小評価することはできません-データベースシステムとアプリケーション構成によっては、攻撃者がこれを使用して(この場合のように)データ損失を引き起こしたり、データへの不正アクセスを取得したり、実行したりすることさえできますホストマシン自体の任意のコード。
XKCDコミックで指摘されているように、SQLインジェクション攻撃から保護する1つの方法は、特殊文字をエスケープするなどしてデータベース入力を無害化することです。これにより、基になるSQLコマンドを変更できないため、任意のSQLコードを実行できなくなります。SqlParameter
ADO.NETでの使用などによってパラメーター化されたクエリを使用する場合、SQLインジェクションを防ぐために、入力は少なくとも自動的にサニタイズされます。
ただし、アプリケーションレベルで入力をサニタイズしても、より高度なSQLインジェクションテクニックが停止することはありません。たとえば、mysql_real_escape_string
PHP関数を回避する方法があります。保護を強化するために、多くのデータベースシステムは準備済みステートメントをサポートしています。バックエンドで適切に実装されている場合、準備されたステートメントは、データ入力をコマンドの残りの部分から意味的に分離して扱うことにより、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 = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);
student_nameが指定されたものである場合、「Robert」という名前の選択が行われ、テーブルが削除されます。「-」部分は、指定されたクエリの残りをコメントに変更します。
この場合、 'はコメント文字ではありません。文字列リテラルを区切るために使用されます。コミックアーティストは、問題の学校が次のような動的SQLをどこかに持っているという考えに頼っています。
$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";
そのため、プログラマーが期待する前に、 '文字は文字列リテラルを終了します。と組み合わせると、攻撃者はステートメントを終了するための文字を使用して、必要なSQLを追加できます。最後の-コメントは、元のステートメントの残りのsqlがサーバーでのクエリのコンパイルを妨げないようにするためのものです。
FWIW、私はまた、問題のコミックに重要な詳細が間違っていると思います:データベースの入力をサニタイズすることを考えている場合、コミックが示唆しているように、それはまだ間違っています。代わりに、データベース入力の隔離について考える必要があります。これを行う正しい方法は、パラメーター化されたクエリを使用することです。
これがどのように機能するかです:管理者が学生の記録を探しているとしましょう
Robert'); DROP TABLE STUDENTS; --
管理者アカウントには高い権限があるため、このアカウントからテーブルを削除することは可能です。
リクエストからユーザー名を取得するコードは
クエリは次のようになります(studentテーブルを検索するため)
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を削除します(通常、学校の管理者アカウントがそのようなクエリを実行し、上記で説明した権限を持ちます)。
SELECT* FROM sutdents ...
-"s"を忘れました。これはあなたが落とすものです。DROP TABLE STUDENTS;
誰もこれを指摘しなかったので、私はあなたに警告するかもしれません。
ほとんどの場合、フォーム入力にパッチを適用しようとします。しかし、SQLインジェクションで攻撃されるのはここだけではありません。GETリクエストを介してデータを送信するURLで非常に簡単な攻撃を行うことができます。休閑の例を考えてみましょう:
<a href="/show?id=1">show something</a>
あなたのURLはhttp://yoursite.com/show?id=1のようになります
今誰かがこのようなことを試すことができます
http://yoursite.com/show?id=1;TRUNCATE table_name
table_nameを実際のテーブル名に置き換えてみてください。彼があなたのテーブル名を正しければ、彼らはあなたのテーブルを空にするでしょう!(単純なスクリプトでこのURLを強制するのは非常に簡単です)
クエリは次のようになります...
"SELECT * FROM page WHERE id = 4;TRUNCATE page"
<?php
...
$id = $_GET['id'];
$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch();
/************* You have lost your data!!! :( *************/
...
<?php
...
$id = $_GET['id'];
$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...