前回記事MySQLでテーブル作成2ではフィールドの挿入等の確認をしました。次にPHP側でコードを書いてデータベースへ挿入です。色々とセキュリティに関する事項がでてくるので、執筆が長くなるかもしれませんが、ご了承ください。6月2日記事


目次


phpでMySQLとの連携プログラムを書く際に、注意しなければならない点がいくつかあります。その中で、2つに絞ってまずは話していきたいと思います。非常に最初は混同しがちですが、よく読みながら見ていってください。

ユーザが入力したJavaScriptを実行させないhtmlspecialchars関数

現在、JavaScriptを使ったWEBページは多く存在しています。JavaScriptを利用することで、表示上動きのあるWEBサイト構築が可能になっています。閲覧者、利用者の多くはブラウザでJavaScriptの実行をONにしていることが多く、JavaScriptの利用は必要不可欠になっています。しかし、その部分を悪用し、「JavaScriptで他のサイトに転送(リダイレクト)させたり、悪意のあるコードを読み込ませたりする攻撃」が多発しています。この攻撃を「クロスサイトスクリプティング」といいます。ある利用者が掲示板や皆が閲覧するページにこのJavaScriptのコードを埋め込まれた記事を書いてしまったら、そのページの利用者に多くの迷惑をかけることになります。そのため、 利用者には、JavaScriptの実行コードを埋め込ませてもそれを実行させないための対策 が必要となります。開発者は悪意がなければ開発時にページにJavaScriptコードを埋め込んでしまってもかまいません。あくまで 利用者にJavaScriptのコードを埋め込ませてもよいが、その内容を表示させるときにJavaScriptは実行させないプログラムを書く ということが重要です。それができるのがhtmlspecialchars関数です。

MySQLへデータを正確に処理させ、データベースの内容を改ざん削除させないSQLインジェクション対策

次に、利用者が勝手にデータベースの内容を改ざんしたり、削除させたりしない対策が必要になります。データベースに内容を挿入、更新、削除する場合、当然SQLのクエリを内部で実行させています。そのため、悪意のある利用者がこのSQLクエリを実行させるような内容を記述されてしまったら、データベースの内容が改ざん、削除されてしまいます。URLの末尾にSQLのクエリを書いたり、フォームの部分にSQLのクエリを書けばそのようなことが起きる場合があります。この攻撃のことを「SQLインジェクション」と言います。そのため、 利用者に悪意のあるSQLクエリを入れられても実行させないための対策 が必要になります。以前はmysql_real_escape_stringという関数を使って対策を行っていましたが、色々と悪意のあるコードを実行させる手法が登場してきているため現在はこの関数は 非推奨 となっています。現在では、PDOを使ってデータベースに挿入、更新、取得をさせる方法が主流です。

PDOの利用

PDOの設定についてはPDOの利用をご参照ください。

フォームを作ってみる

こちらはただのHTMLを記述していきます。
と宣言したのですが、SELECTタグを使ってフォームを作ると、2月30日や4月31日が作成可能になってしまいますので、公開日等のフォームをjQueryで作っていきたいと思います。もし2月30日や4月31日をクエリで投げてもデフォルト値で指定したもの等になってしまいますので、制限する必要があります。多少横道にはそれますがご了承ください。作り方はjQueryで日付入力フォームを作るをご参照ください。公開日のところ以外のCSS記述は割愛します。各自CSSでうにょうにょやってください。
ファイル名(register.php)

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>新規作成</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/i18n/jquery.ui.datepicker-ja.min.js"></script>
<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/ui-lightness/jquery-ui.css" rel="stylesheet" />
<script type="text/javascript">
$(function(){
 $("#datepicker").datepicker()

});
</script>

</head>

