SQLインジェクション

SQLインジェクション

SQL インジェクションは、攻撃者がユーザー入力を通じて有害な SQL コードを挿入する Web アプリケーションのセキュリティ上の欠陥です。これにより、機密データ変更データベースの内容にアクセスしたり、システムを制御したりする可能性があります。 Web アプリケーションの安全性を確保するには、SQL インジェクションについて知っておくことが重要です。

SQL インジェクション (SQLi) は、攻撃者が悪意のある SQL コードをユーザー入力フィールドに挿入することによって Web アプリケーションのデータベース クエリを操作できる場合に発生するセキュリティ上の脆弱性です。これらの挿入されたクエリは、基礎となるデータベースを操作して、機密データを取得、変更、削除することができます。場合によっては、攻撃者が権限を昇格させてデータベースやサーバーを完全に制御できるようになる場合もあります。

SQLインジェクション

現実世界の例:

2019 年に、Web アプリケーションの設定が間違っていたことが原因で Capital One のデータ侵害が発生し、攻撃者が SQL インジェクションの脆弱性を悪用できるようになりました。その結果、氏名、住所、信用スコアなど、1億人以上の顧客の個人データが漏洩した。

SQL インジェクションのセキュリティ レベル

DVWA は、学習者がさまざまな保護が攻撃にどのような影響を与えるかを理解できるように、SQL インジェクションに 4 つのセキュリティ レベルを提供します。

1. セキュリティが低い

アプリは入力を受け取り、フィルタリングせずに SQL クエリに直接入力します。

 $id = $_GET['id'];$query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';';  
  • 入る ': クエリを中断し、データベースが脆弱であることを示すエラーをスローさせます。
  • 入る 1' OR '1'='1: クエリをだまして常に true になるようにし、すべてのユーザーが返されるようにします。
  • 入る 1' UNION SELECT user password FROM users--: 別のクエリを結合して、ユーザー名やパスワードなどの非表示データを取得します。

2. 中程度のセキュリティ

アプリは、次のような関数を使用して基本的な入力サニタイズを適用します。 addslashes() 逃げる '

 $id = addslashes($_GET['id']);$query = 'SELECT first_name last_name FROM users WHERE user_id = '$id';';  

どのように攻撃することができますか:

シンプルな ' インジェクションはもう機能しません( ' )。

ただし、攻撃者は数値インジェクションを使用してバイパスすることができます (数値には引用符が必要ないため)。
例:

 1 OR 1=1  

これでもすべてのレコードが返されます。

3. 高いセキュリティ

アプリは準備されたステートメント (パラメーター化されたクエリ) を使用してユーザー入力を安全に処理します。

 $stmt = $pdo->prepare('SELECT first_name last_name FROM users WHERE user_id = ?');$stmt->execute([$id]);  

攻撃:

のような試み ' OR 1=1 または UNION SELECT もう機能しません。

クエリはすべての入力を SQL コードではなくデータとして扱います。

SQL インジェクションの種類

SQL インジェクションにはさまざまな種類があります

1. エラーベースの SQL インジェクション

エラーベースの SQL インジェクションは、攻撃者が意図的にデータベースにエラー メッセージを生成させるインバンド SQL インジェクションの一種です。次に、攻撃者はこのエラー メッセージを分析して、テーブル名や列名などのデータベースの構造に関する貴重な情報を取得し、これを使用してさらに正確な攻撃を作成します。

仕組み

この攻撃は、一般的なメッセージを表示するのではなく、生のデータベース エラーを明らかにするアプリケーションをターゲットとしています。 SQL 構文を破壊する悪意のある入力を挿入することで、攻撃者はこれらのエラーを引き起こし、データベース構造に関する貴重な手がかりを入手します。

  1. 脆弱な入力を特定します。 攻撃者は、適切な入力サニタイズを行わずにデータベースと直接対話する検索バーや URL パラメータなどの入力フィールドを見つけます。
  2. 悪意のあるペイロードを挿入します。 攻撃者は特殊文字 (一重引用符など) を挿入します。 ' )、またはデータベース エラーを引き起こすことが知られている関数。
  3. エラーを分析します。 データベースは不正な形式のクエリを処理できないため、詳細なエラー メッセージが返されます。このメッセージにより、次のような重要な情報が明らかになる可能性があります。
    • データベース システム (例: MySQL Oracle SQL Server)。
    • データベースのバージョン。
    • 実行される完全な SQL クエリ。
    • テーブル名または列名を理解するために使用できる特定の構文エラー。
  4. 攻撃を洗練する: 攻撃者は、エラー メッセージから収集した情報を使用してペイロードを調整し、ユーザー名やパスワードなどのより多くのデータを抽出できます。

