実現したいこと
セキュリティ上 安全なQ&A掲示板を作成したい
前提
PHPとデータベースを使い名前・コメント・アップロードファイル・スタンプが送信できる、Q&A掲示板を作成しております。
PHP側ではCSRF対策にトークンを実装しております。
single-index.php(インデックス)
single-input.php(入力画面)
single-refication.php(文字列チェック)
single-index.php
<?php session_start(); header('X-FRAME-OPTIONS: SAMEORIGIN'); class MAX_LENGTH { public const NAME = 50; public const MESSAGE = 500; } $noindexaccess = true; $errors = []; $mode = $_POST['mode'] ?? 'init'; switch ($mode) { case 'regist': $namae = $_SESSION['namae']; $message = $_SESSION['message']; $stamp = $_SESSION['stamp']; check(); break; case 'confirm': $namae = $_POST['namae']; $message = $_POST['message']; $stamp = $_POST['stamp']; check(); break; case 'input': $namae = $_SESSION['namae']; $message = $_SESSION['message']; $stamp = $_SESSION['stamp']; break; default: $namae = ''; $message = ''; $stamp = '1'; $_SESSION = []; break; } if ('init' !== $mode && empty($errors)) { $_SESSION['namae'] = $namae; $_SESSION['message'] = $message; $_SESSION['stamp'] = $stamp; } else { $mode = ''; } switch ($mode) { case 'regist': include 'single-regist.php'; break; case 'confirm': include 'single-confirm.php'; break; default: include 'single-input.php'; break; } function check() { global $errors; global $namae; global $message; $namae = Chk_StrMode($namae); $message = Chk_StrMode($message); Chk_InputMode($namae, '・お名前をご記入ください。'); Chk_InputMode($message, '・お問い合わせ内容をご記入ください。'); if (!empty($_FILES)) { foreach ($_FILES['attach']['tmp_name'] as $i => $tmp_name) { if (empty($tmp_name)) { if (empty($_POST['attachdel'][$i])) { if (empty($_SESSION['attach']['data'][$i])) { $_SESSION['attach']['data'][$i] = ''; $_SESSION['attach']['type'][$i] = ''; } } else { $_SESSION['attach']['data'][$i] = ''; $_SESSION['attach']['type'][$i] = ''; } } else { if (false) { $errors[] = 'ファイルエラー'.$i; } else { $data = file_get_contents($tmp_name); $_SESSION['attach']['data'][$i] = $data; $_SESSION['attach']['type'][$i] = $_FILES['attach']['type'][$i]; } } } } } function Chk_StrMode($str) { // タグを除去 $str = strip_tags($str); // 空白を除去 $str = mb_ereg_replace('^( ){0,}', '', $str); $str = mb_ereg_replace('( ){0,}$', '', $str); $str = trim($str); // 特殊文字を HTML エンティティに変換する $str = htmlspecialchars($str); return $str; } /* 未入力チェックファンクション */ function Chk_InputMode($str, $mes) { global $errors; if ('' == $str) { $errors[] = $mes; } } ?>
single-input.php
<?php if (!$noindexaccess) { exit('不正アクセス'); } if (empty($_SESSION['token'])) {// 悪意のある攻撃者があらかじめ作成したコードが実行されてしまうのを防ぐ $_SESSION['token'] = bin2hex(random_bytes(16)); } $attach = []; if (!empty($_SESSION['attach'])) { foreach ($_SESSION['attach']['data'] as $i => $data) { if (!empty($data)) { $base64 = base64_encode($data); } $type = $_SESSION['attach']['type'][$i]; switch ($type) { case 'image/jpeg': case 'image/png': $attach[] = '<img style="height: 100px;" src="data:'.$type.';base64,'.$base64.'">'; break; case 'video/mp4': $attach[] = '<video style="height: 100px;" controls src="data:'.$type.';base64,'.$base64.'">'; break; default: $attach[] = ''; break; } } } $stamp_checked = []; $stamp_checked[$stamp] = 'checked'; $upload_dir = wp_upload_dir(); $camera_url = $upload_dir['baseurl'].'/camera.png'; ?> <h2>入力画面</h2> <?php foreach ($errors as $error) { echo "<p>{$error}</p>"; } ?> <div class="board_form_partial" id="js_board_form_partial"> <form method="post" enctype="multipart/form-data"> <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>"> <div class="image-partial"> <h2>動画・画像をアップロード(Upload video・image)<span class="required">※ファイルサイズ15MB以内、JPG/PNG/MP4</span></h2> <div class="image-selector-button"> <label> <div class="image-camera-icon"> <img src="<?php echo $camera_url; ?>" class="changeImg"> </div> <input type="file" class="attach" name="attach[]" accept=".png, .jpg, .jpeg, .mp4" style="display: none;"> </label> <input type="hidden" class="attachdel" name="attachdel[]"> <div class="viewer"><?php echo $attach[0]; ?></div> <button type="button" class="attachclear">clear</button> </div> <div class="image-selector-button"> <label> <div class="image-camera-icon"> <img src="<?php echo $camera_url; ?>" class="changeImg"> </div> <input type="file" class="attach" name="attach[]" accept=".png, .jpg, .jpeg, .mp4" style="display: none;"> </label> <input type="hidden" class="attachdel" name="attachdel[]"> <div class="viewer"><?php echo $attach[1]; ?></div> <button type="button" class="attachclear">clear</button> </div> <div class="image-selector-button"> <label> <div class="image-camera-icon"> <img src="<?php echo $camera_url; ?>" class="changeImg"> </div> <input type="file" class="attach" name="attach[]" accept=".png, .jpg, .jpeg, .mp4" style="display: none;"> </label> <input type="hidden" class="attachdel" name="attachdel[]"> <div class="viewer"><?php echo $attach[2]; ?></div> <button type="button" class="attachclear">clear</button> </div> </div> <style> .hideItems { display: none; } </style> <div class="title-partial"> <h2>名前<span class="required"></span></h2> <input class="length_input" data-maxlength="<?php echo MAX_LENGTH::NAME; ?>" type="text" name="namae" id="name" placeholder="未入力の場合は、匿名で表示されます" value="<?php echo $namae; ?>"> <div class="msg_partial"></div> </div> <div class="body-partial"> <h2>コメント<span class="required"></span></h2> <textarea class="length_input" data-maxlength="<?php echo MAX_LENGTH::MESSAGE; ?>" name="message" id="message" placeholder="荒らし行為や誹謗中傷や著作権の侵害はご遠慮ください"><?php echo $message; ?></textarea> <div class="msg_partial"></div> </div> <div class="stamp-partial"> <h2>スタンプを選ぶ(必須)</h2> <input type="radio" name="stamp" value="1" id="stamp_1" <?php echo $stamp_checked['1']; ?>><label for="stamp_1"></label> <input type="radio" name="stamp" value="2" id="stamp_2" <?php echo $stamp_checked['2']; ?>><label for="stamp_2"></label> <input type="radio" name="stamp" value="3" id="stamp_3" <?php echo $stamp_checked['3']; ?>><label for="stamp_3"></label> <input type="radio" name="stamp" value="4" id="stamp_4" <?php echo $stamp_checked['4']; ?>><label for="stamp_4"></label> </div> <div class="post-button"> <button type="submit" id="submit_button" name="mode" value="confirm">表示画面へ進む</button> </div> </form> <script> /* 名前とメッセージの文字数チェック */ const length_input = document.querySelectorAll('.length_input'); const submit_button = document.getElementById('submit_button'); for (let i = 0; i < length_input.length; i++) { length_input[i].addEventListener('input', lengthCheck); let event = new Event("input"); length_input[i].dispatchEvent(event); } function lengthCheck() { const left = this.dataset.maxlength - this.value.length; if (left >= 0) { this.nextElementSibling.innerHTML = 'あと<strong>' + left + '</strong>文字'; this.dataset.submit_disabled = this.value.length === 0; } else { this.nextElementSibling.innerHTML = '<strong>' + -left + '</strong>文字超過しています'; this.dataset.submit_disabled = true; } let disabled = false; for (let i = 0; i < length_input.length; i++) { if (length_input[i].dataset.submit_disabled === "true") { disabled = true; } } submit_button.disabled = disabled; } /* カメラ画像をファイルアップロード時に非表示にする */ const attach = document.querySelectorAll('.attach'); const del = document.querySelectorAll('.attachdel'); const clear = document.querySelectorAll('.attachclear'); const viewer = document.querySelectorAll('.viewer'); const changeImg = document.querySelectorAll('.changeImg'); for (let i = 0; i < attach.length; i++) { attach[i].addEventListener('change', () => { if (attach[i].files[0].size > 15 * 1024 * 1024) { alert('ファイルサイズが 15MBバイトを超えています'); return; } del[i].value = ""; viewer[i].innerHTML = ""; if (attach[i].files.length !== 0) { const reader = new FileReader(); reader.onload = () => { var child = null; if (reader.result.indexOf("data:image/jpeg;base64,") === 0 || reader.result.indexOf("data:image/png;base64,") === 0) { child = document.createElement("img"); } else if (reader.result.indexOf("data:video/mp4;base64,") === 0) { child = document.createElement("video"); child.setAttribute("controls", null); } else { alert("対象外のファイルです"); alert(reader.result); attach[i].value = ""; } if (child !== null) { child.style.height = "350px"; child.style.width = "528px"; child.src = reader.result; viewer[i].appendChild(child); changeImg[i].classList.add('hideItems'); // もともとの画像を消す } }; reader.readAsDataURL(attach[i].files[0]); } }); clear[i].addEventListener('click', () => { attach[i].value = ""; del[i].value = "1"; viewer[i].innerHTML = ""; changeImg[i].classList.remove('hideItems'); }); } </script>
single-refication.php
<?php function Chk_StrMode($str) { // タグを除去 $str = strip_tags($str); // 空白を除去 $str = mb_ereg_replace('^( ){0,}', '', $str); $str = mb_ereg_replace('( ){0,}$', '', $str); $str = trim($str); // 特殊文字を HTML エンティティに変換する $str = htmlspecialchars($str); return $str; } function Chk_InputMode($str, $mes) { $errmes = ''; if ('' == $str) { $errmes .= "{$mes}<br>\n"; } return $errmes; } ?>
試したこと
PHPでのトークン実装してCSRF対策
回答1件
あなたの回答
tips
プレビュー