<body>
<form action="confirm.php" method="post">
<dl>
    <dt>公開日</dt>
    <dd><input type="text" name="form_date" id="datepicker"></dd><!--textにした理由はwebkit対策-->
    <dt>種類</dt>
    <dd><select name="form_type">
    	<option value="1" selected>ニュース</option>
    	<option value="2">更新情報</option>
    	<option value="3">入荷情報</option>
    	<option value="4">その他</option>
        </select>
    <dt>タイトル</dt>
    <dd><input type="text" name="form_title" size="40" maxlength="30"></dd>
    <dt>内容</dt>
    <dd><textarea name="form_info_text" cols="40" rows="5"></textarea></dd>
    <dt>URL</dt>
    <dd><input type="url" name="form_url" size="40"></dd>
    <dt>ステータス</dt>
    <dd><select name="form_status">
    	<option value="2" selected>公開</option>
    	<option value="1">下書き</option>
        </select>
    </dd>
</dl>
<input type="submit" name="form_submit" value="確認">
</form>
</body>
</html>

データベースに挿入する名前とフォームの部品の名前を区別するためにフォームの方にはform_という名前を付けておきました。
するとこうなります。



確認ページ

こちらは、クロスサイトスクリプティング対策を兼ねたコードを記述してきます。
JavaScriptのコードを実行させない確認表示が必要になります。「別に、埋め込んだ人が自分で自分を攻撃してどうするの?」という方もいらっしゃると思いますが、HTMLを正確に出力するということはある意味標準で考えなければならないことなので、htmlspecialchars関数を使って記述していきます。ここからはphpを実行させる環境を用意してください。

コード(ファイル名:confirm.php)

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>確認画面</title>
</head>

<body>
<?php

/*特定のURIから送信された時のみ実行*/
if($_SERVER["HTTP_REFERER"] == (empty($_SERVER["HTTPS"]) ? "http://" : "https://").$_SERVER['HTTP_HOST']."/register.phpまでのフォルダを記述/register.php"){
	/*まずはエラー処理*/
	
	$error = '';/*初期化。以降エラーがあれば$errorに格納していく。*/
	if($_POST['form_date'] == '') $error .= '日付が選択されていません。<br>';/*①*/
	if(preg_match('/^([1-9][0-9]{3})\/(0[1-9]{1}|1[0-2]{1})\/(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]{1})$/', $_POST['form_date'])){}else{ $error .= '不正な日付です。<br>';}
	if($_POST['form_type'] == '') $error .= '投稿種類が選択されていません。<br>';
	if($_POST['form_title'] == '') $error .= 'タイトルが入力されていません。<br>';
	if(mb_strlen($_POST['form_title']) > 30) $error .= '30文字以内にしてください<br>';	
	if($_POST['form_info_text'] == '') $error .= '内容が入力されていません。<br>';
	if(mb_strlen($_POST['form_info_text']) > 1000) $error .= '1000文字以内にしてください<br>';	
	if($_POST['form_status'] == '') $error .= '状態が選択されていません。<br>';

	echo $error;/*エラーがあればエラーを表示*/
	
	if($error == ''){
	echo '
	<dl>
	<dt>日付</dt>
	<dd>'.htmlspecialchars($_POST['form_date']).'</dd>
	<dt>種類</dt>
	<dd>';
	switch ($_POST['form_type']){
	case 1:
		echo 'ニュース';
		break;
	case 2:
		echo '更新情報';
		break;
	case 3:
		echo '入荷情報';
		break;
	case 4:
		echo 'その他';
		break;
	default :
		echo 'その他';
	}
	echo'</dd>
	<dt>タイトル</dt>
	<dd>'.htmlspecialchars($_POST['form_title']).'</dd>
	<dt>内容</dt>
	<dd>'.nl2br(htmlspecialchars($_POST['form_info_text'])).'</dd>
	<dt>URL</dt>
	<dd>'.htmlspecialchars($_POST['form_url']).'</dd>
	<dt>状態</dt>
	<dd>';
		switch ($_POST['form_status']){
	case 0:
		echo '削除';
		break;
	case 1:
		echo '下書き';
		break;
	case 2:
		echo '公開';
		break;
	default :
		echo '削除';
	}
	
	echo'</dd>
	</dl>
	<form action="complete.php" method="post">
	<input type="hidden" name="form_date" value="'.htmlspecialchars($_POST['form_date']).'">
	<input type="hidden" name="form_type" value="'.htmlspecialchars($_POST['form_type']).'">
	<input type="hidden" name="form_title" value="'.htmlspecialchars($_POST['form_title']).'">
	<input type="hidden" name="form_info_text" value="'.htmlspecialchars($_POST['form_info_text']).'">
	<input type="hidden" name="form_url" value="'.htmlspecialchars($_POST['form_url']).'">
	<input type="hidden" name="form_status" value="'.htmlspecialchars($_POST['form_status']).'">
	<p>この内容でよろしいでしょうか?</p>
	<input type="submit" name="submit" value="はい">
     <input type="button" value="戻る" onClick="history.back()">
	</form>
	';
	}

}else{
	echo '正しいアクセスを心がけてください。';
}
?>
</body>
</html>

