- 投稿日:2021-08-09T18:49:46+09:00
PHP、CakePHP、LaravelのそれぞれのCSRF対策
CSRFとは CSRF(Cross Site Request Forgery)とは、Webアプリのユーザー自身が、意図しない処理を実行するようにする脆弱性または攻撃手法のことです。 悪意のあるWebサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、ユーザーに別のWebサイト上で何らかの操作を意図せずに行わせる攻撃となっています。 CSRF対策をしていないサイトの例と攻撃方法 今回、会員サイトのパスワード変更画面を例にします ログイン後にパスワード変更を行う場合を想定しております (今回テーマから逸れてしまうので、ログイン画面とパスワード変更完了時のサニタイズ処理は省略しております)。 パスワード変更_入力画面 <?php // ログイン確認 session_start(); if (!isset($_SESSION[‘id’])) { header('Location: http://hoge.com/login.php'); } ?> <h1>パスワード変更</h1> <form action="http://hoge.com/password_change_complete.php" method="post"> <input type="hidden" name="password" value="password"> <input type="password" name="password"> <input type="submit" value="変更する"> </form> パスワード変更_完了画面 <?php session_start(); if (!isset($_SESSION[‘id’])) { header('Location: http://hoge.com/login.php'); } $id = $_SESSION[‘id’]; $password = filter_input(INPUT_POST, ‘password’); ?> <h1>パスワード変更完了</h1> <p><?php echo $id; ?>さんのパスワードを<?php echo $password; ?>に変更しました。</p> 図:id:hoge、変更後のパスワード:passwordで変更を行った場合 このようなCSRF対策のされていない画面でパスワードの変更を行うと、一定時間ブラウザにcookieの情報が保持されてしまいます。 cookieが保持されている状態で、以下のようなタグが入っている悪意のある別のWebページにアクセスしてしまうと、会員情報変更処理ページに自動でPOST送信されて、パスワードが変更されてしまいます。 CSRF攻撃(パスワードを勝手に変更) // password_change.html <body onload="document.forms[0].submit()"> <form action="http://hoge.com/password_change_complete.php" method="POST"> <input type="hidden" name="password" value="cracked"> </from> 図:id:hogeのcookie情報が残っている状態で上記のCSRF攻撃にあった場合 CSRFの対策 主な対策としては、データを送信するページにトークンを埋め込む方法がとられます。 これは、正規利用者の意図したリクエストかを確かめるために行います。 リクエスト送信フォーム内にhidden値でワンタイムのtokenを埋め込むことで、同一セッション内での一致の確認を行います。 対策に必要なページは、他のサイトから勝手に実行されてはいけないページです。 例えばECサイトでは、物品購入の確定ページや、パスワード変更など個人情報の編集確定画面などが挙げられます。 PHPの場合:inputタグでトークンの埋め込み(送信側)+トークンのチェック(受信側) パスワード変更_入力画面 <?php session_start(); if (!isset($_SESSION[‘id’])) { header('Location: http://hoge.com/login.php'); } if (empty($_SESSION['token'])) { // トークンが空の場合、生成する $token = bin2hex(openssl_random_pseudo_bytes(24)); $_SESSION['token'] = $token; } else { // トークンがあれば使用する $token = $_SESSION['token']; } ?> <h1>パスワード変更画面</h1> <form action="http://hoge.com/password_change_complete.php" method="post"> <input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>"> <input type="text" name="password"> <input type="submit" value="変更する"> </form> パスワード変更_完了画面 <?php session_start(); if (!isset($_SESSION[‘id’])) { header('Location: http://hoge.com/login.php'); } $token = filter_input(INPUT_POST, 'token'); if (empty($_SESSION['token']) || $token !== $_SESSION['token']) { die('CSRF攻撃が発生 !!'); } $id = $_SESSION[‘id’]; $password = filter_input(INPUT_POST, ‘password’); ?> <h1>パスワード変更完了</h1> <p><?php echo $id; ?>さんのパスワードを<?php echo $password; ?>に変更しました。</p> CakePHPの場合:コントローラーにコンポーネントを追加する CakePHPには共通のコントローラごとに共通の処理を支援する、「コンポーネント」という機能があります。 この中でトークンを埋め込み、CSRF対策をしてくれる機能が提供されています。 AppContorller.phpに以下の記述を行うことで、自動的にCSRF対策を行ってくれるようになるので、View側での記述は特に必要ありません。 AppContorller.php <?php class AppController extends Controller { { public $components = array('Security'); } } Laravel(Blade)の場合:viewファイルのformタグ内に「@csrf」と記述する <h1>パスワード変更画面</h1> <form action="{{ url('/password_change_complete') }}" method="post"> @csrf // もしくは {{ csrf_field() }} <input type="text" name="password"> <input type="submit" value="変更する"> </form> LaravelではミドルウェアにデフォルトでCSRF対策が入っています(Formタグが存在するページでCSRF対策ができていないと、自動的に419ページにリダイレクトします)。 /app/Http/Kernel.php <?php protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\VerifyCsrfToken::class, ... ], ]; XSSとの違いについて XSS(Cross Site Scripting):とは、Webアプリのユーザーが、Webページにアクセスすることで不正なスクリプトが実行されてしまう攻撃手法のことです。 文章で簡潔に表すと、かなり類似していますが、攻撃が実行される場所が異なります。 攻撃シナリオをを示すと以下のようになります。 ①ユーザーが罠サイト閲覧する ②仕掛けのあるHTMLの実行される ③②により、ユーザーから別のサイトに攻撃用のリクエストが送られる(CSRF) ④仕掛けを含むレスポンスが返ってくる(XSS) CSRFでは、③のリクエストに対するサーバー側の処理を悪用しています。 不正なスクリプトが実行されるのは「 Webサーバー」です。 そのため、CSRF対策が必要となるページは、ログイン後に変更処理が絡んでくる箇所のみとなります。 対してXSSの場合は、③のリクエストに対して④が返ってきてそれがユーザーのWebブラウザ上で実行されます。不正なスクリプトが実行されるのは「被害にあうユーザーの Webブラウザ」となっています。 まとめ PHP CakePHP Laravel トークン埋め込み処理書く コンポーネントの追加 @csrf 参考 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 徳丸 浩 (著) https://book.cakephp.org/3/ja/controllers/components/csrf.html https://readouble.com/laravel/7.x/ja/csrf.html
- 投稿日:2021-08-09T17:40:47+09:00
プログラミング初心者がphp artisan db:seedしたらエラーが出た
はじめに 現在プログラミングスクールにてGitHubを用いて共同開発中なのですが 相方が作ってくれたコードをローカルでマージして、DBにシーダーファイルのデータを登録しようとしたところ エラーが発生しました。 ※Laravelのverは6.20.16です。 エラーの内容 php artisan db:seedをしたところ下記のようなエラーが出ました。 UsersTableSeederクラスが存在しないよと書かれていますが ちゃんとフォルダの中には存在しています笑 困ったので色々と調べたところ下記のコマンドが有効でした。 composer dump-autoload こちらを打ち込んだところGenerating optimized autoload filesとなり 無事にシーダーファイルの中のデータを登録することができました! しかしこのdump-autoloadが具体的にどのような動きをしているのかまでは まだ調べきれていないため、わかり次第追記したいと思います。 ご覧いただきありがとうございました! 参考記事 https://qiita.com/teneye/items/1fdedd8ffe5a5ebd6c71
- 投稿日:2021-08-09T17:19:06+09:00
【講義11.php_ファイルアップロード】詰んだことでパーミッションの理解が深まった話
本記事について 今後の開発の参考になればと思い、PHPについて、講義での実体験をまとめたもの。 PHPのファイルアップロードについて PHPではformを利用して画像ファイルの受け取り、アップロードが可能。 アップロードの流れは以下の通り 1.formからアップロード 2.tmp領域(一時保存場所)に保存 3.サーバの保存領域に移動 4.データベースに保存場所のパスを登録 詰んだ内容 ファイルの権限設定をし忘れたことに気づかず、コードの記述ミスやパーミッション権限を疑ってハマった。 パーミッションはアップしたデータの修正、削除のみに使うため、疑うべきところが謝っていた。 試したこと、疑ったコード コメントしているfilename_to_save関数まではvar_dumpで確認できた。 そのため、パーミッション部分(if (move_uploaded_file以降以下)を疑った。 以下は、実際に記述したコード。 $img = $_FILES['img']['name']; $img_data = ""; if (isset($_FILES['img']) && $_FILES['img']['error'] == 0) { // 送信時OK // 送信されてきたファイルの情報を取得 $uploaded_file_name = $_FILES['img']['name']; $temp_path = $_FILES['img']['tmp_name']; $directory_path = 'upload/'; // ファイル名の準備 $extension = pathinfo($uploaded_file_name, PATHINFO_EXTENSION); $unique_name = date('YmdHis') . md5(session_id()) . "." . $extension; $filename_to_save = $directory_path . $unique_name; if (is_uploaded_file($temp_path)) { // ここでtmpファイルを移動する // var_dump($temp_path); // var_dump($filename_to_save); // exit(); if (move_uploaded_file($temp_path, $filename_to_save)) { chmod($filename_to_save, 0644); $img_data = $filename_to_save; } else { exit('Error:アップロードできませんでした'); } } else { exit('Error:画像がありません'); } } else { // 送信時エラー exit('Error:画像が送信されていません'); } // var_dump($filename_to_save); // exit(); 原因と対策 原因:ファイルの権限設定をし忘れたため。 対策:ファイル権限を設定。※macのみの設定※windowsは特に設定の必要なし 「htdocs内の画像ファイルを格納するフォルダ」で メニュー表示 => 情報を見る => 共有とアクセス権 全て「読み/書き」に変更 => 歯車ボタン => 内包している項目に適用 余談: パーミッションを0644から0777に変更しなければ!などととんでもないことをしようとしていた。 今回の講義で指定しているパーミッションは「0644」。 オーナーが「6」、グループは「4」、一般が「4」という事になるので、 オーナーが「rw = 4+2 = 6」で読み込みと書き込みを許可、グループと一般が「r = 4」で読み込みだけを許可していることになる。 「0777」に変更した場合、オーナー、グループ、一般全て読み込み、書き込み、実行を許可すると言う意味になる… 本番環境に実装していたら、ユーザーが画像の読み書き、つまり編集&削除できる。 意味を理解せず、動く(ここでは画像がアップ、編集、削除できる)からといって安易に変更すべきではない。 最後に「全てを疑え!自分でさえも」と自分にも同じ道を通る人にも伝えたい。 参考サイト https://atmarkit.itmedia.co.jp/ait/articles/1605/23/news020.html http://www.tryhp.net/permission.htm 以上。
- 投稿日:2021-08-09T16:45:05+09:00
setErrors 上書き出来ない cakePHP
はじめに 気力と体力がほとんどないのでちょっと乱雑な文章になりそうですが、 共有したいことは共有出来ているはずなので優しい心でお願いします! バリデーションエラーの一部をなかったことにしたいけど少し戸惑ったのでここに残しておきます! ちなみになぜそうしたかったかですが、Formでエラー表示しないとしてもエレメントで確認すると表示に違いがあるな〜〜〜と思い、これじゃだめだ!!!!だめなんだ!!!となりました、、 ちなみに表示させたくなかった文言は『このメールアドレスはすでに使用されています。』です。どのメールアドレスが登録されているかバレちゃうので、、 では早速 $fix_errors = $user->getErrors(); $fix_errors['email'] = null; //キーごと無くすunsetでは上手くいかなく戸惑いました、 $user->setErrors($fix_errors, true); //メソッド確認したら上書きするかの指定しないといけなかったのね、、と ちなみに /** * Sets error messages to the entity * * @param array $errors The array of errors to set. * @param bool $overwrite Whether or not to overwrite pre-existing errors for $fields * @return $this */ public function setErrors(array $errors, bool $overwrite = false); おまけ(エレメントの確認) <input type="email" name="email" placeholder="メールアドレス" required="required" data-validity-message="入力してください" oninvalid="this.setCustomValidity(''); if (!this.value) this.setCustomValidity(this.dataset.validityMessage)" oninput="this.setCustomValidity('')" id="email" value="aaab@s.com" maxlength="191"> //使用されているメールアドレスもの <input type="email" name="email" placeholder="メールアドレス" required="required" data-validity-message="入力してください" oninvalid="this.setCustomValidity(''); if (!this.value) this.setCustomValidity(this.dataset.validityMessage)" oninput="this.setCustomValidity('')" id="email" value="aaabaaa@s.com" maxlength="191"> //使用されていないメールアドレスもの あと最初は以下のようにやろうとしてました、、セキュリティ的な問題等なければ以下でも問題ないかと思います! if ($isShowEmailError === true) { echo $this->Form->control('email', ['placeholder'=>'メールアドレス', 'label' => '']); } else { echo $this->Form->control('email', ['placeholder'=>'メールアドレス', 'label' => '', 'error' => false]); } 最後に そもそものメールアドレスが間違っている時等ははエラーが表示されるように、エラーを削除する前にエラー内容の確認は必須かと思います!以下のような場合のエラー表示は必要ですからね( ・ὢ・ )
- 投稿日:2021-08-09T16:35:03+09:00
GETとPOSTとは PHP
■はじめにリクエスト行う方法を認識する。 ●ブラウザのアドレス欄にURLを直接入力する。 ●お気に入りからアクセスする。 ●ページ内のリンクやボタン、画像などをクリックする。 ●入力フォームに情報を入力して送信ボタンをクリックする。 などがあります。 ■リクエストを行う際サーバーに渡したいデータ ●見たい商品や動画のID ●ページ移動する際のページ番号 ●ログイン画面に入力するメールアドレスやパスワード ●注文画面に入力する名前や住所などの個人情報 ■GETとPOSTというのはリクエストの方式の1つです。 ▼GET リクエストを行う時にURLの末尾にデータ名を記載しくっつけて送る方法です。 ?以降にデータが記載している。 ■できない事 URLに長さの制限がある。長い文章や画像のようなデータ量が多いデータをURLにくっつけて送る事はできない。 個人情報やパスワードなどの情報をURLに記載されていたらまずい。 ▼使い分け 画面表示のために必要 表示したい画像や動画のID、ページ番号など GETは取得するためのリクエスト /search?id=なになに ▼POST リクエストを送る際にURLにデータ等を記載しないで送る方法。 個人情報やパスワードを入力する場合や膨大なデータを送信する場合はこのPOST方式を使う。 ユーザーが画面に入力して送信するもの ログインIDや個人情報 POSTは送信するためのリクエスト ■formタグのmethodにpostを使う <form method="post"> </form>
- 投稿日:2021-08-09T15:43:02+09:00
queryとprepareの使いわけ php
■queryとprepareとは PHPからデータベースを操作する手順です。 ①データベースに接続 ②実行したいSQL文をセットする。 ③SQLに対してパラメーターをセットする。 ④実際にSQLを実行する。 ⑤結果を取得する。 ⑥データベースから切断する。 ▼PHPからデータベースを操作するには簡易版と詳細版がある。 ■簡易版にはqueryをつかう。 上記の②③④の手順を簡易的に一括で行えるのがquery。 ▼使い分け SQL文の中に変動値がない場合queryを使う。 //変動値がないSQL文 例 $sql = "SELECT * FROM テーブル名"; ▼使い方 セットから実行まで一括で行なっている。 $dbh->query($sql); ■詳細版にはprepareを使う。 上記の手順通り個別に行う。②③④に個別にメソッドが用意されている。 ②prepareメソッド ③bindValueメソッド ④executeメソッド ▼使い分け SQL文の中に変動値がある場合prepareを使う。 プレースホルダを使用する際と同じ時に使う前記事で紹介。 //変動値があるSQL文(プレースホルダ使用) 例 $sql = "SELECT * FROM テーブル名 WHERE name= :name"; ▼使いかた ②③④を個別に行う。 ③のbindValueに関してはプレースホルダを使用するのに必要なメソッド //②実行したいSQL文をセット $stmt = $dbh->prepare($sql); //③SQLに対してパラメーターをセット(プレースホルダを使用するのに必要な手順) $stmt->bindValue(':name',$name,PDO::PARAM_STR); //④SQL文の実行 $stmt->execute()
- 投稿日:2021-08-09T14:41:02+09:00
プレースホルダ php
■プレースホルダとは SQLインジェクション(DBに対して第三者が不必要なSQLを実行する事)等から守る方法 開発者の想定しない命令テーブルのデータ消去等を悪意のある第三者にさせないために使う。 万が一不正な値が入力されても第三者にSQL文の命令はできなくなる。 ▼SQL文の中にPHPの変数を直接書いてはダメ!!! SQLインジェクションが容易に行えるようになってしまう!! //■ダメな例 $sql = "SELECT * FROM user WHERE name= '$name'"; ■プレースホルダを使用する。 ①変動箇所を:で指定。文字列カラムでもシングルクオテーションは不要。 プレースホルダの名前はカラム名と同じにしておくと分かりやすい。 $sql = "SELECT * FROM user WHERE name= :name"; ②プレースホルダに実際の値をバインドする。 実際にプレースホルダを使えるようにここで指定してる。 $sql = "SELECT * FROM user WHERE name= :name"; //bindValue('プレースホルダ名',バインドするデータ,データの型); bindValue(':name', $name, PDO::PARAM_STR); 文字型:PARAM_STR 数値型: PARAM_INT ラージオブジェクト(画像データなど) PARAM_LOB NULL PARAM_NULL
- 投稿日:2021-08-09T14:40:24+09:00
PHP7.4でzipがインストールできない
PHPのインストールでzip(php74-php-pecl-zip)のインストールに依存関係の問題で少しはまったのでメモ。php -m で「zip」が表示されてない状態。 環境:PHP/7.4 OS/Amazon Linux2 結論からいうと、Amazon Linux2のリポジトリが邪魔して、依存関係を解決できない状態になってました。 1.libzip-1.3.2-1.amzn2.0.1.x86_64が邪魔してインストールできない [root@ ~]# yum install php74-php-pecl-zip --> トランザクションの確認を実行しています。 ---> パッケージ php74-php-pecl-zip.x86_64 0:1.19.3-2.el7.remi を インストール --> 依存性の処理をしています: libzip5(x86-64) >= 1.8.0 のパッケージ: php74-php-pecl-zip-1.19.3-2.el7.remi.x86_64 --> トランザクションの確認を実行しています。 ---> パッケージ **libzip5.x86_64 0:1.8.0-2.el7.remi** を インストール --> 依存性の処理をしています: libzstd(x86-64) >= 1.3.6 のパッケージ: libzip5-1.8.0-2.el7.remi.x86_64 --> 依存性解決を終了しました。 エラー: パッケージ: libzip5-1.8.0-2.el7.remi.x86_64 (remi-safe) 要求: libzstd(x86-64) >= 1.3.6 インストール: **libzstd-1.3.3-1.amzn2.0.1.x86_64** (@amzn2-core) libzstd(x86-64) = 1.3.3-1.amzn2.0.1 利用可能: libzstd-1.3.3-1.amzn2.x86_64 (amzn2-core) libzstd(x86-64) = 1.3.3-1.amzn2 よく読むと必要なパッケージにremiって書いてあります。 2.libzip-1.3.2-1.amzn2.0.1.x86_64を削除 yum remove libzip-1.3.2-1.amzn2.0.1.x86_64 3.amzn2-coreをrepoを対象から外す。 yum update --disablerepo=amzn2-core ここがちょっと盲点でした。 まさかAmazon Linux2のリポジトリが邪魔してるなんて思いもしませんでした。 4.改めてphp74-php-pecl-zipをインストール [root@ ~]# yum install php74-php-pecl-zip 依存性の解決をしています --> トランザクションの確認を実行しています。 ---> パッケージ php74-php-pecl-zip.x86_64 0:1.19.3-2.el7.remi を インストール --> 依存性の処理をしています: libzip5(x86-64) >= 1.8.0 のパッケージ: php74-php-pecl-zip-1.19.3-2.el7.remi.x86_64 --> 依存性の処理をしています: libzip.so.5()(64bit) のパッケージ: php74-php-pecl-zip-1.19.3-2.el7.remi.x86_64 --> トランザクションの確認を実行しています。 ---> パッケージ libzip5.x86_64 0:1.8.0-2.el7.remi を インストール --> 依存性解決を終了しました。 依存性を解決しました インストール: php74-php-pecl-zip.x86_64 0:1.19.3-2.el7.remi 依存性関連をインストールしました: libzip5.x86_64 0:1.8.0-2.el7.remi 今回の問題の直接的な解決には繋がらなかったけど、パッケージのリポジトリまわりはこの記事が参考になりました。どのリポジトリがきいてるとか、このパッケージはこのリポジトリから入れてるよってのも知ることができます。 https://qiita.com/SwuBHj8aKGqBKHet/items/5dba7a80d63c52d89b82 rpmでハマったことは何度もあったけど、yumでハマることはあんまりなかったので、yumまわりの仕組みがいろいろわかりました。Remiってずっとなんだと思ったけど、人の名前でした。Remi Colletさん。