Webセキュリティの小部屋

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

Like 句に対する SQL インジェクション対策(PHP編)

概要

SQL インジェクションは、プレースホルダを利用することで対策が可能ですが、Like 句のワイルドカードついては、ほとんどの情報元が「別途エスケープが必要です」としか記載されていません。

この記事では、MySQL に対するワイルドカードのエスケープ方法の、PHP + PDO + MySQL と PHP + MDB2 + MySQL の2種類の実装を例示します。

PHP の 5.4系、5.5系は今後 PDO の採用を推奨します。5.3系では、状況に合わせて適切な方を選択してください。

PDO と MDB2 の詳細については、以下の記事を参照してください。

サンプルアプリケーションの概要は、以下の通りです。

search

MySQL のワイルドカードのエスケープ

MySQL のワイルドカードは、以下の 2 つになります。

%0個以上の文字
_1文字

MySQL :: MySQL 5.1 リファレンスマニュアル :: 11.3.1 文字列比較関数

MySQL では、以下の SQL 文のように ESCAPE 句がない場合は、「\」がエスケープ文字と仮定されます。しかし、SQL の規格(ISO および JIS)では ESCAPE 句がない場合はエスケープ文字が定義されないので、ESCAPE 句は必ず書くことを推奨します(「体系的に学ぶ 安全なWebアプリケーションの作り方」より)。

WHERE NAME LIKE '%#%%' ESCAPE '#'

PDO 版

・input.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>名前検索</h1>
  <form action="search.php" method="post">
    名前:<input type="text" name="name">
    <input type="submit" value="検索">
  </form>
</body>
</html>

・search.php

<?php
define("USERNAME", "user01");
define("PASSWORD", "pass");

  session_start();
 
  $name = $_POST["name"];
   
  try{ 
    //文字エンコーディングを必ず指定する
    $dbh = new PDO("mysql:host=192.168.11.8;dbname=Test;charset=utf8", USERNAME, PASSWORD);

    // 静的プレースホルダを指定
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  

    //プレースホルダで SQL 作成
    $sql = "SELECT ID,NAME FROM USERS WHERE NAME LIKE ? ESCAPE '#';";

    $stmt = $dbh->prepare($sql);
    $stmt->setFetchMode(PDO::FETCH_ASSOC);
    
    //パラメーターをエスケープしてバインド
    $stmt->bindParam(1, escapeString($name), PDO::PARAM_INT);

    $stmt->execute();

    //結果格納配列
    $result = array();

    //検索結果格納
    while ($row = $stmt->fetch()) {
      $result += array($row['ID'] => $row['NAME']);
    }

    $stmt = null;

  } catch(PDOException $e){
    echo $e->getMessage();
  }
 
  if (count($result) > 0) {
   //検索結果をセッションに格納
   $_SESSION['result'] = $result;
 
    //リダイレクト
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: result.php");
  } else {
   //リダイレクト
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: input.php");
  }
 
  
  function escapeString($s) {
    //ワイルドカードをエスケープ
    return "%" . mb_ereg_replace('([_%#])', '#\1', $s) . "%";
  }
?>

・result.php

<?php 
  session_start();
  $result = $_SESSION['result'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>検索結果</h1>
<?php
  //検索結果出力
  foreach ($result as $id => $name) {
    echo htmlspecialchars($id . ":" . $name, ENT_QUOTES, "UTF-8") . "<br/>";
  }
  
  //セッション変数クリア
  unset($_SESSION['result']);
?>
</body>
</html>

MDB2 版

・input.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>名前検索</h1>
  <form action="search.php" method="post">
    名前:<input type="text" name="name">
    <input type="submit" value="検索">
  </form>
</body>
</html>

・search.php

<?php
  require_once 'MDB2.php';
  session_start();

  $name = $_POST["name"];

  //DB接続
  $db = MDB2::connect("mysql://user01:pass@localhost/Test?charset=utf8");
  
  if (MDB2::isError($db)) {
    throw new exception("DB connection error.");
  }
  
  //プレースホルダで SQL 作成
  $sql = "SELECT ID,NAME FROM USERS WHERE NAME LIKE ? ESCAPE '#';";
  
  //パラメーターの型を指定
  $stmt = $db->prepare($sql, array('text'));
  
  //パラメーターをエスケープして SQL 実行
  $rs = $stmt->execute(array(escapeString($name)));
  
  //結果格納配列
  $result = array();

  //検索結果格納
  while ($row = $rs->fetchRow(MDB2_FETCHMODE_ASSOC)) {
      $result += array($row['id'] => $row['name']);
  }

  //DB接続切断
  $db->disconnect();

  if (count($result) > 0) {
  	//検索結果をセッションに格納
  	$_SESSION['result'] = $result;

    //リダイレクト
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: result.php");
  } else {
  	//リダイレクト
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: input.php");
  }

  
  function escapeString($s) {
    //ワイルドカードをエスケープ
    return "%" . mb_ereg_replace('([_%#])', '#\1', $s) . "%";
  }
?>

・result.php

<?php 
  session_start();
  $result = $_SESSION['result'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>検索結果</h1>
<?php
  //検索結果出力
  foreach ($result as $id => $name) {
    echo htmlspecialchars($id . ":" . $name, ENT_QUOTES, "UTF-8") . "<br/>";
  }
  
  //セッション変数クリア
  unset($_SESSION['result']);
?>
</body>
</html>

参考

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


スポンサーリンク





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

コメントを残す

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

CAPTCHA