web-dev-qa-db-ja.com

"Bobby Tables" XKCDコミックからのSQLインジェクションはどこのように機能しますか?

見ているだけです:

XKCD Strip (出典: https://xkcd.com/327/

このSQLは何をしますか。

Robert'); DROP TABLE STUDENTS; --

私は'--の両方がコメント用であることを知っていますが、WordのDROPも同じ行の一部なのでコメントされませんか?

1048
Blankman

studentsテーブルを削除します。

学校のプログラムの元のコードは、おそらく次のようになります

q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";

これはクエリにテキスト入力を追加する素朴な方法であり、後で見るように非常に悪いです。

名の値の後、ミドルネームのテキストボックスFNMName.TextRobert'); DROP TABLE STUDENTS; --)とラストネームのテキストボックスLName.Text(これをDerperと呼びましょう) )はクエリの残りの部分と連結され、結果は実際には2つのクエリステートメントターミネータ (セミコロン)で区切られています。 2番目のクエリは、最初のクエリに injected されています。コードがデータベースに対してこのクエリを実行すると、次のようになります

INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')

これは、平易な英語では、おおよそ次の2つのクエリに変換されます。

「Robert」のName値を使用して、Studentsテーブルに新しいレコードを追加します

そして

学生テーブルを削除する

2番目のクエリを過ぎたものはすべて、 コメントとしてマークされます--', 'Derper')

学生の名前の'はコメントではなく、終了 文字列区切り文字 です。学生の名前は文字列であるため、架空のクエリを完了するには構文的に必要です。インジェクション攻撃は機能しますインジェクトするSQLクエリの結果が有効なSQLである場合

againdan04 の抜け目のないコメントに従って編集

1083
Will

名前が変数$Nameで使われたとしましょう。その後、このクエリを実行します。

INSERT INTO Students VALUES ( '$Name' )

このコードは、ユーザーが変数として指定したものを誤って配置しています。あなたはSQLがであることを望みました:

学生の価値観に挿入( 'Robert Tables

しかし、賢いユーザは、欲しいものは何でも提供することができます。

学生の価値観に挿入( 'Robert'); DROP TABLE Students; --「)

あなたが得るものは:

INSERT INTO Students VALUES ( 'Robert' );  DROP TABLE STUDENTS; --' )

--は行の残りの部分をコメントアウトするだけです。

585
sinoth

他の誰もがすでに指摘したように、');は元の文を閉じてから2番目の文が続きます。 PHPのような言語を含むほとんどのフレームワークは、今ではデフォルトのセキュリティ設定を持っています。たとえば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つのステートメントを使用できる必要はありませんが、複数のステートメントを指定できる場合はもっと破壊的なことができます。

153

いいえ、'はSQLのコメントではなく区切り文字です。

ママはデータベースプログラマが次のようなリクエストをしたとします。

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');

(たとえば)$xxx変数の内容が、形式のチェックや特殊文字のエスケープなしでHTMLフォームから直接取り出された新しい学生を追加する場合。

したがって、$firstNameRobert'); DROP TABLE students; --が含まれている場合、データベースプログラムはDB上で次の要求を直接実行します。

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');

すなわち。それはinsert文の早期に終了し、クラッカーが望む悪意のあるコードを実行してから、残りのコードがあればそれをコメントアウトします。

うーん、私は遅すぎる、私はオレンジ色のバンドで私の前にすでに8の答えを参照してください... :-)人気のあるトピック、それはそうです。

69
PhiLho

TL、DR

  - アプリケーションは、入力をサニタイズしようとせずに、入力をサニタイズすることなく、入力を受け入れます。この場合は「Nancy」です。
 school => INSERT INTO Students VALUES( 'Nancy'); 
 INSERT 0 1 
 
  - データベースコマンドへの入力が
に操作されるとSQLインジェクションが発生します - データベースサーバーを起動します任意のSQL 
 school => INSERT IN INSERT VALUES( 'Robert')を実行します。 DROP TABLEの学生 -  '); 
 INSERT 0 1 
 DROP TABLE 
 
  - 生徒の記録は消えています - さらに悪い結果になっているかもしれません!
 school => SELECT * FROM Students; 
エラー:関係 "Students"が存在しません
行1:SELECT * FROM Students; 
 ^ 

これはstudentテーブルを削除(削除)します。

この回答のすべてのコード例はPostgreSQL 9.1.2データベースサーバで実行されました。

何が起こっているのかを明確にするために、nameフィールドだけを含む単純なテーブルを使ってこれを試し、単一行を追加しましょう。

 school => CREATE TABLEの学生(名前TEXT PRIMARY KEY); 
注意:CREATE TABLE/PRIMARY KEYは、テーブル "students"に対して暗黙のインデックス "students_pkey"を作成します。
 school => INSERT IN学生の価値観( 'John'); 
 INSERT 0 1 

アプリケーションが次のSQLを使ってテーブルにデータを挿入すると仮定しましょう。

生徒の価値観( 'foobar')に挿入する; 

foobarを実際の学生の名前に置き換えます。通常の挿入操作は次のようになります。

  - 入力:Nancy 
 school => INSERT IN学生にVALUES( 'Nancy'); 
 INSERT 0 1 

