Webセキュリティの小部屋

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

SQLインジェクション対策(PHP編)

SQL インジェクションの対策は、簡単に言えば SQL で静的プレースホルダを使用することです。

静的プレースホルダの詳細については、以下の記事を参照してください。

では、PHP で SQL インジェクション対策を実際にどのように行うのか簡単なログイン処理で具体的に見てみましょう。

ログイン処理の概要は以下の通りです。

login

なお、これは簡単なログイン処理のため、ログインにまつわるセキュリティ対策は省いています。ログインのセキュリティ対策は、以下の記事を参照してください。

動作環境は、PHP + PDO + MySQL と PHP + MDB2 + MySQL です。以前は MDB2 を推奨していましたが、今後の開発では PDO を採用することを推奨します。

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

接続先のデータベース名は Test、テーブル名は LOGIN で、列名は ID, PASSWORD です。接続文字列で UTF-8 を指定していますが、PHP + MySQL では、これがセキュリティ上重要です(文字コードは全て UTF-8 で統一)。

PDO 版

・login.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>ログイン</h1>
  <form action="logincheck.php" method="post">
    ID:<input type="text" name="id"><br>
    Password:<input type="password" name="password"><br>
    <input type="submit" value="ログイン">
    <input type="reset" value="リセット">
  </form>
</body>
</html>

・logincheck.php

<?php
  session_start();
  
  //パラメーター取得
  $id = $_POST['id'];
  $password = $_POST['password'];
  
  $count = 0;
  
  try{ 
    //文字エンコーディングを必ず指定する
    $dbh = new PDO("mysql:host=192.168.11.8;dbname=Test;charset=utf8", "user01", "pass", 
         array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
               PDO::MYSQL_ATTR_MULTI_STATEMENTS => false,
               PDO::ATTR_EMULATE_PREPARES => false));

    $stmt = $dbh->prepare("SELECT COUNT(*) AS CNT from LOGIN WHERE ID = ? AND PASSWORD = ?;");
    $stmt->setFetchMode(PDO::FETCH_ASSOC);

    $stmt->bindParam(1, $id, PDO::PARAM_STR);
    $stmt->bindParam(2, $password, PDO::PARAM_STR);

    $stmt->execute();

    while ($row = $stmt->fetch()) {
      $count = $row["CNT"];
    }

    $stmt = null;

  } catch(PDOException $e){
    echo $e->getMessage();
  }
  
  $_SESSION['id'] = $id;
  
  if ($count == 1) {
   //ログイン成功
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: welcome.php");
  } else {
   //ログイン失敗
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: login.php");
  }
 
?>

・welcome.php

<?php
  session_start();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>ようこそ、<?php echo htmlspecialchars($_SESSION['id'], ENT_QUOTES, "UTF-8") ?>さん</h1>
</body>
</html>

MDB2 版

・login.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>ログイン</h1>
  <form action="logincheck.php" method="post">
    ID:<input type="text" name="id"><br>
    Password:<input type="password" name="password"><br>
    <input type="submit" value="ログイン">
    <input type="reset" value="リセット">
  </form>
</body>
</html>

・logincheck.php

<?php
  require_once 'MDB2.php';
  session_start();
  
  //パラメーター取得
  $id = $_POST['id'];
  $password = $_POST['password'];
  
  $count = 0;
  
  //DB接続
  $db = MDB2::connect('mysql://user01:pass@localhost/Test?charset=utf8');
  
  if (MDB2::isError($db)) {
    throw new exception("DB connection error.");
  }
  
  //プレースホルダで SQL 作成
  $sql = "SELECT COUNT(*) AS CNT FROM LOGIN WHERE ID = ? AND PASSWORD = ?;";
  
  //パラメーターの型を指定
  $stmt = $db->prepare($sql, array('text','text'));
  
  //パラメーターを渡して SQL 実行
  $rs = $stmt->execute(array($id, $password));
  
  while ($row = $rs->fetchRow(MDB2_FETCHMODE_ASSOC)) {
  	$count = $row['cnt'];
  }
  
  $db->disconnect();
  
  $_SESSION['id'] = $id;
  
  if ($count == 1) {
  	//ログイン成功
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: welcome.php");
  } else {
  	//ログイン失敗
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: login.php");
  }

?>

・welcome.php

<?php
  session_start();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>ようこそ、<?php echo htmlspecialchars($_SESSION['id'], ENT_QUOTES, "UTF-8") ?>さん</h1>
</body>
</html>

参考

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


スポンサーリンク





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

コメントを残す

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

CAPTCHA