情報処理

【SQLインジェクション】INSERT文への攻撃例

2019年11月3日

INSERT文に「SQLインジェクション攻撃」ってどうやるんですか?
良い質問だね。

本記事では「SQLインジェクション」の対策をしていないと、どのような被害に遭うのか具体的なINSERT文への攻撃例を紹介していきます。

スポンサーリンク

INSERT文への「SQLインジェクション」攻撃例

INSERT文への簡単な攻撃例を紹介します。

よくある登録画面に以下の文字列を入力して「登録」します。

  • 名前:テスト名前', (select user_password from sampledb.user_password where id='1' limit 0,1)) -- 終わり
  • 住所:任意の文字列(空でも平気)※画面例は空 
登録画面

次に登録内容を画面に表示させると、住所の所に登録していない値が表示されています

この「password01」というのは「SQLインジェクション」攻撃により情報漏洩したデータです。

登録完了画面
では、どういう仕組みで情報漏洩に繋がったのか。具体的な攻撃方法を紹介していきます。

スポンサーリンク

SQLインジェクション攻撃を検証するための事前準備

SQLインジェクション攻撃を検証するための、事前準備は以下の通りです。

※Javaで簡易的なサーブレットで作成しています。

データベース

前提条件として以下の2つのテーブルがあるとします。CREATE文は以下の通り。

◆ユーザー情報TBL(users)

CREATE TABLE `sampledb`.`users` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(100) NULL,
  `address` VARCHAR(255) NULL,
  PRIMARY KEY (`id`));

◆ユーザーパスワードTBL(user_password)

CREATE TABLE `sampledb`.`user_password` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `user_id` INT NOT NULL,
  `user_password` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`id`));

そして「user_password」テーブルには以下のデータを作成しておきます。※このデータを「SQLインジェクション」の攻撃で盗み取ることを目的とします。

※通常パスワードは「ハッシュ化」してDBに格納するのが一般的ですが、ここではあえて平文で登録しています。

iduser_iduser_password
11password01

HTML画面

「登録画面」と「登録完了画面」を用意。「登録画面」で入力し登録された結果を「登録完了画面」に表示する流れです。

◆登録画面イメージ図

登録画面

◆登録画面ソースコード

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登録画面</title>
</head>
<body>
<form action="/SampleProject/AddServlet" method="post">
名前:<input type="text" name="name" size="100"/><br />
住所:<input type="text" name="address" size="100" /><br />
<input type="submit" value="登録" />
</form>
</body>
</html>

◆登録完了画面イメージ図

登録完了画面イメージ図

◆登録完了画面ソースコード

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
  // Servletのデータ受け取り
  request.setCharacterEncoding("UTF-8");
  String id = (String) request.getAttribute("id");
  String name = (String) request.getAttribute("name");
  String address = (String) request.getAttribute("address");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登録完了</title>
</head>
<body>
以下の内容で登録されました。<br />
ID:<%=id%><br />
名前:<%=name%><br />
住所:<%=address%><br /><br />
<a href="http://localhost:8080/SampleProject/add.jsp">登録画面へ</a>
</body>
</html>

サーブレット

◆登録用サーブレット

package servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
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 dao.UsersDao;
import entity.Users;

/**
 * 登録処理サーブレット
 */
@WebServlet("/AddServlet")
public class AddServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

    public AddServlet() {
        super();
    }

    /**
     * 登録処理
     */
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html; charset=UTF-8");

    String name = request.getParameter("name");
    String address = request.getParameter("address");

    UsersDao dao = new UsersDao();

    // データ登録
    String id = dao.add(name, address);

    if (id != null) {
      // 登録したデータを抽出
      Users users = dao.getOne(id);

      request.setAttribute("id", users.getId());
      request.setAttribute("name", users.getName());
      request.setAttribute("address", users.getAddress());
    }

    // 画面遷移
    RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp");
        dispatcher.forward(request, response);
  }
}

◆エンティティ

package entity;

import java.io.Serializable;

public class Users implements Serializable {

  private String id;
  private String name;
  private String address;

  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getAddress() {
    return address;
  }
  public void setAddress(String address) {
    this.address = address;
  }
}

◆データアクセス

package dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import entity.Users;

public class UsersDao {