例:

ステップ 1: 環境をセットアップする

  • DVWAを起動します。通常、次のような URL に移動してアクセスします。 http://localhost/dvwa ブラウザで。
ファイル
  • デフォルトの認証情報を使用して DVWA にログインします。 admin / password
ファイル
  • [DVWA セキュリティ] タブに移動し、セキュリティ レベルを低に設定します。これにより、脆弱性が悪用されやすくなります。
ファイル

ステップ 2: 脆弱性を特定する

SQL インジェクション ページには、ユーザー ID を入力できる簡単な入力ボックスがあります。バックエンドクエリはおそらく次のようなものです SELECT * FROM users WHERE id = 'user_input'

  • 次のような有効な ID を入力してください 1 入力ボックスに入力し、「送信」をクリックします。 ID 1 のユーザーの詳細が表示されるはずです。
ファイル

SQLインジェクションソース

PHP
      $id   =   $_REQUEST  [   'id'   ];   switch   (  $_DVWA  [  'SQLI_DB'  ])   {   case   MYSQL  :   // Check database   $query   =   'SELECT first_name last_name FROM users WHERE user_id = '  $id  ';'  ;   $result   =   mysqli_query  (  $GLOBALS  [  '___mysqli_ston'  ]   $query   )   or   die  (   ' 
'   .   ((  is_object  (  $GLOBALS  [  '___mysqli_ston'  ]))   ?   mysqli_error  (  $GLOBALS  [  '___mysqli_ston'  ])   :   ((  $___mysqli_res   =   mysqli_connect_error  ())   ?   $___mysqli_res   :   false  ))   .   ' 
'
); // Get results while ( $row = mysqli_fetch_assoc ( $result ) ) { // Get values $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; // Feedback for end user echo '
ID:   {  $id  }    
First name:
{ $first }
Surname:
{ $last } ' ; } mysqli_close ( $GLOBALS [ '___mysqli_ston' ]); break ; case SQLITE : global $sqlite_db_connection ; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $query = 'SELECT first_name last_name FROM users WHERE user_id = ' $id ';' ; #print $query; try { $results = $sqlite_db_connection -> query ( $query ); } catch ( Exception $e ) { echo 'Caught exception: ' . $e -> getMessage (); exit (); } if ( $results ) { while ( $row = $results -> fetchArray ()) { // Get values $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; // Feedback for end user echo '
ID:   {  $id  }    
First name:
{ $first }
Surname:
{ $last } ' ; } } else { echo 'Error in fetch ' . $sqlite_db -> lastErrorMsg (); } break ; } } ode ?>
  • 次に、クエリを中断してみます。一重引用符を入力してください ' 入力ボックスに入力して送信します。
ファイル

