SQLインジェクション(injection)

injectionは「注入」の意味です。つまり不正なSQLコマンドを注入する攻撃です。
データベースを使うWebアプリケーションなどで、想定しないSQL文を実行させることで、 データベースシステムを不正に操作するという事件が起きています。
それは、攻撃を想定した対処を怠った脆弱性のあるシステムの場合に起こります。

SQLインジェクションができてしまう危険なphpプログラムの具体例

以下で、実際に実験できます。

ユーザーIDとパスワードを入力ください。

ユーザーID

パスワード

左のフォームは、次のように作られています。
<form action="e71056a.php" method="post" target="blank">
<h1>ユーザーIDとパスワードを入力ください。</h1>
<p>  ユーザーID<input  type="text" name="memberID" value="s102031" size="20"><br>
  パスワード<input type="password" name="passwd" value="abcxyz" size="20"><br>
  <input type="submit"  value="ログイン"></p>
</form>
実験しやすいように、ユーザ名とパスワードが入力済みになっています。
パスワードに、ある入力を行うSQLインジェクションを行ってみましょう。

(利用しているデータベースの表は、以下のような操作で作成しています。使用している表は、 ここで作成確認できます。)

実験で用意するテーブル作成手順

このページで使うデータベースを準備する手順を示します。
STEP 1
root権限で、goodsデータベースを次のコマンドで作成
mysql> CREATE DATABASE goods;

root権限で、ローカルアクセス「usrgd」のユーザーを「g7d5」のパスワードで作成
mysql> GRANT ALL PRIVILEGES ON goods.* TO 'usrgd'@'127.0.0.1' IDENTIFIED BY 'g7d5';

STEP 2
上記で作成した「usrgd」のユーザーでログインし直します
mysql --user=usrgd --password=g7d5 goods
mysql>


次の「memberTbl」テーブルを作成します。
create table memberTbl(
memberID char(8) not null,
lastName char(10),
firstName char(10),
city char(10),
address char(20),
tel char(15),
mail char(20),
passwd char(10),
primary key (memberID)
);

jspの具体的なソース例

次の構成の中で、p_authorize.jspの処理で、 SQLインジェクションができます。

ファイル 動作概要 ソース
MySQLのデータベースgoods 各種データの記憶 テーブル作成などの作成情報
p_display.jsp 全ユーザーリストの表示 ソース
p_addition.htm 新規データ入力(ボタンで下記のp_inert.jspを実行) ソース
p_inert.jsp 上記の新規データで、データーベースへ追加 p_inert.jspのソース
p_login.html ユーザーログインキー入力 ソース
p_authorize.jsp 上記ログインデータで認証判定 p_authorize.jspのソース

p_display.jspのソース内容

<HTML><BODY>
<%@ page 
  contentType="text/html; charset=Shift_JIS"
  import="java.sql.*"
%>
<%
	try {
		Class.forName("org.gjt.mm.mysql.Driver").newInstance();
				// mysql-connector-java-5.1.7-bin.jar コネクタで、mysql5.0.67使用
				//「?useUnicode=true&characterEncoding=SJIS」で日本語有効の接続指定
		String connectStr = "jdbc:mysql://127.0.0.1:3306/goods?useUnicode=true&characterEncoding=SJIS";
		Connection dbConn = DriverManager.getConnection(connectStr,"usrgd","g7d5");
		
		Statement st = dbConn.createStatement();//静的 SQL 文を実行用オブジェクト取得

		ResultSet rs = st.executeQuery("SELECT * FROM memberTbl");
		
		ResultSetMetaData meta = rs.getMetaData();	//メタデータ取得
		int columnNumber = meta.getColumnCount();	//列数を取得
		
		String column[] = new String[columnNumber];	//列名記憶用
		
		out.println("<table border><caption>memberTbl 表の内容</caption>\n");//表作成

		out.println("<tr>");
		for(int i = 0; i < columnNumber; i++){		//列名
			column[i] = meta.getColumnName(i+1);
			out.println("<td>" + column[i] + "</td>");
		}
		out.println("</tr>\n");

		while (rs.next()) {	//結果のレコード集合を列ごとに処理する繰り返し
			out.println("<tr>");
			for(int i = 0; i < column.length; i++){
				byte []a = rs.getBytes(column[i]);
				String data = "";
				// for( int k = 0; k < a.length; k++)data += String.format("%2x ", a[k]);
				data = new String(a, "Shift_JIS");//Shift_JIS,UTF-8,UTF-16,UTF-16BE,UTF-16LE,ISO-8859-1
				out.println("<td>" + data + "</td>");
			}
			out.println("</tr>\n");
		}

		out.println("</table>\n");
		st.close();
		dbConn.close();

		out.println("<br><br><br><a href='index.html'>index.htmlに戻ります。</a>\n");
	}
	catch ( Exception e) {
		e.printStackTrace();
	}
