[PHP] プリペアードステートメントを使ってもエスケープ処理は必要か?

以前の記事で、
プリペアードステートメントを使うと、
文字がエスケープされて、
SQLインジェクション対策になる。
的なことを書いたんですが、

プリペアードステートメントを深堀り~SQLインジェクション対策

PHPで、文字列をエスケープする関数って、
あるじゃないですか。
例えば、”htmlspecialchars“とか。

これって、POSTデータとかをDBに挿入するときは、
プリペアードステートメントを使えば、別に要らないんじゃね?
と思うんですが・・・

↑↑を使った時と使わない時で、
何が違うのか見ていきたいと思います。

●テスト環境

DBのテーブルはこんな感じ。

MariaDB [test]> show columns from test1;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | int(11)      | YES  |     | NULL    |       |
| name  | varchar(100) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+

MariaDB [test]> select * from test1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | name1 |
|    2 | name2 |
|    3 | name3 |
+------+-------+

プログラムをこんな感じに作りました。
フォームで文字列を送って、DBに挿入し、
挿入したデータを表示させるプログラム。

(フォーム側:from.php)
<form method="post" action="sql_injection.php">
	<input type="text" name="value">
	<input type="submit" value="送信">
</form>

(処理側:sql_injection.php)
<?php
$id = 4; //エスケープするデータ用
//$id = 5; //エスケープしないデータ用
$value = htmlspecialchars($_POST['value'], ENT_QUOTES, 'UTF-8');
//$value = $_POST['value'];

$db = new TestDB();
$db->insert_value($id,$value);
$row = $db->get_value($id);

print("挿入しました。<br />\n");
print($row['name']."<br />\n");
print("<a href=\"form.php\">戻る</a><br />\n");

class TestDB {

	private $pdo;
	const DSN = 'mysql:dbname=test; host=localhost; charset=utf8';
	const USER = 'testuser';
	const PASS = 'password';
	
	public function __construct(){
		try {
			$this->pdo = new PDO(self::DSN, self::USER, self::PASS);
			$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		} catch(PDOException $Exception) {
			die('エラー:' . $Exception->getMessage());
		}
	}
	
	public function insert_value(int $id, string $value){
		try {
			$sql = "INSERT into test1 values (:id, :value)";
			$stt = $this->pdo->prepare($sql);
			$stt->bindvalue('id', $id);
			$stt->bindvalue('value', $value);
			$stt->execute();
		} catch(PDOException $Exception) {
			die('エラー:' . $Exception->getMessage());
		}
	}
	
	public function get_value(int $id){
		try {
			$sql = "SELECT * from test1 where id=:id";
			$stt = $this->pdo->prepare($sql);
			$stt->bindvalue('id', $id);
			$stt->execute();
			
			$row = $stt->fetch(PDO::FETCH_ASSOC);
			
		} catch(PDOException $Exception) {
			die('エラー:' . $Exception->getMessage());
		} finally {
			return $row;
		}
	}
	
}

●動作確認

エスケープあり

上記環境で、フォームに以下のように入力します。

で、「送信」押す。

と、結果はこのように表示されます。

入力した文字通りに表示される。

エスケープなし

では、エスケープしなかった場合、
↑と同じようにフォームに入力して送信すると、

リンクになりましたねw

実際に、DBに挿入されたデータを見てみると、

MariaDB [test]> select * from test1;
+------+-----------------------------------------------+
| id   | name                                          |
+------+-----------------------------------------------+
|    1 | name1                                         |
|    2 | name2                                         |
|    3 | name3                                         |
|    4 | &lt;a href=&quot;test&quot;&gt;test&lt;/a&gt; |
|    5 | <a href="test">test</a>                       |
+------+-----------------------------------------------+

エスケープをした方は、
「<」が「&lt;」、
「”」が「&quot;」
などに置き換えて挿入されています。

それで、htmlに表示させた時には、
ただの文字として表示されると。

要は、「<」とかは、
SQLでDBに挿入する分には問題ないから、
プリペアードステートメントでは処理しないけど、

それをそのままhtmlに表示させると、
タグが効いてしまうから、エスケープ関数が必要。
ってことですね。

逆に、DBから読みだしたデータを、
htmlタグとして使いたい場合は、
「<a>」とかの形で入れておくといいのでしょう。

まあ、フォームで送ったデータをタグとして使う事って、
あんまりないと思いますけど・・・

コメントを残す

メールアドレスが公開されることはありません。