解説
①$error .= は前の$errorという変数に付け加えていくという意味です。
表示はこのようになります。



完了ページ

こちらは、SQLインジェクション対策を兼ねたコードを記述していきます。 ここではMySQLのINSERT文を投げていきます。
ファイル名(complete.php)

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>完了</title>
</head>

<body>
<?php

/*特定のURIから送信された時のみ実行*/
if($_SERVER["HTTP_REFERER"] == (empty($_SERVER["HTTPS"]) ? "http://" : "https://").$_SERVER['HTTP_HOST']."/confirm.phpまでのフォルダ記述/confirm.php"){

	$dsn = 'mysql:dbname=データベース名;host=ローカルホスト;port=ポート番号';
	$user = 'ユーザ名';
	$password = 'ユーザパスワード';
	$dbh = new PDO($dsn, $user, $password);

	try{
		//成功時対応

		$stmt = $dbh -> prepare("INSERT INTO テーブル名 ( info_date ,title,type,info_text,url,status) VALUES (:info_date, :title, :type, :info_text, :url, :status)");
		$stmt->bindParam(':info_date', $_POST['form_date'], PDO::PARAM_STR);
		$stmt->bindParam(':title', $_POST['form_title'], PDO::PARAM_STR);
		$stmt->bindParam(':type', $_POST['form_type'], PDO::PARAM_INT);
		$stmt->bindParam(':info_text', $_POST['form_info_text'], PDO::PARAM_STR);
		$stmt->bindParam(':url', $_POST['form_url'], PDO::PARAM_STR);
		$stmt->bindParam(':status', $_POST['form_status'], PDO::PARAM_INT);

		$stmt->execute();//実行
		
		switch ($_POST['form_status']){
			case 0:
				echo '削除しました。<br>';
				break;			
			case 1:
				echo '下書きに保存しました。<br>';
				break;			
			case 2:
				echo '公開対象になりました。<br>';
				break;
			default :
				echo '削除しました。<br>';
		}
		
		print '<a href="register.php">新規作成へ戻る</a>';

	}catch (PDOException $e){//失敗時対応
	print('Connection failed:'.$e->getMessage());
	die();
	}

$dbh = null;
	
}else{
	echo '正しいアクセスを心がけてください。';
}

?>

</body>
</html>



現在の風潮

現在、確認ページを作らず、挿入される値をチェックして、エラーがなければすぐ挿入という方法も流行ったりしています。その記事はいつか作っていきたいと思います。初回ということでSQLインジェクションとクロスサイトスクリプティングの利用ということで、このような記事を作っていきました。

感想

この方法だと、complete.phpで更新ボタンを押すと、またデータが挿入されてしまいます。Sessionファイルを使って、フォームを受け取り等をし、完了したら、Sessionファイルを空にする的なコードを書けば、それが防げます。 我ながらに下手なコードだなぁ・・・。もっと色々と変えなければならないことが山ほどあります。まあ、PHPでこんな風に書くよ的なことがわかればいいなと思ったりもします。(考え方甘い?)

次回は挿入したデータを取得していき、表示したいと思います。ページ名は、PHPとMySQLでデータの取得です。


コメントお待ちしております。

名前:
コメント:
最終更新:2015年06月05日 09:51
添付ファイル