テーブルをクエリすると、次のようになります。

 school => SELECT * FROM学生から; 
 name 
 ------- 
 John 
 Nancy 
(2行)

テーブルにLittle Bobby Tablesの名前を挿入するとどうなりますか?

  - 入力:Robert '); DROP TABLEの学生 -  
 school =>生徒の価値観に挿入する( 'Robert')。 DROP TABLEの学生 -  '); 
 INSERT 0 1 
 DROP TABLE 

ここでのSQLインジェクションは、文を終了し、別のDROP TABLEコマンドを含む学生の名前の結果です。入力の最後にある2つのダッシュは、それ以外ではエラーを引き起こす可能性のある残りのコードをコメントアウトするためのものです。出力の最後の行は、データベースサーバがテーブルを削除したことを確認します。

INSERT操作中、アプリケーションは特殊文字の入力をチェックしないため、任意の入力をSQLコマンドに入力することを許可しています。つまり、悪意のあるユーザーは、通常はユーザー入力を目的としたフィールドに、引用符などの特殊記号を任意のSQLコードとともに挿入してデータベースシステムに実行させることができます。したがって、 SQL injection

結果?

 school =>学生からのSELECT * [
エラー:関係 "学生"が存在しません。
行1:学生からのSELECT *; 
 ^ 

SQLインジェクションは、オペレーティングシステムまたはアプリケーションにおけるリモートの{ 任意のコードの実行 の脆弱性と同等のデータベースです。 SQLインジェクション攻撃が成功した場合の潜在的な影響を過小評価することはできません。データベースシステムやアプリケーションの構成によっては、攻撃者が(この場合のように)データを損失したりホストマシン上の任意のコード.

XKCDコミックで指摘されているように、SQLインジェクション攻撃に対する保護の1つの方法は、特殊文字をエスケープするなどしてデータベース入力をサニタイズすることです。 ADO.NETで SqlParameter を使用するなど、パラメータ化されたクエリを使用する場合、SQLインジェクションを防ぐために入力は少なくとも自動的にサニタイズされます。

ただし、アプリケーションレベルで入力をサニタイズしても、より高度なSQLインジェクション手法が止まるわけではありません。たとえば、 mysql_real_escape_string PHP関数を回避する方法はいくつかあります です。保護を強化するために、多くのデータベースシステムは prepared statement をサポートしています。バックエンドで適切に実装されている場合.

36
bwDraco

あなたが素朴にこのような生徒作成方法を書いたとしましょう:

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 --')

セミコロンは挿入コマンドを終了し、別のコマンドを開始します。 the - 行の残りの部分をコメントアウトします。 DROP TABLEコマンドが実行されます...

これが、バインドパラメータが優れている理由です。

27
Dan Vinton

一重引用符は文字列の始めと終わりです。セミコロンは文の終わりです。それで、もし彼らがこのような選択をしていたら:

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

SQLは次のようになります。

Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
--             ^-------------------------------^

システムによっては、selectが最初に実行され、その後にdropステートメントが続きます。メッセージは次のとおりです。値をSQLに組み込みません。代わりにパラメータを使用してください。

24
CodeAndCats

');はクエリを終了しますが、コメントを開始しません。次に、Studentsテーブルを削除し、実行されるはずだった残りのクエリにコメントを付けます。

16
Jorn

データベースの作成者はおそらく

sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);

Student_nameが指定されたものである場合、それは "Robert"という名前の選択をしてからテーブルを削除します。 " - "部分は与えられた問い合わせの残りをコメントに変えます。

15
Paul Tomblin

この場合、 'はコメント文字ではありません。文字列リテラルを区切るために使用されます。漫画家は、問題の学校がどこかに次のような動的SQLを持っているという考えに頼っています。

$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";

そのため、プログラマが予期する前に '文字が文字列リテラルを終了します。と組み合わせる。ステートメントを終了する文字は、攻撃者は今彼らが望むすべてのSQLを追加することができます。最後の - コメントは、元の文に残っているSQLが、サーバ上でのクエリのコンパイルを妨げないようにするためのものです。

FWIW、問題の漫画の重要な詳細は間違っていると思います。漫画が示唆しているように、 サニタイズ /データベース入力について考えているのであれば、まだ間違っていると思います。その代わりに、あなたは 検疫 あなたのデータベース入力に関して考えるべきです、そしてこれをする正しい方法はパラメータ化された問い合わせを通してです。

15
Joel Coehoorn

SQLの'文字は、文字列定数に使用されます。この場合、コメントではなく文字列定数を終了するために使用されます。

14
Rockcoder

管理者が学生の記録を探しているとしましょう。

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を削除します(通常、学校の管理者アカウントはこのようなクエリを実行し、上記で説明した権限を持ちます)。

5
vivek

SQLインジェクションを行うためにフォームデータを入力する必要はありません。

誰もこれを指摘していなかったので、私はあなたの一部に警告するかもしれません。

ほとんどの場合、フォーム入力にパッチを当てます。しかし、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"

PDOを使用したPHP脆弱なコードの例:

<?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!!! :( *************/
...

解決策-PDO prepare()およびbindParam()メソッドを使用します。

<?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! :) *************/
...
1
DevWL