Webセキュリティの小部屋

Twitter のフォローはこちらから Facebook ページはこちら RSSフィードのご登録はこちらから
公開日:2013年3月3日
最終更新日:2020年8月18日

SQLインジェクション対策(Java編)

SQL インジェクションの対策は、簡単に言えば SQL で静的プレースホルダを使用することです。

静的プレースホルダの詳細については、以下の記事を参照してください。

では、Java で SQL インジェクション対策を実際にどのように行うのか簡単なログイン処理で具体的に見てみましょう。

ログイン処理の概要は以下の通りです。動作環境は、Java 11 + Tomcat 9 + MySQL 8 になります。

java

接続先のデータベース名は loginsample、テーブル名は users で、列名は id,  password です。

なお、これは簡単なログイン処理のため、ログインにまつわるセキュリティ対策は省いています。ログインのセキュリティ対策は、以下の記事を参照してください。

・login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <h1>ログイン</h1>
  <form action="/test/login" method="post">
    ID:<input type="text" name="id"><br>
    Password:<input type="password" name="password"><br>
    <input type="submit" value="ログイン">
    <input type="reset" value="リセット">
  </form>
</body>
</html>

 

・login(Login.java)

JavaとMySQLの組み合わせでは、デフォルトで動的プレースホルダが使用されるので、接続文字列で静的プレースホルダを使用するように、useServerPrepStmts=true を指定する必要がるので注意してください。

package test.login;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class HelloWorld
 */
@WebServlet("/Login")
public class Login extends HttpServlet {
  private static final long serialVersionUID = 1L;

    public Login() {
        super();
    }

  /**
   * ログイン処理を行う
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      int count = 0;

      //パラメーター取得
      request.setCharacterEncoding("utf-8");
      String id = request.getParameter("id");
      String password = request.getParameter("password");

      Connection con = null;
      PreparedStatement stmt = null;
      ResultSet rs = null;

        String url = "jdbc:mysql://192.168.33.10:3306/loginsample?useServerPrepStmts=true&useUnicode=true&characterEncoding=utf8";
        String user = "mysql";
        String pass = "Mysql@1234";

        try {
      	  //DB に接続
          Class.forName("com.mysql.cj.jdbc.Driver");
          con = DriverManager.getConnection(url, user, pass);

          //プレースホルダで SQL 作成
          String sql = "SELECT COUNT(*) AS cnt FROM users WHERE id=? AND password=?;";

          //SQL をプリコンパイル
          stmt = con.prepareStatement(sql);

          //パラメーターセット
          stmt.setString(1, id);
          stmt.setString(2, password);

          //SQL 実行
          rs = stmt.executeQuery();

          while (rs.next()) {
          	count = rs.getInt("cnt");
          	System.out.println(rs.getInt("cnt"));
          }

        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();   
                } catch (SQLException e) {
                    e.printStackTrace();  
                }
            }
            if (stmt != null) {
                try {  
                   stmt.close();   
                } catch (SQLException e) {   
                   e.printStackTrace();  
                }
            }
            if (con != null) {
               try {
                  con.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              } 
            }
        }

        if (count == 1) {
            //ログイン成功
            HttpSession session = request.getSession(true);
            session.setAttribute("id", id);
            ServletContext sc = getServletContext();
            RequestDispatcher rd = sc.getRequestDispatcher("/welcome.jsp");
            rd.forward(request, response);
          } else {
            //ログイン失敗
            response.sendRedirect("/test/login.jsp");
          }
  }
  
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  }
}

 

(追記)

データーベースの種類と接続方法によっては、Java でプレースホルダを指定しても、安全な「静的プレースホルダ」ではなく、SQL インジェクションの可能性が起きうる「動的プレースホルダ」を使用する場合があります。詳しくは、IPA が公開している「安全なSQLの呼び出し方」をご参照ください。

(追記2)

JDBC Driver の仕様では、PreparedStatement を利用しても、必ずしもプレースホルダとしてSQL インジェクションを防ぐという期待通りの動作を保証している訳ではないようです。ですので、 PreparedStatement が SQL インジェクションを防ぐかどうかは JDBC Driver の実装に依存します。採用する JDBC Driver が PreparedStatement で SQL インジェクションを防げるか動作確認をする必要があります。Java + Oracle, Java + MySQL については「安全なSQLの呼び出し方」に記載があるのでご参照ください。

 

・welcome.jsp

以前はHTMLエスケープ処理をするのに、org.apache.commons.lang3.StringEscapeUtils を使用していましたが非推奨となったため、org.apache.commons.text.StringEscapeUtils を使用するように変更しています。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="org.apache.commons.text.StringEscapeUtils" %>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <h1>ようこそ、<%=StringEscapeUtils.escapeHtml4((String)session.getAttribute("id")) %>さん</h1>
</body>
</html>

 

参考

Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。


スポンサーリンク





カテゴリー:Webアプリケーションセキュリティ対策

コメントを残す

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

CAPTCHA