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


1093

ただ見て:

XKCDストリップ (出典:https : //xkcd.com/327/

このSQLは何をしますか:

Robert'); DROP TABLE STUDENTS; --

私は両方を知って'おり--、コメントを求めていますDROPが、同じ行の一部であるため、コメントも付けられませんか?


16
Stack Overflowポッドキャスト#31(2008年11月27日)を聞いていると、彼らは実際にこれについて議論しています。
EBGreen 2008

93
MySQLでは、コメント'用ではありません。たとえあったとしても、その前にはスペースがないため、その前の文字列のみを終了できます。
オービットの軽さのレース

45
XKCDに関する限り、いくつかの漫画について質問がある場合は、いつでもExplain XKCDにアクセスして答えを理解させることができます。XKCD wikiもあります。これは、XKCDジオハッシュ
Anatoli

13
このリンクはここに記録する必要があると思い
Arioch 'The

2
beta.companieshouse.gov.uk/company/10542519は、というコンサルタント会社の登録です。DROP TABLE "COMPANIES";-LTD
Alex Dupuy

回答:


1116

それは学生のテーブルを落とします。

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

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「は抜け目のないコメント


3
うーん、引数を括弧で囲むWHEREはかなり珍しいですが、少なくとも構文エラーは回避されます... :-)
PhiLho

60
@PhiLho:元のステートメントがであるINSERT場合、括弧はより意味があります。また、データベース接続が読み取り専用モードにならない理由についても説明します。
dan04

3
@ dan04が説明するように、括弧はINSERT。逆に考えると、SELECTテーブル内のリトルボビーテーブルの挿入はすでにテーブルを削除しているため、は実行されません。
ypercubeᵀᴹ

10
実際、この例では、最初のクエリ( "add a new record ...")は失敗します。これStudentsは、1つの列(元の/正しいステートメントが2つの列を提供する)だけではないためです。とはいえ、2列目の存在は、コメントが必要な理由を示すのに役立ちます。そして、ボビーの名前を変更することはできないので、脚注としてこの観察以外はほとんどそのままにしておくのがおそらく最善です。
eggyal

7
Explain XKCDによると、Bobbyの姓、または少なくとも母親の姓はRoberts です。しかし、それを修正することで回答の明確さが改善されるかどうかはわかりません。
WBT 2016

611

名前が変数で使用されたとしましょう$Name

次に、このクエリを実行します

INSERT INTO Students VALUES ( '$Name' )

コードは、ユーザーが変数として提供したものを誤って配置しています。

SQLを次のようにしたいとしました。

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

しかし、賢いユーザーは好きなものを提供できます:

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

あなたが得るものは:

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

--唯一の行の残りをコメントしています。


87
これは、閉じ括弧を説明するため、最高得票数よりもはるかに優れています。
TimBüthe

1
ちなみに、学生のテーブルが削除されているため、漫画の学校長やXSSが気付く方法はありません。彼は誰がこれをしたのかわかりません。
xryl669

@ xryl669ログはこのような状況で非常に役立ちます...すべてのクエリがログに記録されることもあれば、他のログに記録された情報が原因の推定に役立つこともあります。
inemanja

165

他の人がすでに指摘したように、');は元のステートメントを閉じ、次に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つのステートメントを使用する必要はありませんが、複数のステートメントを提供できる場合は、より破壊的なことができます。


71

番号、 ' 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つの答えを見ています... :-)人気のあるトピックのようです。


39

TL; DR

-アプリケーションがしようとせずに、「ナンシー」は、この場合には、入力を受け付け、そのような特殊文字のエスケープによると、サニタイズ入力- 
学校=> 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コードを実行できなくなります。SqlParameterADO.NETでの使用などによってパラメーター化されたクエリを使用する場合、SQLインジェクションを防ぐために、入力は少なくとも自動的にサニタイズされます。

ただし、アプリケーションレベルで入力をサニタイズしても、より高度なSQLインジェクションテクニックが停止することはありません。たとえばmysql_real_escape_stringPHP関数を回避する方法があります。保護を強化するために、多くのデータベースシステムは準備済みステートメントをサポートしています。バックエンドで適切に実装されている場合、準備されたステートメントは、データ入力をコマンドの残りの部分から意味的に分離して扱うことにより、SQLインジェクションを不可能にする可能性があります。


30

次のような単純な学生作成メソッドを作成したとします。

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コマンドが実行されます...

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


26

一重引用符は、文字列の開始と終了です。セミコロンはステートメントの終わりです。したがって、彼らがこのような選択を行っていた場合:

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

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

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

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


18

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


17

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

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

student_nameが指定されたものである場合、「Robert」という名前の選択が行われ、テーブルが削除されます。「-」部分は、指定されたクエリの残りをコメントに変更します。


それは私の最初の考えでしたが、最後の閉じ括弧で構文エラーが発生しましたね?
PhiLho 2008

3
そのため、最後に-があり、残りのテキストはコメントであり、無視する必要があります。

17

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

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

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

FWIW、私はまた、問題のコミックに重要な詳細が間違っていると思います:データベースの入力をサニタイズすることを考えている場合、コミックが示唆しているように、それはまだ間違っています。代わりに、データベース入力の隔離について考える必要があります。これを行う正しい方法は、パラメーター化されたクエリを使用することです。


16

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


7

これがどのように機能するかです:管理者が学生の記録を探しているとしましょう

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;
DevWL

4

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! :) *************/
...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.