%>
</BODY></HTML>

p_addition.htmのソース内容

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
	<title>新規ユーザー追加</title>
	<style type="text/css">
	span.WB { font-family: monospace; background-color: black; color: white; }
	pre.BW { border-style: solid; border-width: 1px;}
	body { font-size: 14pt; margin-left: 10%; font-family: monospace;}
	</style>
</head>
<body>

<h1 align="center">新規ユーザー追加</h1>

<form action="p_inert.jsp"
method="post" accept-charset="Shift_JIS">

各内容を適当に変更して送信ボタンをクリックください。<br>
(既に使われているIDの場合は登録できません)<br>

<br>
<table>
<tr>
 <td>ID</td>
 <td><input type="text" name="memberID" value="s987654" size="20"></td>
</tr><tr>
 <td>姓</td>
 <td><input type="text" name="lastName" value="yamada" size="20"></td>
</tr><tr>
 <td>名</td>
 <td><input type="text" name="firstName" value="tarou" size="20"></td>
</tr><tr>
 <td>市、区</td>
 <td><input type="text" name="city" value="tokyo" size="20"></td>
</tr><tr>
 <td>住所</td>
 <td><input type="text" name="address" value="sinjuku" size="40"></td>
</tr><tr>
 <td>TEL</td>
 <td><input type="text" name="tel" value="110" size="30"></td>
</tr><tr>
 <td>mail</td>
 <td><input type="text" name="mail" value="ta@hotmail.com" size="30"></td>
</tr><tr>
 <td>password</td>
 <td><input type="password" name="passwd" value="abcxyz" size="20"></td>
</tr>
</table>

<input type="submit" value="送信">

</form>
<p align="right">
<a href='index.html'>index.htmlに戻ります</a>
</p>
</body>
</html>

p_inert.jspのソース内容

<HTML><BODY>
<%@ page 
  contentType="text/html; charset=Shift_JIS"
  import="java.sql.*"
%>
<%
	try{
		//入力データの取得
		String memberID = request.getParameter("memberID");//顧客ID
		String lastName = request.getParameter("lastName");//姓
		String firstName = request.getParameter("firstName");//名
		String city = request.getParameter("city");//市、区
		String address = request.getParameter("address");//住所
		String tel = request.getParameter("tel");//TEL
		String mail = request.getParameter("mail");//mail
		String passwd = request.getParameter("passwd");//passwd

		lastName = new String(lastName.getBytes("iso-8859-1"),"Shift_JIS");//JISAutoDetect
		firstName = new String(firstName.getBytes("iso-8859-1"),"Shift_JIS");
		city = new String(city.getBytes("iso-8859-1"),"Shift_JIS");
		address = new String(address.getBytes("iso-8859-1"),"Shift_JIS");

		out.println(memberID + "<br>");
		out.println(lastName + "<br>");
		out.println(firstName + "<br>");
		out.println(city + "<br>");
		out.println(address + "<br>");
		out.println(tel + "<br>");
		out.println(mail + "<br>");
		out.println(passwd + "<br>");

		//SQL作成
		String sql="INSERT INTO memberTbl(memberID,lastName,firstName,city,address,tel,mail,passwd) VALUES (";
		sql+="'" + memberID + "',";
		sql+="'" + lastName + "',";
		sql+="'" + firstName + "',";
		sql+="'" + city + "',";
		sql+="'" + address + "',";
		sql+="'" + tel + "',";
		sql+="'" + mail + "',";
		sql+="'" + passwd + "');";
		
		//sql = "INSERT INTO memberTbl(memberID,lastName,firstName,city,address,tel,mail,passwd) VALUES ('s9876','山田','太郎','東京','文京区','110','ta@hotmail.com','abcxyz');";
		
		Class.forName("org.gjt.mm.mysql.Driver").newInstance();
				// mysql-connector-java-5.1.7-bin.jar コネクタで、mysql5.0.67使用
				//「?useUnicode=true&characterEncoding=SJIS」で日本語有効の接続指定
		String connectStr = "jdbc:mysql://127.0.0.1:3306/goods";
		Connection dbConn = DriverManager.getConnection(connectStr,"usrgd","g7d5");
		
		Statement st = dbConn.createStatement();//静的 SQL 文を実行用オブジェクト取得

		out.println("<pre>次のSQL文を使いました。\n");
		out.println( sql );
		out.println("</pre>");

		int r;
		//SQL 文を実行し、結果を得る
		r = st.executeUpdate( sql );
		
		if(r == 1){
			out.println( "上記データが追加されました。" );
		} else {
			out.println( "上記データは追加できませんでした。「" + memberID + "」のIDが既に使われています。" );
		}

		out.println("<br><br><br><a href='index.html'>index.htmlに戻ります。</a>\n");
		
		st.close();
		dbConn.close();
	  
	}
	catch ( Exception e) {
		out.println( e.toString() + "<br>" ); 
		out.println( e.getMessage() ); 
		e.printStackTrace();
	}