  private Connection getConnection() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/sampledb?characterEncoding=UTF-8&serverTimezone=Asia/Tokyo","root","password");
    	conn.setAutoCommit(false);
    	return conn;
  }

  /**
     * データ取得
     */
  public Users getOne(String id) {
    Users users = null;

    try {
      Connection conn = null;
      PreparedStatement ps = null;
      ResultSet rs = null;

          try {
          	conn = getConnection();

              String sql = "SELECT * FROM sampledb.users WHERE id = '" + id + "';";

              ps = conn.prepareStatement(sql);
              rs = ps.executeQuery();

              if (rs.next()) {
              	users = new Users();
              	users.setId(rs.getString("id"));
              	users.setName(rs.getString("name"));
              	users.setAddress(rs.getString("address"));
              }

          } catch (SQLException e){
          	if (conn != null) {
          		conn.rollback();
          	}
          	System.out.println(e);
          }

          finally {
          	if (conn != null) {
          		conn.close();
          	}
          	if (ps != null) {
          		ps.close();
          	}
          	if (rs != null) {
          		rs.close();
          	}

          }
    } catch (Exception e){
      System.out.println(e);
    }

    return users;
  }

  /**
     * データ登録
     */
  public String add(String name, String address) {
    String autoIncKey = null;

    try {
      Connection conn = null;
      PreparedStatement ps = null;
      ResultSet rs = null;

          try {
          	conn = getConnection();

              String sql = "INSERT INTO users (name,address) VALUES ('" + name + "','" + address +"')";

              ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
              ps.executeUpdate();

              rs = ps.getGeneratedKeys();
              if (rs.next()) {
              	autoIncKey = String.valueOf(rs.getInt(1));
              }

              conn.commit();

          } catch (SQLException e){
          	if (conn != null) {
          		conn.rollback();
          	}
          	System.out.println(e);
          }

          finally {
          	if (conn != null) {
          		conn.close();
          	}
          	if (ps != null) {
          		ps.close();
          	}
          	if (rs != null) {
          		rs.close();
          	}
          }
    } catch (Exception e){
      System.out.println(e);
    }

    return autoIncKey;
  }
}

スポンサーリンク

INSERT文への「SQLインジェクション」攻撃の解説

ここから具体的にINSERT文への「SQLインジェクション」攻撃について解説していきます。
登録画面

登録画面で以下のデータを入力し登録します。

  • 名前:テスト名前', (select user_password from sampledb.user_password where id='1' limit 0,1)) -- 終わり
  • 住所:任意の文字列(空でも平気)※画面例は空 

ユーザー情報TBL(users)へのINSERT文は以下です。

INSERT INTO users (name,address) VALUES ('" + name + "','" + address +"')

このINSERT文に画面から入力されたデータを代入すると以下となります。

INSERT INTO users (name,address) VALUES ('テスト名前', (select user_password from sampledb.user_password where id='1' limit 0,1)) -- 終わり','')
  1. まず「カラム:name」には「テスト名前」が登録されます。
  2. 次に「カラム:address」には「(select user_password from sampledb.user_password where id='1' limit 0,1)」という文字列が入っています。この文字列は、ユーザーパスワードTBL(user_password)から「id = 1」の条件と一致する「user_password」を取得するサブクエリです。
  3. その結果、「カラム:address」には「password01」が登録されます。
  4. そして、INSERT文の最後にある「-- 終わり','')」という文字列。これは「-- 」から後ろはコメントアウトという意味です。

結果、実際に実行されたINSERT文(コメントアウト部分を除く)は、以下と同じです。

INSERT INTO users (name,address) VALUES ('テスト名前', (select user_password from sampledb.user_password where id='1' limit 0,1))

そして、登録内容を閲覧する画面で登録データを見ると、住所の所に「password01」が表示される流れです。

登録完了画面

これで、他人の個人情報(※今回の例はパスワード)を盗み取る事に成功

このように「テーブル名」や「カラム名」が分かっていれば「サブクエリ」を使い簡単に個人情報を盗み取る事が可能であり、深刻な被害にあう危険性があるのです。

怖いですね。SQLインジェクションの対策は大切なんですね。
対策
SQLインジェクションとは | 具体的な攻撃例と対策方法
SQLインジェクションとは | 具体的な攻撃例と対策方法

続きを見る

helpful