最近,妻がExcelを勉強し始めた。以前は教えようかと言ってもあまり積極的ではなかったのだが,多少使えるようになったので,知識を広めたいと思ったらしい。図書館で本を借りて休みの日に少しずつ進めている。一緒にいるときは,ちょこちょこ補足したりしている。やはり,何でも少しできるようにならないと,欲が出てこないようだ。

 今年度も,地元の小学校で始まったロボット・プログラミングの講習会で,小学生たちの欲が引き出せるように,成功体験を積ませてやりたいと思う。

 実は以前,受講してくれていた中学3年生が経済産業省の基本情報処理試験に合格したと報告してくれて,ロボット・プログラミング講座はやはり有意義だと意を強くしている。

 さて,今回は前回から始めたC#2005によるデータベース・プログラミングその2である。今回のテーマはSQLインジェクション対策。めずらしく硬派なテーマを取り上げてみたい。

 SQLインジェクションとは,アプリケーションのセキュリティ上の不備をついて,想定外のSQL文を実行させて,データベースシステムを不正に操作する攻撃のことをいう。主にWebアプリケーションが対象になる。

 前回の最後で紹介したADO.NETのSqlCommandを使ったデータベース・アクセスで,SQLインジェクションの例を紹介しよう。

 ユーザーID(UserID)とパスワード(Passwd)をフィールドとするUsersテーブルを作成する。

 takeda,tanakaという二人のユーザーを登録する。パスワードはそれぞれ,1a267b5uとmfixu7biとした。
 
 ログイン処理を作っていく。

 ユーザーIDとパスワードを入力するためのテキストボックスを作成する。ここではボタンを二つ作っている。上のボタン・クリック時の処理は,SQLインジェクション対策が施してないものである。下のボタンには,SQLインジェクション対策をしたコードが記述してある。listBox1は本来,ログイン処理には不要なものだが,事の重大さを伝えるために配置してある。

 先頭のusing句に以下を追加するのは前回と同様である。

-------------------------------------------------------------------
  using System.Data.SqlClient;
-------------------------------------------------------------------
 上のボタンが押されたときのコードから見ていきます。
-------------------------------------------------------------------
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
string sql = "SELECT UserID,Passwd FROM Users";
sql += " WHERE UserID = '" + txtUserID.Text + "' AND "
+ "Passwd = '" + txtPasswd.Text + "'";
SqlConnection cn = new SqlConnection();
cn.ConnectionString = "Data Source=(local)\\SQLEXPRESS;"
+ "Initial Catalog=SampDB;"
+ "Integrated Security=True;Pooling=False";
cn.Open();
SqlCommand cmd = new SqlCommand(sql, cn);
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
MessageBox.Show("ログイン成功");
while (reader.Read())
{
listBox1.Items.Add(reader.GetString(0) + " " + reader.GetString(1));
}
reader.Close();
cn.Close();
}
-------------------------------------------------------------------  

 テキストボックスに入力されたユーザーIDとパスワードをWHERE句につないで,SqlCommandオブジェクトのExecuteReaderメソッドで,SELECT文を実行する。if (reader.HasRows)でユーザーIDとパスワードに一致する行があったら,ログイン成功と表示する。そのあとで,ユーザーIDとパスワードをリストボックスに表示している。

 正しいユーザーIDとパスワードを入力すると,ログイン成功のメッセージボックスを表示する。

 OKをクリックすると,リストボックスにユーザーIDとパスワードを表示する。

 間違ったパスワードを入力すると,何も表示されない。当然だろう。

 しかし,存在しないユーザーIDと秘密の呪文'or'A'='Aを入力すると,2行ともマッチしてしまう。

 ブレークポイントを設け,展開されたSELECT文を表示させてみた。SQL文のor句に続けて,必ず真になる条件を書くことで,ユーザーIDやパスワードを知らなくてもログインできてしまう。

 もちろん,この場合はUsersテーブルから取得したユーザーID,パスワードと入力されたユーザーID,パスワードを比較することでチェックできるが,悪意のあるSQL文を入力されるとデータを破壊される可能性もある。
 
 ADO.NETの場合,SqlCommandオブジェクトのSqlParameterを使うことでSQLインジェクション対策を取ることができる。

-------------------------------------------------------------------  
  private void button2_Click(object sender, EventArgs e)
  {
  listBox1.Items.Clear();
  string sql = "SELECT UserID,Passwd FROM Users";
  sql += " WHERE UserID = @UserID AND Passwd = @Passwd";
  SqlConnection cn = new SqlConnection();
  cn.ConnectionString = "Data Source=(local)\\SQLEXPRESS;"
  + "Initial Catalog=SampDB;"
  + "Integrated Security=True;Pooling=False";
  cn.Open();
  SqlCommand cmd = new SqlCommand(sql, cn);
  cmd.Parameters.Add(new SqlParameter("@UserID", txtUserID.Text));
  cmd.Parameters.Add(new SqlParameter("@Passwd", txtPasswd.Text));
 
  SqlDataReader reader = cmd.ExecuteReader();
  if (reader.HasRows)
  MessageBox.Show("ログイン成功");
 
  while (reader.Read())
  {
  listBox1.Items.Add(reader.GetString(0) + " " + reader.GetString(1));
  }
  reader.Close();
  cn.Close();
  }
-------------------------------------------------------------------   @UserIDや@Passwdがパラメータになる。

 今度は,'or'A'='Aと入力してもログインできない。これでSQL文を注入されても,大丈夫になった。SqlParameterを使うとSQLインジェクション対策になるだけなく,シングルクォートを書かなくてもよくなるのでWHERE句がすっきりする。Webアプリケーションでなくとも,SqlParameterを使った方が便利であることがおわかりいただけるだろう。