Java PreparedStatementを使ってSQLインジェクションを防ぐ

はじめに

JavaでSQLインジェクションを防ぐためのコーディング作法を紹介します。

SQLインジェクションとは

以下OWASPのドキュメントの抜粋ですが

A SQL injection attack consists of insertion or “injection” of a SQL query via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database …

アプリケーションの入力項目にSQLクエリを入力し、機密情報を漏洩させる攻撃手法のことです。

SQLインジェクションに対する対策

こちらもOWASPのドキュメントの抜粋ですが

Option 1: Use of Prepared Statements (with Parameterized Queries)
Option 2: Use of Stored Procedures
Option 3: White List Input Validation
Option 4: Escaping All User Supplied Input

上記4つが一般的な対策です。今回はOption 1のプリペアドステートメントをJavaの実装で試していきます。

サンプルコード

※以下サンプルコードでは、例外ハンドリングやリソースクローズ処理は割愛しています。

環境情報

  • Windows 10 Home edition
  • Java 8u151
  • Intelij IDEA 2017.2.5
  • MySQL 5.7.20

テスト用データベース

userテーブルにname,passwordカラムの2カラムがあり、2レコードのみ入っているデータベースを使います。

> mysql-sql select * from user;
+-------+----------+
| name  | password |
+-------+----------+
| admin | secret   |
| user1 | pass1    |
+-------+----------+

SQLインジェクション対策前のコード

ポイントは、入力値を検証せずにそのままSQLクエリの一部として文字列連結しているところです。

public static void main(String[] args) throws SQLException {
        String user = args[0];
        String pass = args[1];
        String sql = "SELECT name FROM USER " + "WHERE name='" + user + "' AND password='" + pass + "';";
        try (Connection con = DriverManager.getConnection("jdbc:mysql://localhost/sample", "root", "password");
             Statement stmt = con.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                System.out.println(rs.getString(1));
            }
        }
    }

上記コードに “user1” “pass1” を引数で渡すと以下の通り期待通り動きますが

user1

上記コードに “user1” “pass1′ OR ‘1’ = ‘1” を引数で渡すと以下の通り他のユーザの情報も意図せず返ってきてしまいます。

user1
admin

SQLインジェクション対策後のコード

ポイントは、PreparedStatementのAPIを使い入力値をバインドし、SQLクエリを文字列連結で作成していない点です。

public static void main(String[] args) throws SQLException {
        String user = args[0];
        String pass = args[1];
        String sql = "SELECT name FROM USER WHERE name= ? AND password= ?;";
        try (Connection con = DriverManager.getConnection("jdbc:mysql://localhost/sample", "root", "password");
             PreparedStatement pstmt = con.prepareStatement(sql)) {
            pstmt.setString(1, user);
            pstmt.setString(2, pass);
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    System.out.println(rs.getString(1));
                }
            }
        }
    }

上記コードに “user1” “pass1” を引数で渡すと以下の通り期待通り動いて

user1

上記コードに “user1” “pass1′ OR ‘1’ = ‘1” 何も出力されません。期待通りSQLインジェクションを防げています。


まとめ

ユーザの入力値を検証せず、そのまま文字列連結でSQLを実行させていると簡単にSQLインジェクションができてしまうので気をつけましょう。

参考サイト

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA