ここでは、プログラミングには欠かせない「サニタイズ(無害化)」について、解説をしていきます。
サニタイズってなに?
サニタイズとは「消毒する」「無害化する」という意味の英語で、サニタイズの一種に「エスケープ」というものがあります。
大抵はサニタイズ=エスケープとして捉えている事がほとんどです。
エスケープってなに?
PHPでは<>'"&
などの文字を変数値としてそのまま使用することができますが、これをHTMLにそのまま出力すると、HTMLタグなどの特殊文字として解釈されてしまいます。
例えばエディターで下記のようなコードを作成して画面に表示させてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php $html = '<h2>あいうえお</h2>'; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>プログラミング初心者向け「サニタイズ」まとめ|サンプル1</title> </head> <body> <h1>プログラミング初心者向け「サニタイズ」まとめ|サンプル1</h1> <?php echo $html; ?> </body> </html> |
そうすると
1 |
<h2>あいうえお</h2> |
の文字列がそのまま表示されずにhtmlとして解釈されてh2タグとブラウザに認識されて「あいうえお」の文字が太字で表示されるはずです。
これは、自分で文字を入れているからいいですが、もしこれが入力フォームからユーザーが入力した値をそのまま表示するコードだったらどうなるでしょう?
今度は、下記のコードを画面に表示して、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php $html = $_POST['data']; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>プログラミング初心者向け「サニタイズ」まとめ|サンプル2</title> </head> <body> <h1>プログラミング初心者向け「サニタイズ」まとめ|サンプル2</h1> <?php echo $html; ?> <form method="post" action=""> <input type="text" value="" name="data"> <input type="submit" value="送信"> </form> </body> </html> |
入力フォームにさっきの
1 |
<h2>あいうえお</h2> |
を入力して送信ボタンを押してみましょう。
同じようにh2タグとして認識されちゃいましたね?
次にJavaScriptのコードを入力フォームに入力してみましょう。
1 |
<script>alert('ok');</script> |
画面にアラートウィンドウが出て「ok」と表示されましたか?
※GoogleChromeブラウザだと「このページは動作していません」という画面が表示されてページがブロックされてしまう場合があります。本来だと画面にアラートウィンドウが表示されるんですが、最近のブラウザは賢くなっていてそういった勝手な処理をブラウザが自動で見つけてくれてブロックしてしまったんですね。
こういったものは自分の中だけで完結しているからいいですが、SNSのように自分が投稿した内容が他の人も見れるとしたらどうでしょうか?
入力されたままのものがDBにそのまま保存され、それをそのまま他のユーザーが見る。
そうすると相手のブラウザにアラートを出まくったり、相手のブラウザの中にある個人情報などを勝手に取得して外部に送信する。なんてハッカーの真似事もできてしまうのです。
(例えば、悪意のある人がSNSのプロフィールにjsの処理を入れておき、誰かがそのプロフィールを見たらjsの処理が開始されてしまいます。もし、そのjsに「閲覧した人のCookieなり個人情報を悪意のある人のDBに保存する」といった処理が書かれていた場合、簡単に情報を盗めてしまいます。※Cookieがまだ分からない方はWEBサービス部でやります)
そういったセキュリティのために「サニタイズの一環」として「エスケープ」という処理を行います。
エスケープは「特殊な文字を無害な文字に強制的に置き換えてしまう」という手法です。
エスケープのやり方
先ほども触れたようにPHPでは<>'"&
などの文字を変数値としてそのまま使用することができますが、これをHTMLにそのまま出力すると、HTMLタグなどの特殊文字として解釈されてしまいます。
これをHTMLとして認識させず、入力した値と同じものとして見せるために、出力する値をHTMLエスケープする必要があります。
HTMLのタグとして認識させないように文字を置き換えるので「HTMLエスケープ」という名前になります。
PHPにはそのための専用関数htmlspecialchars()が存在します。
1 |
<span class="nx">ようこそ</span><span class="err">、</span><span class="o"><?=</span><span class="nb">htmlspecialchars</span><span class="p">(</span><span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'name'</span><span class="p">],</span> <span class="nx">ENT_QUOTES</span><span class="p">,</span> <span class="s1">'UTF-8'</span><span class="p">);</span> <span class="cp">?></span>さん。 |
このように、出力先にかかわらず、エスケープは必ず、出力を行うその時点で行います。
ただし、最近はフレームワークを使うのが普通です。フレームワークは大抵HTMLエスケープ機能を持っているため、htmlspecialchars()ではなくフレームワークに沿った出力方法を使いましょう。
その無効化して表示させることを「サニタイズ」といいます。
「サニタイズ」する目的のための手段として「エスケープ」という方法がある。ということですね。
JavaScriptでのサニタイズ方法
PHP言語からJavaScriptへ値を渡したい時もあります。PHP言語で色々処理した値をHTML上に表示するんではなく、JavaScriptに渡したい。といった場合ですね。ウェブカツ!!の1年生では想像しづらいと思いますが、3年生になるとこのあたり見えてきます。
そんな時にはPHP言語にあるjson_encode()というものを使ってあげます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php $var = [1, 2, 3]; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>プログラミング初心者向け「サニタイズ」まとめ|サンプル3</title> </head> <body> <h1>プログラミング初心者向け「サニタイズ」まとめ|サンプル3</h1> <p id="js-sample"></p> <script> var array = <?=json_encode($var, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>; var dom = document.getElementById('js-sample'); dom.innerHTML = array; </script> </body> </html> |
SQLでのサニタイズ方法
SQLでもサニタイズする必要があります。その方法が「プリペアドステートメント」という仕組みです。どの言語でも共通の言葉です。
プリペアドは「用意する」といった意味になります。詳しくはこちらをみてください。
https://qiita.com/tabo_purify/items/d1166236f3b03c7be60d
プリペアドステートメントは、簡単に言うと
SQLを実行する前にSQL文を作って準備し、その中に値を後から当て込んで初めて実行する。
という仕組みです。このあたりの処理の書き方はPHP・MySQL部入門でやっていますね。
php言語ではプリペアドステートメントを使うためにはprepare()を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php $pdo = new PDO('mysql:dbname=test;host=localhost;charset=utf8', $user, $pass, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ]); $stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name'); $stmt->bindValue(':name', $_REQUEST['name'], PDO::PARAM_STR); $stmt->execute(); $data = $stmt->fetchAll(); ?> |
動的に入れ込まなければならない部分をいったん:idといった別の固定値にしておいて、実際の値は別のメソッドで代入するという方法です。
もしプリペアドステートメントを使わなかった場合どうなるか?というと例えば下記のようなコードがあったとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php if(!empty($_POST['name'])){ $pdo = new PDO('mysql:dbname=test;host=localhost;charset=utf8', $user, $pass, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ]); $sql = 'SELECT * FROM users WHERE name = ' . $_POST['name']; $res = $pdo->query($sql); var_dump($res); } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>プログラミング初心者向け「サニタイズ」まとめ|サンプル4</title> </head> <body> <h1>プログラミング初心者向け「サニタイズ」まとめ|サンプル4</h1> <form action="" method="post"> <input type="text" name="name"> <input type="submit" value="送信"> </form> </body> </html> |
SQLをプリペアドステートメントという機能を使わずに自分でSQL文に直接値をくっつけて1つのSQL文を作る。というものですね。
このコードで画面を表示して、入力フォームに名前を入れて送信してみてください。(DBの中にはユーザーデータをいくつか入れてください)
すると同じ名前の情報が取れて画面に表示されるはずです。
では、次は入力フォームにこんな文字を入れて送信してみてください。
1 |
t' OR 't' = 't |
そうするとあら不思議。
全部のユーザー情報が取得できてしまう。
これは代表的なセキュリティの穴を突いた攻撃方法で
SQLインジェクション
というものです。
どうしてこんな事が出来てしまうかというとフォームから入力された値が最後はこんなSQLに変わってしまうからなんです。
元々下記のように組んでいたコードなので、そこに値が入ってくると
1 |
$sql = 'SELECT * FROM users WHERE name = ' . $_POST['name']; |
こんな感じになります。
1 |
$sql = 'SELECT * FROM users WHERE name = 't' OR 't' = 't' |
nameカラムが「t」の人か または(OR) 「t」が「t」の場合 に合致するデータを全部取得する。
というコードに早変わりしてしまうんですね。
一見わけがわからないが、nameカラムが「t」の人はまずいないでしょう。
しかし、
「t」が「t」か?
というと
「t」なわけですね。
「t」が「t」
なのは当たり前ですね。
そう、
当たり前だから全部のレコードはそれに当てはまる。
ということなんです。
なので、全部のユーザー情報をごっそり抜き出せてしまうわけなんですね。SQLインジェクションは個人情報がごっそり抜き取れてしまうので一番気をつけなければいけないセキュリティ対策です。
ここで問題なのは「’(シングルクウォート)」です。SQLではシングルクウォートは「特殊文字」で、シングルクウォートで挟んだものは「文字列」として認識されてしまうからなんですね。
そのためにプリペアドステートメントを使っておく事でシングルクウォートを自動的に「無害化」してくれるわけなんです。