クエリは次のようになります。

 SELECT * FROM users WHERE id = ''';  

ここでは、データベースは余分な引用符を認識し、クエリを完了する方法を知りません。

ユーザーの詳細を表示する代わりに、アプリケーションは SQL エラー (「SQL 構文にエラーがあります…」など) を返します。

これは、次の理由からエラーベースの SQL インジェクションと呼ばれます。

  • 攻撃者は無効な入力を送信します ( ' )
  • データベースがエラーをスローする
  • このエラーにより、データベースに関する有用な情報 (DB の種類、列数、構造など) が漏洩します。

2. ユニオンベースの SQL インジェクション

ユニオンベースの SQL インジェクションは、攻撃者が UNION 2 つ以上の結果を結合する演算子 SELECT ステートメントを単一の結果セットにまとめます。これにより、データベース内の他のテーブルから情報を抽出できるようになります。の UNION 演算子は、次の場合にのみ使用できます。

  • 両方のクエリの列数は同じです
  • 列のデータ型は類似しています
  • 列は同じ順序です

ユニオンオペレーター : の UNION 演算子は、2 つ以上の結果セットを結合するために使用されます。 SELECT 発言。

  • それぞれ SELECT 内のステートメント UNION 同じ数の列が必要です
  • 列には同様のデータ型が必要です
  • 列は同じ順序である必要があります
 SELECT column_name(s) FROM table1UNIONSELECT column_name(s) FROM table2  

例:

ステップ 1: まず、UNION ベースの SQL インジェクションを注入するために、Web サイト内の既存のテーブルの列の数を見つける必要があります。

SQL インジェクション ページには、ユーザー ID を入力できる簡単な入力ボックスがあります。バックエンドクエリはおそらく次のようなものです

     SELECT * FROM users WHERE id = 'user_input'   

次に、クエリを中断してみます。一重引用符を入力してください ' 入力ボックスに入力して送信します。

アプリケーションに脆弱性がある場合は、詳細なエラー メッセージが表示されます。次のようになります。

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1

ステップ 2: を使用します。 UNION 列数を調べるためのキーワード

を使用するには、 UNION キーワード (一般的な次のステップ) 元のクエリの列数を知る必要があります。これは、 ORDER BY

  • 結果を列ごとに並べ替えてみます
  1   :    1 ORDER BY 1   .   
  • 提出する。うまくいくはずです。
ファイル

SQLインジェクションソース

PHP
      if  (   isset  (   $_REQUEST  [   'Submit'   ]   )   )   {   // Get input   $id   =   $_REQUEST  [   'id'   ];   switch   (  $_DVWA  [  'SQLI_DB'  ])   {   case   MYSQL  :   // Check database   $query   =   'SELECT first_name last_name FROM users WHERE user_id = '  $id  ';'  ;   $result   =   mysqli_query  (  $GLOBALS  [  '___mysqli_ston'  ]   $query   )   or   die  (   ' 
'   .   ((  is_object  (  $GLOBALS  [  '___mysqli_ston'  ]))   ?   mysqli_error  (  $GLOBALS  [  '___mysqli_ston'  ])   :   ((  $___mysqli_res   =   mysqli_connect_error  ())   ?   $___mysqli_res   :   false  ))   .   ' 
'
); // Get results while ( $row = mysqli_fetch_assoc ( $result ) ) { // Get values $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; // Feedback for end user echo '
ID:   {  $id  }    
First name:
{ $first }
Surname:
{ $last } ' ; } mysqli_close ( $GLOBALS [ '___mysqli_ston' ]); break ; case SQLITE : global $sqlite_db_connection ; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $query = 'SELECT first_name last_name FROM users WHERE user_id = ' $id ';' ; #print $query; try { $results = $sqlite_db_connection -> query ( $query ); } catch ( Exception $e ) { echo 'Caught exception: ' . $e -> getMessage (); exit (); } if ( $results ) { while ( $row = $results -> fetchArray ()) { // Get values $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; // Feedback for end user echo '
ID:   {  $id  }    
First name:
{ $first }
Surname:
{ $last } ' ; } } else { echo 'Error in fetch ' . $sqlite_db -> lastErrorMsg (); } break ; } } ?>
  • 数値をインクリメントします。
     1 ORDER BY 2   .   

提出する。うまくいくはずです。

ファイル
  • エラーが発生するまで増加し続けます。例えば 1 ORDER BY 4 あなたに与えるかもしれません: Unknown column '4' in 'order clause'
  • これは、クエリに 3 つの列があることを意味します。

3. ブラインドベースの SQL インジェクション

ブラインド SQL インジェクション これは、攻撃者が Web ページ上でクエリ結果を直接確認できない場合に発生します。代わりに、アプリケーションの動作や応答時間の微妙な変化から情報を推測します。従来の SQLi よりも遅くて退屈ですが、同等の効果があります。

攻撃者はデータを取り戻す代わりに、Web ページの動作を観察して情報を推測します。これは通常、次の 2 つの方法のいずれかで行われます。

  1. ブールベースのブラインド SQLi: 攻撃者は、 真実 または 間違い 結果。 Web アプリケーションの応答は、クエリが true か false かに基づいて変化します。たとえば、ページに別のメッセージが表示されたり、別のレイアウトが表示されたりする場合があります。
  2. 時間ベースのブラインド SQLi: 攻撃者は、データベースに時間のかかるアクション (たとえば、 SLEEP() 関数) 条件が満たされた場合。攻撃者は、ページの読み込みにかかる時間を観察して、挿入された条件が true か false かを判断します。

例:

ユーザー名とパスワードを入力するログイン ページを想像してください。アプリケーションは次のような SQL クエリを構築します。

  SELECT * FROM users WHERE username = 'user_input' AND password = 'password_input'   

ブラインド SQL インジェクションには、 user_input データベースに質問するためのフィールドです。

攻撃者は直接応答を得る代わりに、次のようなことを試みる可能性があります。

  user_input = 'admin' AND 1=1; --   

ページが正常に読み込まれると、攻撃者はそれを認識します。 1=1 です 真実 声明。

  user_input = 'admin' AND 1=2; --   

ページにエラーが表示されたり、動作が異なる場合、攻撃者はそれを知っています。 1=2 です 間違い 声明。

ファイル

これらの一連の正誤質問を使用することで、攻撃者は体系的に推測し、一度に 1 文字ずつ情報を抽出できます。このプロセスを自動化して、テーブル名からユーザーのパスワードに至るまですべてを推測することができます。

SQL インジェクション攻撃の影響

  • 機密データへの不正アクセス :攻撃者は、データベースに保存されている個人の財務情報や機密情報を取得することができます。
  • データの整合性の問題 :攻撃者は、アプリケーションの機能に影響を与える重要なデータを変更、削除または破損する可能性があります。
  • 権限昇格 :攻撃者は認証メカニズムをバイパスし、管理者権限を取得する可能性があります。
  • サービスのダウンタイム : SQL インジェクションはサーバーに過負荷をかけ、パフォーマンスの低下やシステムクラッシュを引き起こす可能性があります。
  • 風評被害 :攻撃が成功すると、組織の評判が著しく損なわれ、顧客の信頼が失われる可能性があります。

SQL インジェクション攻撃の防止

SQL インジェクション攻撃を防ぐためのベスト プラクティスがいくつかあります。

1. プリペアドステートメントとパラメータ化されたクエリを使用する

プリペアド ステートメントとパラメータ化されたクエリにより、ユーザー入力が SQL クエリの一部ではなくデータとして扱われることが保証されます。このアプローチにより、SQL インジェクションのリスクが排除されます。

PHP での例 (MySQLi を使用):

 $stmt = $conn->prepare('SELECT * FROM users WHERE username = ? AND password = ?'); $stmt->bind_param('ss' $username $password); $stmt->execute();  

2. ストアド プロシージャを使用する

ストアド プロシージャは、データベースに保存される事前定義された SQL クエリです。これらのプロシージャは SQL クエリを動的に構築しないため、SQL インジェクションの防止に役立ちます。

例:

 CREATE PROCEDURE GetUserByUsername (IN username VARCHAR(50)) BEGIN SELECT * FROM users WHERE username = username; END;  

3. ホワイトリスト入力の検証

ユーザー入力が SQL クエリで使用される前に検証されていることを確認してください。ユーザー名や電子メール アドレスなどのフィールドには、英数字の入力など、特定の文字とパターンのみを許可します。

4. ORM フレームワークを使用する

オブジェクト リレーショナル マッピング (ORM) フレームワークのような 休止状態 または エンティティフレームワーク 動的クエリ構築を防ぐクエリ生成を自動的に処理することで、SQL インジェクションを防ぐことができます。

5. データベース権限を制限する

最低限必要なデータベース権限をユーザーに付与します。アプリケーションが必要なアクション (例: SELECT INSERT) のみを実行できるようにし、DROP TABLE や ALTER などの権限を制限します。

6. エラー処理

詳細なエラー メッセージをユーザーに表示しないようにデータベースとアプリケーションを構成します。代わりに、内部でエラーをログに記録し、一般的なエラー メッセージをエンド ユーザーに表示します。