%>
</BODY></HTML>

p_login.htmlのソース内容

<!DOCUMENT HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML Lang="ja">
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>login Page</TITLE>
	<style type="text/css">
	body { font-size: 14pt; margin-left: 10%; font-family: monospace;}
	</style>


</HEAD>
<BODY bgcolor="lightblue" text="black" Link="navy" vLink="navy">
<FORM ACTION="p_authorize.jsp" METHOD="POST">

<h1>ユーザーIDとパスワードを入力ください。</h1>

<p>
  ユーザーID<INPUT TYPE="TEXT" NAME="memberID" value="" size="20"><BR>
  <BR>
  パスワード<INPUT TYPE="password" NAME="passwd" value="" size="20"><BR>
  <BR>
  <INPUT TYPE="SUBMIT" NAME="転送" VALUE="ログイン">
</p>
<p align="right">
<a href='index.html'>index.htmlに戻ります</a>
</p>

</FORM>
</BODY>
</HTML>

p_authorize.jspのソース内容

<HTML><BODY>
<%@ page 
  contentType="text/html; charset=Shift_JIS"
  import="java.sql.*"
%>
<%
	try{
		//入力データの取得
		String memberID = request.getParameter("memberID");//顧客ID
		String passwd = request.getParameter("passwd");//passwd

		//SQL作成
		String sql="SELECT * FROM memberTbl WHERE ";
		sql+="memberID ='" + memberID; 
		sql+="' AND passwd = '" + passwd + "';";
		
		out.println("<pre>次のSQL文を使いました。\n");
		out.println( sql );
		out.println("</pre>");
		
		Class.forName("org.gjt.mm.mysql.Driver").newInstance();
				// mysql-connector-java-5.1.7-bin.jar コネクタで、mysql5.0.67使用
				//「?useUnicode=true&characterEncoding=SJIS」で日本語有効の接続指定
		String connectStr = "jdbc:mysql://127.0.0.1:3306/goods";
		Connection dbConn = DriverManager.getConnection(connectStr,"usrgd","g7d5");
		
		Statement st = dbConn.createStatement();//静的 SQL 文を実行用オブジェクト取得

		ResultSet rs = st.executeQuery( sql );

		if(rs.next()){
			ResultSetMetaData meta = rs.getMetaData();	//メタデータ取得
			int columnNumber = meta.getColumnCount();	//列数を取得
			
			String column[] = new String[columnNumber];	//列名記憶用
			
			out.println("<table border><caption>認証できました。</caption>\n");//表作成
	
			out.println("<tr>");
			for(int i = 0; i < columnNumber; i++){		//列名
				column[i] = meta.getColumnName(i+1);
				out.println("<td>" + column[i] + "</td>");
			}
			out.println("</tr>\n");
	
			do {//結果のレコード集合を列ごとに処理する繰り返し
				out.println("<tr>");
				for(int i = 0; i < column.length; i++){
					byte []a = rs.getBytes(column[i]);
					String data = "";
					// for( int k = 0; k < a.length; k++)data += String.format("%2x ", a[k]);
					data = new String(a, "Shift_JIS");//Shift_JIS,UTF-8,UTF-16,UTF-16BE,UTF-16LE,ISO-8859-1
					out.println("<td>" + data + "</td>");
				}
				out.println("</tr>\n");
			}while (rs.next());
			out.println("</table>\n");
		} else {
			out.println("認証できません!\n");
		}

		out.println("<br><br><br><a href='index.html'>index.htmlに戻ります。</a>\n");
		
		st.close();
		dbConn.close();
	  
	}
	catch ( Exception e) {
		out.println( e.toString() + "<br>" ); 
		out.println( e.getMessage() ); 
		e.printStackTrace();
	}
%>
</BODY></HTML>
なお、不具合が生じるパスワードのキーワードの例は、『' OR 1='1』です。