概要
リレーショナルデータベース管理システム(RDBMS)には、データベースとの通信に使用されるSQL(構造化照会言語)と呼ばれる特定の言語があります。 SQLで記述されたクエリステートメントは、データベースのコンテンツと構造を操作するために使用されます。データベースの構造を作成および変更する特定のSQLステートメントはDDL(データ定義言語)ステートメントと呼ばれ、データベースのコンテンツを操作するステートメントはDML(データ操作言語)ステートメントと呼ばれます。 RDBMSパッケージに関連付けられたエンジンは、SQLステートメントを解析および解釈し、それに応じて結果を返します。これは、RDBMSとの通信の一般的なプロセスです。SQLステートメントを実行して結果を取得するだけです。システムは、言語の構文および意味構造に準拠するステートメントの意図を判断しません。これは、誰がステートメントを実行したか、および出力を取得するための特権を確認するための認証または検証プロセスがないことも意味します。攻撃者は、悪意を持ってSQLステートメントを起動し、取得するはずのない情報を取り戻すことができます。たとえば、攻撃者は、悪意のあるペイロードを使用してSQLステートメントを実行し、無害に見えるクエリを使用してWebアプリケーションのデータベースサーバーを制御できます。
仕組み
攻撃者はこの脆弱性を利用して、自分の利益のために使用することができます。たとえば、アプリケーションの認証および承認メカニズムをバイパスして、データベース全体からいわゆる安全なコンテンツを取得できます。 SQLインジェクションを使用して、データベースのレコードを作成、更新、および削除できます。したがって、SQLを使用して自分の想像力に限定したクエリを作成できます。
通常、アプリケーションは、特定のレコードのフェッチ、レポートの作成、ユーザーの認証、CRUDトランザクションなど、さまざまな目的でデータベースに対してSQLクエリを頻繁に実行します。攻撃者は、アプリケーション入力フォーム内でSQL入力クエリを見つける必要があります。フォームによって準備されたクエリを使用して、悪意のあるコンテンツを絡ませることができます。これにより、アプリケーションがクエリを実行したときに、挿入されたペイロードも伝送されます。
理想的な状況の1つは、アプリケーションがユーザーにユーザー名やユーザーIDなどの入力を要求する場合です。アプリケーションはそこで脆弱な場所を開きました。 SQLステートメントは無意識のうちに実行される可能性があります。攻撃者は、SQLクエリの一部として使用され、データベースによって処理されるペイロードを挿入することで利用します。たとえば、ログインフォームのPOST操作のサーバー側の擬似コードは次のようになります。
uname = getRequestString("username"); pass = getRequestString("passwd"); stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "'"; database.execute(stmtSQL);
上記のコードは、変数「uname」および「pass」を介してSQLステートメントに与えられた入力が、ステートメントのセマンティクスを変更する方法で操作される可能性があるため、SQLインジェクション攻撃に対して脆弱です。
たとえば、MySQLのように、データベースサーバーに対して実行するようにクエリを変更できます。
stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";
これにより、元のSQLステートメントが認証をバイパスできる程度に変更されます。これは深刻な脆弱性であり、コード内から防ぐ必要があります。
SQLインジェクション攻撃に対する防御
SQLインジェクション攻撃の可能性を減らす方法の1つは、フィルタリングされていないテキスト文字列を実行前にSQLステートメントに追加できないようにすることです。たとえば、 PreparedStatementを使用できます。 必要なデータベースタスクを実行します。 PreparedStatementの興味深い側面 これは、文字列ではなく、コンパイル済みのSQLステートメントをデータベースに送信することです。これは、クエリとデータが別々にデータベースに送信されることを意味します。これにより、SQLインジェクション攻撃の根本的な原因が防止されます。これは、SQLインジェクションでは、データが実際にはデータを装ってコードの一部であるコードとデータを混合するという考え方であるためです。 PreparedStatement 、複数の setXYZ()があります setString()などのメソッド 。これらのメソッドは、SQLステートメントに含まれる引用符などの特殊文字をフィルタリングするために使用されます。
たとえば、次の方法でSQLステートメントを実行できます。
String sql = "SELECT * FROM employees WHERE emp_no = "+eno;
たとえば、 eno =10125と言う代わりに 入力の従業員番号として、次のような入力でクエリを変更できます。
eno = 10125 OR 1=1
これにより、クエリによって返される結果が完全に変更されます。
例
次のサンプルコードでは、 PreparedStatement データベースタスクの実行に使用できます。
package org.mano.example; import java.sql.*; import java.time.LocalDate; public class App { static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost:3306/employees"; static final String USER = "root"; static final String PASS = "secret"; public static void main( String[] args ) { String selectQuery = "SELECT * FROM employees WHERE emp_no = ?"; String insertQuery = "INSERT INTO employees VALUES (?,?,?,?,?,?)"; String deleteQuery = "DELETE FROM employees WHERE emp_no = ?"; Connection connection = null; try { Class.forName(JDBC_DRIVER); connection = DriverManager.getConnection (DB_URL, USER, PASS); }catch(Exception ex) { ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(insertQuery);){ pstmt.setInt(1,99); pstmt.setDate(2, Date.valueOf (LocalDate.of(1975,12,11))); pstmt.setString(3,"ABC"); pstmt.setString(4,"XYZ"); pstmt.setString(5,"M"); pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1))); pstmt.executeUpdate(); System.out.println("Record inserted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(selectQuery);){ pstmt.setInt(1,99); ResultSet rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getString(3)+ " "+rs.getString(4)); } }catch(Exception ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(deleteQuery);){ pstmt.setInt(1,99); pstmt.executeUpdate(); System.out.println("Record deleted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try{ connection.close(); }catch(Exception ex){ ex.printStackTrace(); } } }
PreparedStatementを垣間見る
これらのジョブは、JDBCステートメントを使用して実行することもできます。 ただし、問題は、特に動的SQLステートメントを実行してデータベースにクエリを実行し、ユーザー入力値がSQLクエリと連結されている場合は、非常に安全でない場合があることです。これまで見てきたように、これは危険な状況になる可能性があります。ほとんどの通常の状況では、ステートメント まったく無害ですが、 PreparedStatement ステートメントをデータベースに送信する際のアプローチが異なるため、悪意のある文字列が連結されるのを防ぎます。 PreparedStatement 連結ではなく変数置換を使用します。 SQLクエリに疑問符(?)を付けると、代替変数が代わりに使用され、クエリの実行時に値が提供されることを意味します。置換変数の位置は、 setXYZ()で割り当てられたパラメータインデックスの位置に従って行われます。 メソッド。
この手法により、SQLインジェクション攻撃を防ぐことができます。
さらに、 PreparedStatement AutoCloseableを実装します。 これにより、 try-with-resourcesのコンテキスト内での書き込みが可能になります。 ブロックし、スコープ外になると自動的に閉じます。
結論
SQLインジェクション攻撃は、責任を持ってコードを作成することによってのみ防ぐことができます。実際、どのソフトウェアソリューションでも、コーディング慣行が悪いためにセキュリティがほとんど破られています。ここでは、避けるべきことと、 PreparedStatement 安全なコードを書くのに役立ちます。 SQLインジェクションの完全なアイデアについては、適切な資料を参照してください。インターネットはそれらでいっぱいです、そして、 PreparedStatement 、詳細な説明については、JavaAPIドキュメントを参照してください。