SQLインジェクション
日本語 | 構造化問合せ言語挿入 |
英語 | Structured Query Language injection |
ふりがな | えすきゅーえるいんじぇくしょん |
フリガナ | エスキューエルインジェクション |
SQLが改竄されるセキュリティホール。
画面等から入力された検索条件をSQLに組み込む際、通常の文字列連結を使用して組み込むと、もし検索条件にSQLが含まれていると、SQLの中に別のSQLが組み込まれSQLが変化し、意図しない結果をもたらすことになる。これを「SQLインジェクション」という。
SQLインジェクションを使用することで、最悪、好きなSQLを実行されてしまうため、外部から重要な情報を盗み出したり、データの変更や削除をされる可能性がある。
SQLインジェクションを防ぐためには、検索条件をサニタイジングして「SQLではない状態にする」ことが必要となる。
このサニタイジングはPreparedStatementインターフェイスのsetString()メソッド等を使用することで行うことができる。
そのため、外部からの入力をSQLに組み込む場合には必ずその組み込む箇所を「?」にし、PreparedStatementインターフェイスを用いて実行する。
画面等から入力された検索条件をSQLに組み込む際、通常の文字列連結を使用して組み込むと、もし検索条件にSQLが含まれていると、SQLの中に別のSQLが組み込まれSQLが変化し、意図しない結果をもたらすことになる。これを「SQLインジェクション」という。
SQLインジェクションを使用することで、最悪、好きなSQLを実行されてしまうため、外部から重要な情報を盗み出したり、データの変更や削除をされる可能性がある。
SQLインジェクションを防ぐためには、検索条件をサニタイジングして「SQLではない状態にする」ことが必要となる。
このサニタイジングはPreparedStatementインターフェイスのsetString()メソッド等を使用することで行うことができる。
そのため、外部からの入力をSQLに組み込む場合には必ずその組み込む箇所を「?」にし、PreparedStatementインターフェイスを用いて実行する。
参考サイト
// Sample.java
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Sample
{
public static void main( String[] args )
{
Connection conn = null;
PreparedStatement psCreate = null;
PreparedStatement psInsert = null;
PreparedStatement psSelect = null;
Statement stmt = null;
ResultSet rs1 = null;
ResultSet rs2 = null;
try
{
// この使用例の使用方法についてはSQLの項目を参照してください。
// JDBCドライバをロードします。
Class.forName( "org.hsqldb.jdbcDriver" );
// コネクションを取得します。
String url = "jdbc:hsqldb:mem:aname";
String user = "sa";
String password = "";
conn = DriverManager.getConnection( url, user, password );
// テーブルを作ります。
psCreate = null;
final String SQL_CREATE = "CREATE TABLE TABLE_TEST( NAME VARCHAR, VALUE INT );";
psCreate = conn.prepareStatement( SQL_CREATE );
psCreate.execute();
// 行を追加します。
final String SQL_INSERT = "INSERT INTO TABLE_TEST VALUES( ?, ? );";
psInsert = conn.prepareStatement( SQL_INSERT );
for( int iF1 = 0; iF1 < 3; ++iF1 )
{
// ?に値をセットします。
// 列の指定は、インデックスナンバーで行います。
// 1から始まるので注意。
psInsert.setString( 1, "ネーム" + iF1 );
psInsert.setInt( 2, iF1 );
// execute()メソッドでSQLを実行して、行を追加します。
psInsert.execute();
}
// たとえば、以下のような検索をする場合。
final String SQL_SELECT = "SELECT NAME, VALUE FROM TABLE_TEST WHERE NAME = ";
// その時に、以下のような検索条件が画面から入力されたとします。
String query = "' OR VALUE = 2 OR NAME = '";
// これをStatementでそのまま検索してみます。
stmt = conn.createStatement();
rs1 = stmt.executeQuery( SQL_SELECT + "'" + query + "'" );
while( rs1.next() )
{
// 行を進めてから、getString()メソッド等で取得します。
// NAME列のデータを文字列として取得します。
String name = rs1.getString( "NAME" );
// VALUE列のデータを整数値として取得します。
int value = rs1.getInt( "VALUE" );
System.out.println( name + ", " + value );
}
// ネーム2, 2
// このように、NAMEで絞り込みをしていたのが、
// VALUEでの絞り込みに変えられてしまいました。
// こういった、SQLが改竄されてしまうことを「SQLインジェクション」といいます。
// SQLインジェクションはPreparedStatementインターフェイスを
// 使用することで防ぐことができます。
// 検索条件を直接SQLには入れず、?にします。
psSelect = conn.prepareStatement( SQL_SELECT + "?" );
// ?に検索条件をセットします。
psSelect.setString( 1, query );
// 検索します。
rs2 = psSelect.executeQuery();
while( rs2.next() )
{
String name = rs2.getString( "NAME" );
int value = rs2.getInt( "VALUE" );
System.out.println( name + ", " + value );
}
// (0件です)
// このようにヒットしなくなります。
// これは、検索条件に含まれている「'」が「\'」に置き換えられて、
// 普通の文字とみなされているからです。この置き換えを
// 「サニタイジング」といいます。
// このおかげで、SQLインジェクションを防ぐことができます。
}
catch( SQLException e )
{
// SQLの実行で問題があった場合に投げられます。
e.printStackTrace();
}
catch( ClassNotFoundException e )
{
// JDBCドライバが存在しなかった場合に投げられます。
e.printStackTrace();
}
finally
{
if( rs1 != null )
{
try
{
rs1.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( rs2 != null )
{
try
{
rs2.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psSelect != null )
{
try
{
psSelect.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( stmt != null )
{
try
{
stmt.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psInsert != null )
{
try
{
psInsert.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psCreate != null )
{
try
{
psCreate.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( conn != null )
{
try
{
conn.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
}
}
}
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Sample
{
public static void main( String[] args )
{
Connection conn = null;
PreparedStatement psCreate = null;
PreparedStatement psInsert = null;
PreparedStatement psSelect = null;
Statement stmt = null;
ResultSet rs1 = null;
ResultSet rs2 = null;
try
{
// この使用例の使用方法についてはSQLの項目を参照してください。
// JDBCドライバをロードします。
Class.forName( "org.hsqldb.jdbcDriver" );
// コネクションを取得します。
String url = "jdbc:hsqldb:mem:aname";
String user = "sa";
String password = "";
conn = DriverManager.getConnection( url, user, password );
// テーブルを作ります。
psCreate = null;
final String SQL_CREATE = "CREATE TABLE TABLE_TEST( NAME VARCHAR, VALUE INT );";
psCreate = conn.prepareStatement( SQL_CREATE );
psCreate.execute();
// 行を追加します。
final String SQL_INSERT = "INSERT INTO TABLE_TEST VALUES( ?, ? );";
psInsert = conn.prepareStatement( SQL_INSERT );
for( int iF1 = 0; iF1 < 3; ++iF1 )
{
// ?に値をセットします。
// 列の指定は、インデックスナンバーで行います。
// 1から始まるので注意。
psInsert.setString( 1, "ネーム" + iF1 );
psInsert.setInt( 2, iF1 );
// execute()メソッドでSQLを実行して、行を追加します。
psInsert.execute();
}
// たとえば、以下のような検索をする場合。
final String SQL_SELECT = "SELECT NAME, VALUE FROM TABLE_TEST WHERE NAME = ";
// その時に、以下のような検索条件が画面から入力されたとします。
String query = "' OR VALUE = 2 OR NAME = '";
// これをStatementでそのまま検索してみます。
stmt = conn.createStatement();
rs1 = stmt.executeQuery( SQL_SELECT + "'" + query + "'" );
while( rs1.next() )
{
// 行を進めてから、getString()メソッド等で取得します。
// NAME列のデータを文字列として取得します。
String name = rs1.getString( "NAME" );
// VALUE列のデータを整数値として取得します。
int value = rs1.getInt( "VALUE" );
System.out.println( name + ", " + value );
}
// ネーム2, 2
// このように、NAMEで絞り込みをしていたのが、
// VALUEでの絞り込みに変えられてしまいました。
// こういった、SQLが改竄されてしまうことを「SQLインジェクション」といいます。
// SQLインジェクションはPreparedStatementインターフェイスを
// 使用することで防ぐことができます。
// 検索条件を直接SQLには入れず、?にします。
psSelect = conn.prepareStatement( SQL_SELECT + "?" );
// ?に検索条件をセットします。
psSelect.setString( 1, query );
// 検索します。
rs2 = psSelect.executeQuery();
while( rs2.next() )
{
String name = rs2.getString( "NAME" );
int value = rs2.getInt( "VALUE" );
System.out.println( name + ", " + value );
}
// (0件です)
// このようにヒットしなくなります。
// これは、検索条件に含まれている「'」が「\'」に置き換えられて、
// 普通の文字とみなされているからです。この置き換えを
// 「サニタイジング」といいます。
// このおかげで、SQLインジェクションを防ぐことができます。
}
catch( SQLException e )
{
// SQLの実行で問題があった場合に投げられます。
e.printStackTrace();
}
catch( ClassNotFoundException e )
{
// JDBCドライバが存在しなかった場合に投げられます。
e.printStackTrace();
}
finally
{
if( rs1 != null )
{
try
{
rs1.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( rs2 != null )
{
try
{
rs2.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psSelect != null )
{
try
{
psSelect.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( stmt != null )
{
try
{
stmt.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psInsert != null )
{
try
{
psInsert.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( psCreate != null )
{
try
{
psCreate.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
if( conn != null )
{
try
{
conn.close();
}
catch( SQLException e )
{
// 各close()メソッドからもSQLException例外が
// 投げられるので、拾っておきます。
e.printStackTrace();
}
}
}
}
}
// Sample.java import java.sql.DriverManager; import java.sql.Connection; import java.sql.Statement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class Sample { public static void main( String[] args ) { Connection conn = null; PreparedStatement psCreate = null; PreparedStatement psInsert = null; PreparedStatement psSelect = null; Statement stmt = null; ResultSet rs1 = null; ResultSet rs2 = null; try { // この使用例の使用方法についてはSQLの項目を参照してください。 // JDBCドライバをロードします。 Class.forName( "org.hsqldb.jdbcDriver" ); // コネクションを取得します。 String url = "jdbc:hsqldb:mem:aname"; String user = "sa"; String password = ""; conn = DriverManager.getConnection( url, user, password ); // テーブルを作ります。 psCreate = null; final String SQL_CREATE = "CREATE TABLE TABLE_TEST( NAME VARCHAR, VALUE INT );"; psCreate = conn.prepareStatement( SQL_CREATE ); psCreate.execute(); // 行を追加します。 final String SQL_INSERT = "INSERT INTO TABLE_TEST VALUES( ?, ? );"; psInsert = conn.prepareStatement( SQL_INSERT ); for( int iF1 = 0; iF1 < 3; ++iF1 ) { // ?に値をセットします。 // 列の指定は、インデックスナンバーで行います。 // 1から始まるので注意。 psInsert.setString( 1, "ネーム" + iF1 ); psInsert.setInt( 2, iF1 ); // execute()メソッドでSQLを実行して、行を追加します。 psInsert.execute(); } // たとえば、以下のような検索をする場合。 final String SQL_SELECT = "SELECT NAME, VALUE FROM TABLE_TEST WHERE NAME = "; // その時に、以下のような検索条件が画面から入力されたとします。 String query = "' OR VALUE = 2 OR NAME = '"; // これをStatementでそのまま検索してみます。 stmt = conn.createStatement(); rs1 = stmt.executeQuery( SQL_SELECT + "'" + query + "'" ); while( rs1.next() ) { // 行を進めてから、getString()メソッド等で取得します。 // NAME列のデータを文字列として取得します。 String name = rs1.getString( "NAME" ); // VALUE列のデータを整数値として取得します。 int value = rs1.getInt( "VALUE" ); System.out.println( name + ", " + value ); } // ネーム2, 2 // このように、NAMEで絞り込みをしていたのが、 // VALUEでの絞り込みに変えられてしまいました。 // こういった、SQLが改竄されてしまうことを「SQLインジェクション」といいます。 // SQLインジェクションはPreparedStatementインターフェイスを // 使用することで防ぐことができます。 // 検索条件を直接SQLには入れず、?にします。 psSelect = conn.prepareStatement( SQL_SELECT + "?" ); // ?に検索条件をセットします。 psSelect.setString( 1, query ); // 検索します。 rs2 = psSelect.executeQuery(); while( rs2.next() ) { String name = rs2.getString( "NAME" ); int value = rs2.getInt( "VALUE" ); System.out.println( name + ", " + value ); } // (0件です) // このようにヒットしなくなります。 // これは、検索条件に含まれている「'」が「\'」に置き換えられて、 // 普通の文字とみなされているからです。この置き換えを // 「サニタイジング」といいます。 // このおかげで、SQLインジェクションを防ぐことができます。 } catch( SQLException e ) { // SQLの実行で問題があった場合に投げられます。 e.printStackTrace(); } catch( ClassNotFoundException e ) { // JDBCドライバが存在しなかった場合に投げられます。 e.printStackTrace(); } finally { if( rs1 != null ) { try { rs1.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( rs2 != null ) { try { rs2.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( psSelect != null ) { try { psSelect.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( stmt != null ) { try { stmt.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( psInsert != null ) { try { psInsert.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( psCreate != null ) { try { psCreate.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } if( conn != null ) { try { conn.close(); } catch( SQLException e ) { // 各close()メソッドからもSQLException例外が // 投げられるので、拾っておきます。 e.printStackTrace(); } } } } }