- ベストアンサー
WordPressのSQL取得エラーとテーブル操作の対策まとめ
- WordPressの質問で、var_dump($row);で確認したところ登録結果画面と質問表示画面でNULLが表示され、SQLのSELECT文が失敗している原因を考察しています。
- データベースにURLが格納されているにも関わらず、取得に失敗する例を検討し、IPアドレスの保存およびメッセージの一部を取得するコードの作成や<a>タグを適用する方法について質問しています。
- 提供されたコードの解析と、画像や動画ファイルの表示、メッセージの一部を表示する方法について、具体的なサンプルコードを元に議論しています。
- みんなの回答 (29)
- 専門家の回答
質問者が選んだベストアンサー
・JavascriptのinnerHTMLがDOM-based XSSの発生源となるようで、HTMLのエスケープ処理が行うべきだとご指摘を頂きました。 それは入力された文字をそのままどこかに表示する場合だと思います。 質問者さまが使っているinnerHTMLは自前の文字なので問題ない気がします。 入力された文字の文字数を計算で使っているだけですし。 参考サイトも見てみましたが、例えばこことか、攻撃者が云々と言うのは入力可能な文字と言うことだと思います。 ---- var text = "...."; // 変数 text は攻撃者がコントロール可能な文字列 form.innerHTML = '<input type="text" name="key" value="' + text + '">'; ---- 質問者さまのコードではこのような入力された文字をそのまま表示する処理がないので大丈夫だと思います。 ・デフォルトでカウント文が表示されずにカウントもされていない状態です… dell_okさんにお聞きしてよいものか迷ったのですが、アドバイスお願い致します… そうですね。 あちらの質問は勉強になるので読むだけ読んでいます。 インターネット上での暗黙のルールみたいなものでマルチポスト扱いされるといけないので、こちらでの回答は控えておきます。 あちらで回答してくださるお返事もあったことですし、しばらく待ってみましょう。
その他の回答 (28)
- dell_OK
- ベストアンサー率13% (766/5720)
・どうやらワンタイムトークンを使用していないことがCSRF対策に不十分だという事を指摘されていたようです。 コードを修正してみたのですが、こちらで問題ないでしょうか? まだコードは見ていませんがあまり見る気にもなれません。 私も何度も言いましたが、参考サイトにもこう書かれています。 ---- ①利用者が攻撃対象のサービスにログインする 実はここがCSRF攻撃の鍵です。利用者がサービスにログイン状態でないとCSRF攻撃はできません。 ---- 質問者さまのサイトはログインしないですよね。 なのになぜCSRF対策が必要なのでしょうか。
お礼
※参考サイト https://gihyo.jp/dev/serial/01/javascript-security/0006 こちらが最新の質問になります、現在セキュリティの最後の問題について考えているのですが、JavascriptのinnerHTMLがDOM-based XSSの発生源となるようで、HTMLのエスケープ処理が行うべきだとご指摘を頂きました。 PHPでエスケープは行うのですが、innerHTMLを使う場合Javascriptでもバリデーションは行った方が良いようです。 innerHTMLを使わずに実装する方法についてもアドバイス頂いたのですが、コードが上手く組めずに悩んでおります… デフォルトでカウント文が表示されずにカウントもされていない状態です… dell_okさんにお聞きしてよいものか迷ったのですが、アドバイスお願い致します… ※現在のコード https://wandbox.org/permlink/UeV5Eb2x1uRDQrBB ※方法1 エスケープの値を確認する function escapeHTML(値) { _ var a = document.createElement('span'); _ a.textContent = 値; _ return a.innerHTML; } 要素.innerHTML = '<strong>' + escapeHTML(文字列) + '</strong>'; ※方法1を自分で考えたコード <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> <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> ※エスケープ処理を追加 function lengthCheck() { const left = this.dataset.maxlength - this.value.length; if (left >= 0) { const a = document.querySelectorAll('msg_partial'); return a.innerHTML; this.nextElementSibling.innerHTML = '<strong>' + escapeHTML(left) + '</strong>文字'; this.dataset.submit_disabled = this.value.length === 0; } else { const b = document.querySelectorAll('msg_partial'); return b.innerHTML; this.nextElementSibling.innerHTML = '<strong>' + escapeHTML(-left) + '</strong>文字超過しています'; this.dataset.submit_disabled = true; } ___________________________________________________________________________ ※方法2 single-input.php (文字数をinnerHTMLを使わずにカウント) https://wandbox.org/permlink/BfWHABwCyxJG1FFL
補足
A.アドバイスありがとうございます、セキュリティについて慎重になりすぎていました申し訳ありません。 確かにログインがない場合は実装する必要はないようですね…
- dell_OK
- ベストアンサー率13% (766/5720)
・constを使って書いた変数名は2度使えないという認識で合ってますでしょうか? そうですね。 今のところはそんな感じの認識でいいと思います。 ちなみにですが、constで定義するものは一般的に定数と呼ばれます。 変数:値が変わるもの(プログラムのどこでどう変更されるかわからないので問題があった場合の調査が難しい) 定数:値が変わらないもの(不変なので安全)
お礼
最新の回答になります。どうやらワンタイムトークンを使用していないことがCSRF対策に不十分だという事を指摘されていたようです。 コードを修正してみたのですが、こちらで問題ないでしょうか? ※現在のコード if (empty($_SESSION['token'])) {// 悪意のある攻撃者があらかじめ作成したコードが実行されてしまうのを防ぐ $_SESSION['token'] = bin2hex(random_bytes(16)); } <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>"> ※修正後のコード $token_byte = openssl_random_pseudo_bytes(16); $csrf_token = bin2hex($token_byte); if (empty($_SESSION['token'])) {// 悪意のある攻撃者があらかじめ作成したコードが実行されてしまうのを防ぐ $_SESSION['token'] = $csrf_token; } ※最新コード https://wandbox.org/permlink/oT2YNNGYhKBXKLOx ※参考コード https://webukatu.com/wordpress/blog/11993/ ※ワンタイムトークンについて https://html-coding.co.jp/knowhow/security/csrf/
補足
A.回答ありがとうございます、セキュリティ(掲示板のCSRF対策)について下記のようにアドバイス頂いたのですが、PHPでtokenを送信するだけではdell_okさんの見解だと不十分だと思われますでしょうか? ※該当コード https://wandbox.org/permlink/k21yrxtxZ4jvA89w ※CSRFについて 悪意あるサイトから正しいトークン(セッション情報)を含んだリクエストが発行されているため、これだけでは見分けがつきません。 セッション情報は攻撃者によって取得済みとなっているので、そもそも信頼できず、同様の理由でセッションから取得できる値を種したトークンも利用できません。 ※代わりに提示いただいた方法 1,初回アクセス時にJavaScript(ブラウザ側)でトークンを作成しグローバル変数に保存、同時にPOSTでサーバーに送信しサーバー側で保存、データ送信時にブラウザのトークンを付けてPOSTで送信、整合性チェックする。 2,外部のワンタイムパスワードや画像データなどを1ユーザーのみに表示し、それをユーザーに追加で入力&送信させ、2つの正当性が確認できたタイミングで受理する。
- dell_OK
- ベストアンサー率13% (766/5720)
constについて変更できるかできないかを簡単に理解するにはこんな感じです。 書き方はこうですね。 const 変数名 = こう書いたら、もう二度と、 変数名 = とは書けない、と言うことです。 ですが、配列の要素の場合はこう書いたり、 変数名[0] = オブジェクトのプロパティの場合はこう書いたり、 変数名.プロパティ名 = して、それぞれの値を変更できるわけです。 それは変数の値を変更しているのではなく、要素やプロパティの値を変更しています。 なので、配列やオブジェクトでも、こうは書けません。 変数名 = 配列やオブジェクトそのものを変更はできないと言うことです。 とにかく、 変数名 = が書けなくなるのがconstです。
補足
A.説明ありがとうございます、constを使って書いた変数名は2度使えないという認識で合ってますでしょうか?varやletは多用できるという認識で考えております。
- dell_OK
- ベストアンサー率13% (766/5720)
・参考サイトを見ると変数をconstに変更しても値を変更できるようですが、 参考サイトの変更できる話しは、配列の要素や、オブジェクトのプロパティのことです。 変数が持っている単なる数値や文字または真偽値は変更できません。 以下のようなものはエラーになります。 ---- const i = 1; i = 2; const s = "a"; s = "b"; const b = true; b = false; ---- なので、以下のような使い方はできません。 ---- 略 const flg_Submit = false; 略 flg_Submit = true; 略 ---- 実行するとエラーになっているので確認してみてください。
補足
A.説明ありがとうございます、理解することが出来ました。関数式だけ変更するように致します。 <script> //2重送信防止スクリプト var flg_Submit = false; const Fnk_DoubleSubmit = () => { if(flg_Submit){ alert("処理中です。");return false; } else{ flg_Submit = true;return true; } } </script>
- dell_OK
- ベストアンサー率13% (766/5720)
・function()の関数宣言は行わないほうが良いみたいなので、constに変更したいと考えているのですが書き換えるだけで問題ないでしょうか? 私は詳しくないので何とも言えませんが、質問者さまがその書き方と用法を理解されて変更されるのでしたらそれでいいと思います。 ・varもconstに書き換えようと考えております。 変数のことでしたらむやみにconstにはできません。 値が変更される変数の場合はvarかletを使ってください。
補足
A.回答ありがとうございます、変更するようにしてみます。参考サイトを見ると変数をconstに変更しても値を変更できるようですが、別名の定数を作るという過程が必要な場合もあるようです、下記コードはそのままで良いような気もするのですがdell_okさんはどう思われますでしょうか? ※参考サイト https://qiita.com/kerupani129/items/c56ae227b8ff6f026884 ※contact-confirm.php <script> //2重送信防止スクリプト const flg_Submit = false; const Fnk_DoubleSubmit = () => { if(flg_Submit){ alert("処理中です。");return false; } else{ flg_Submit = true;return true; } } </script>
- dell_OK
- ベストアンサー率13% (766/5720)
・サイト内で完結するタイプではなく外部のメーラーを使用するため、メールインジェクション対策を確認していればセキュリティにおいては安全だと思うのですが、どちらが良いと思われますでしょうか? そうですね。 メーラーが勝手にリンクにしてしまうので何かしら対応が必要ですね。 無差別にメールが送られてくるわけではないので、質問者さまが気を付けて見られたらいいとは思うのですが、結局危険なことに変わりがないので、URLは入力できない方が無難ですね。 対応するにしてもどのような対応方法があるのか私にはわかりませんが、方法があればそれをメール本文にほどこせばいいような気はします。
補足
Javascriptのセキュリティについて調べていたところfunction()の関数宣言は行わないほうが良いみたいなので、constに変更したいと考えているのですが書き換えるだけで問題ないでしょうか? varもconstに書き換えようと考えております。 ※参考サイト https://qiita.com/kerupani129/items/b2c3619856b048c13394#23-%E9%96%A2%E6%95%B0%E5%AE%A3%E8%A8%80%E3%81%A7%E3%81%AA%E3%81%8F%E9%96%A2%E6%95%B0%E5%BC%8F%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B-func-style ※single-input.php 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; } ※雑談掲示板一覧コード https://wandbox.org/permlink/4lKVqjaUL2s2E02Z ※contact-confirm.php <script> //2重送信防止スクリプト var flg_Submit = false; function Fnk_DoubleSubmit(){ if(flg_Submit){ alert("処理中です。");return false; } else{ flg_Submit = true;return true; } } </script> ※お問合せ一覧コード https://wandbox.org/permlink/bvWm9Oe0ywwJcPAb
- dell_OK
- ベストアンサー率13% (766/5720)
・search.phpをどのように追加すればよいでしょうか? 念のために言っておきますが、検索は入力項目なので危険だと思われているかも知れませんが、そんなに危険なことではありません。 記事のなかにURLがあったとしてそれを検索することができなくなるのですが、それでよろしいですね。 対応するのはここですね。 ---- $search_query = filter_input(INPUT_GET, 's'); $search_query = htmlspecialchars($search_query); ---- クリックされることがないので、htmlspecialchars()で安全だとは思いますが、対応をどうするかですね。 チェックは関数にするまでもなく、これでいいと思います。 ---- if (preg_match("/[\.,:;]/u", $search_query)) { // 対応をどうするか } ---- 対応をどうするかですが、未入力で検索ボタンを押されたと同じにする、くらいしかできません。 ---- if (preg_match("/[\.,:;]/u", $search_query)) { $search_query = ''; } ---- search.php はすでに実行中で、この処理は途中にあって、これより前に出力命令があるためです。 もし他の対応をするとしたら、チェックを先頭に移動して、他の処理をする必要があるかも知れません。
補足
Q.記事のなかにURLがあったとしてそれを検索することができなくなるのですが、それでよろしいですね。 A.回答ありがとうございます、URLは基本的に検索できない仕組みにされていると思いますので、ブロックする方向で考えております。 Q.対応をどうするかですが、未入力で検索ボタンを押されたと同じにする、くらいしかできません。 ---- if (preg_match("/[\.,:;]/u", $search_query)) { $search_query = ''; } ---- search.php はすでに実行中で、この処理は途中にあって、これより前に出力命令があるためです。 もし他の対応をするとしたら、チェックを先頭に移動して、他の処理をする必要があるかも知れません。 A.アドバイスありがとうございます、URL送信できないようにしたかったのでこちらのコードを実装してみます。 ※search.phpの64行目に追加いたしました https://wandbox.org/permlink/1KS6QppKMNPBLnIx
- dell_OK
- ベストアンサー率13% (766/5720)
先ほどの回答のコードはチェックだけしてtrue/falseを返す方法ではありません。 未入力チェックと同じでエラーメッセージを設定する方法です。 ひとまずこれで動作確認してください。 これでURLらしきものが入力されたらエラーメッセージが出ればいいと思います。 他の画面でのチェックはtrue/falseを返す方法が必要になるかも知れませんが、それはそれで対応するとして、まずこの画面で確認してください。
補足
A.回答ありがとうございます、申し訳ありません。回答No.15の追加コードを読み落としておりました。 エラーメッセージを表示することが出来ました。 検索フォームとお問合せフォームでもURLが入力可能なため対策しておきたいのですが、search.phpをどのように追加すればよいでしょうか? お問い合わせフォームは何か問題が起きた際にURLの入力を許可しておいた方が良いのではないかと懸念しております、サイト内で完結するタイプではなく外部のメーラーを使用するため、メールインジェクション対策を確認していればセキュリティにおいては安全だと思うのですが、どちらが良いと思われますでしょうか?アドバイスお願い致します。 ※メールインジェクション対策が行われているのは確認済みです ※変更コード(contact-check.phpに追加) $error_mes .= CheckUrl($namae, '・お名前にURLは記入できません。'); // 追加 $error_mes .= CheckUrl($message, '・お問い合わせ内容にURLは記入できません。'); // /* 以下追加 */ function CheckUrl($checkurl, $mes) { global $errors; if (preg_match("/[\.,:;]/u", $checkurl)) { $errors[] = $mes; } } ※search.php https://wandbox.org/permlink/cMaBKPSVWIRG7OUw ※contact-index.php https://wandbox.org/permlink/FadVXOEmuL3OCOW6
- dell_OK
- ベストアンサー率13% (766/5720)
・関数名function〇〇の部分はfunction Chk_StrMode($str){}のように自由に命名してよいと思うのですが、CheckUrlというのが良くなかったでしょうか… 命名は自由ですよ。 CheckUrlで問題ありません。 回答No.15の「追加」の部分に気付いてくれましたか。 CheckUrl()を呼ぶようにしていますが、いただいたコードでは呼んでいません。 改めて全体を載せます。 ---- <?php /* Template Name: single 固定ページ: 単一テンプレート(入力画面-確認画面-登録結果画面) */ session_start(); header('X-FRAME-OPTIONS: SAMEORIGIN'); get_header(); 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, '・お問い合わせ内容をご記入ください。'); CheckUrl($namae, '・お名前にURLは記入できません。'); // 追加 CheckUrl($message, '・お問い合わせ内容にURLは記入できません。'); // 追加 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; } } /* 以下追加 */ function CheckUrl($checkurl, $mes) { global $errors; if (preg_match("/[\.,:;]/u", $checkurl)) { $errors[] = $mes; } } ----
- dell_OK
- ベストアンサー率13% (766/5720)
・一度試してみたのですが.だけをブロックしても危険なサイトに誘導できる可能性がありそうです。 可能性はないと思います。 どのようにして誘導できるか教えていただければ確認します。 ・アラートループ事件のようにコメントに書き込まれた際に無限に発火してしまう可能性がありそうです。 可能性はないと思います。 このOKWAVEでも同じですが、補足に表示されているように data:text/html;base64,~ が表示されるだけで何も起こりません。 ・※ケース2. リンクにスクリプトを挿入するから引用 この情報は質問者さまのサイトにはリンク(URL)を入力する欄がないので関係ありません。 ・function CheckUrl()の中の関数をどうすれば良いか分かりませんでした。 function CheckUrl($checkurl, $mes) { global $errors; if (preg_match("/[\.,:;]/u", $checkurl)) { $errors[] = $mes; } }
補足
Q.可能性はないと思います。 どのようにして誘導できるか教えていただければ確認します。 A.回答ありがとうございます、説明不足で申し訳ありません、具体的に危険なものはあげられていないのですが例として挙げられておりました。 参考サイトを見ると入力にJavascriptが含まれる場合をpreg_matchでバリデーションしても、抜け道があり javascript:alert('crack')やdata:text/html;base64,PHNjcmlwdD5hbGVydCgnY3JhY2snKTwvc2NyaXB0Pg== をURL(プロトコル)として入力できるためチェックにはならないと書いてあるようです。 参考サイトに書いてある通りホワイトリスト方式でhttp://以外のプロトコルを拒否することで該当しないJavascriptなど危険なURLはすべて防げそうですが、今回はhttp://も含めて書き込めないようにしたい為、真逆のブラックリスト方式で.と,と:と;を指定して該当するものをすべて書き込み禁止にすることで両方を防ぐコードが必要になりそうです。 ※参考サイト(ケース2. リンクにスクリプトを挿入する) https://qiita.com/tomochan154/items/a93c56536c78d1faff0f アタックA // javascript:alert('crack') と同じ javascript:alert('crack') アタックB // <script>alert('crack')</script> を Base64 エンコードしたもの data:text/html;base64,PHNjcmlwdD5hbGVydCgnY3JhY2snKTwvc2NyaXB0Pg== Q.この情報は質問者さまのサイトにはリンク(URL)を入力する欄がないので関係ありません。 A.回答ありがとうございます、$strで名前とメッセージを調べていると勘違いしておりました申し訳ありません。 関数名function〇〇の部分はfunction Chk_StrMode($str){}のように自由に命名してよいと思うのですが、CheckUrlというのが良くなかったでしょうか… URLを入力する欄はありませんが、参考サイトのコードは正規表現にあたるため入力のチェックには使えるものだと考えております。 調べながらコードを書いてみたので、もし検索文字列が含まれていた場合どのように対処するのが適切なのか分かりませんでした。アドバイスお願い致します。 return false; } return true; } ※参考サイト https://teratail.com/questions/56491 ※パターン2参考サイト https://tech.kurojica.com/archives/47020/ ※パターン1 function CheckUrl() { global $errors; global $namae; global $message; //内部文字エンコーディング mb_internal_encoding("UTF-8"); //正規表現にパターン修飾子の u を付けて、パターンと対象文字列を UTF-8 として処理 $pattern="/[\.,:;]/u"; if(!preg_match($pattern,$namae,$message)){ return false; } return true; } ※パターン2 function CheckUrl() { global $errors; global $namae; global $message; //内部文字エンコーディング mb_internal_encoding("UTF-8"); //正規表現にパターン修飾子の u を付けて、パターンと対象文字列を UTF-8 として処理 $chkeck="/[\.,:;]/u"; if (strpos($namae, $chkeck) !== FALSE || strpos($message, $chkeck) !== FALSE || { return true; } return false; } ※現在の雑談掲示板コード https://wandbox.org/permlink/4lKVqjaUL2s2E02Z
お礼
補足
A.アドバイスありがとうございます、脆弱性はinnerHTMLに設定する内容が制御できない内容である場合であり、変数leftが数値であることが保証されているように思われますので問題ないのではないかとアドバイスを頂いたのですが、少し神経質になりすぎていたかもしれません… leftの値が数値であることを証明されている場合は(エスケープ)必要なさそうですね、Wordpressを通すことでHTML側とJavascript側のかみ合わせが悪いようなので、再度確認して原因となりそうな部分を探してみます。 ※ページソース http://www.irasuto.cfbx.jp/%e9%9b%91%e8%ab%87%e6%8e%b2%e7%a4%ba%e6%9d%bf/