以前の記事で、
プリペアードステートメントを使うと、
文字がエスケープされて、
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 | <a href="test">test</a> | | 5 | <a href="test">test</a> | +------+-----------------------------------------------+
エスケープをした方は、
「<」が「<」、
「”」が「"」
などに置き換えて挿入されています。
それで、htmlに表示させた時には、
ただの文字として表示されると。
要は、「<」とかは、
SQLでDBに挿入する分には問題ないから、
プリペアードステートメントでは処理しないけど、
それをそのままhtmlに表示させると、
タグが効いてしまうから、エスケープ関数が必要。
ってことですね。
逆に、DBから読みだしたデータを、
htmlタグとして使いたい場合は、
「<a>」とかの形で入れておくといいのでしょう。
まあ、フォームで送ったデータをタグとして使う事って、
あんまりないと思いますけど・・・