20201128のPHPに関する記事は15件です。

MAMPのPHPのexec()からpython3を簡単実行。

MAMPのpythonはversion2なので、ずっとこの壁にぶつかってたんですが、

$command = "python test.py";
exec($command, $output, $return_var);

python→python3でできました。意外と簡単でした。

$command = "python3 test.py";
exec($command, $output, $return_var);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでPHP開発をしている際に、vscodeでの実装変更が反映されない。

環境

macOS
DockerでPHPを使用

解決方法

順序1

Docker コンテナの停止・削除
$docker-compose down

順序2

Docker コンテナの起動
$docker-compose up -d

これだけでした汗

発生状況&原因

発生状況

vscodeでいつものようにPHPの勉強をしていた際に、vscodeで新たな実装変更を行いました。ターミナルで動作確認すると実装変更が反映されない状況になってしまいました。

このような状況に、、、汗

ss@saaaMPro src % docker-compose exec app php book_log.php
Could not open input file: book_log.php

なので原因を探るため
ターミナルでのディレクトリの位置が間違っていないか?
ファイル名が間違っていないか?
エラー内容が、入力ファイルが開けないと言っている、、、、なぜ、、(泣)

原因

ここでは、server-sideディレクトリのpart2の中のディレクトリで作業を行っていました。
一旦、ファイルの中を確認!
スクリーンショット 2020-11-28 19.51.17.png

ここで気になったのは、codeディレクトリの中に違う階層ではあるものの、同じpart2のディレクトリがあり、原因はここではないのか?と何の根拠もないのに自分の中で原因を発見できたと思ってしまいました。汗


記憶をたどると、作業前にFiderを開き、code/part2の中のファイルを直接コピーし、server-side/part2のファイルにペーストし、作業に取り掛かっていました。

その間、Dockerコンテナは最初から作動中だったので、移動前の状況のままになります(恐らく、、、)

なので、コピーしたファイルを移動し変更しても、Dockerコンテナの中は以前のままで反映さていないので、最初の項目(解決方法)を実行し、最新の状態にしてから、作業を行ったところ無事変更を行えました。


前提知識の不足や認識の間違いなど、ご指摘やアドバイスなどいただけましたら凄く有り難いので
お願いします!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ログイン機能例

<?php

try
{

$post=sanitize($_POST);
$staff_code=$post['code'];
$staff_pass=$post['pass'];

$staff_pass= md5($staff_pass);

$dsn = 'mysql:dbname=hoge;host=localhost;charset=utf8';
$user='root';
$password='';
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);

$sql = 'SELECT name FROM mst_staff WHERE code=? AND password=?';
$stmt = $dbh->prepare($sql);
$data[]=$staff_code;
$data[]=$staff_pass;
$stmt->execute($data);

$dbh =null;

$rec = $stmt->fetch(PDO::FETCH_ASSOC);

if($rec==false)
{
  print 'スタッフコードかパスワードが間違っています。<br/>';
  print '<a href="staff_login.html">戻る</a>';
}
else
{
  // $recがtrueであればログイン成功です
  session_start();
  $_SESSION['login']=1;
  $_SESSION['staff_code']=$staff_code;
  $_SESSION['staff_name']=$rec['name'];
  header('Location: staff_top.php');
  exit();
}
}
catch(Exception $e)
{
  print 'ただいま障害により大変ご迷惑をお掛けしております。';
  exit();
}

?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

curlの"-u"オプションで引き渡される認証情報をヘッダーに直接設定したい

先日プロダクトのアップデートを管理しているJenkinsサーバに対して新たにAPIを送るよう実装する機会があり、その際にインフラチームから共有いただいたAPIの呼び出し方法が下記のようなものでした。

curl -k -X GET "https://hogehoge.jp/job
?token=${api_job_token}" -u ${api_user_name}:${api_user_token}

"-u"オプションって?

Specify the user name and password to use for server authentication. Overrides -n, --netrc and --netrc-optional.
If you simply specify the user name, curl will prompt for a password.

参照:https://curl.se/docs/manpage.html#-u

curl先のサーバーで行われる認証に対し、ユーザ名とパスワードを引き渡しているオプションが-uオプションになります。

"curl"経由じゃなくて直接ヘッダーに認証情報を設定したい。。。

今回のケースではcurlからではなくて、ヘッダーなどのリクエスト内容を直接規定する方法でJenkinsAPIへリクエストを送る必要がありました。下記のようにすることで、-uオプションを利用して設定されるBasic認証情報をヘッダに設定することができます。(利用言語はPHP7.3)

private static function createHttpRequestForJenkins(): HttpRequest {
    // Basic認証情報を設定
    $encoded_user_info = base64_encode('user_name:password');
    $request_header = [
        'Content-type'  => 'application/json',
        'Authorization' => "Basic $encoded_user_info",
    ];
    ......
}

Basic認証では引き渡すユーザ名とパスワードはbase64形式でエンコードされる必要があるため、base64_encodeを使って認証情報を設定します。
エンコード済みの認証情報をヘッダーのAuthorization要素として指定をすることで、リクエスト先サーバにBasic認証情報を送信することができます。その際、エンコードされた情報はBasic+半角空白を接頭辞として持つ必要があります。
参照:https://ja.wikipedia.org/wiki/Basic%E8%AA%8D%E8%A8%BC

上記のままでもリクエストを送る上では問題ないのですが、createHttpRequestForJenkinsではリクエストを送ることだけ知っていて欲しかったので、最終的には下記のようにして実装を行いました(メソッドの実装内容は割愛します)。

private static function createHttpRequestForJenkins(): HttpRequest {
    $request_header = ApiRequestHeader::createContentTypeApplicationJsonHeader()
    // basic認証情報を設定
    ->withBasicAuth('user_name', 'password');
    ......
}

まとめ

curl -uはシンプルに認証情報をヘッダーに設定できる便利なオプションですが、いざ自分で同様の情報をヘッダーに設定する際に"?"となってしまったのでまとめてみました。どなたかのお役に立てば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VScodeでPHPを書こうとすると右下に表示されるエラーを解消したい

環境

  • OS

    • Windous10 Home x64
  • VScode

    • Version 1.51.1

表示されたエラーメッセージ

【 phpcs: Request workspace/configuration failed with message: Unable to locate phpcs. Please add phpcs to your global path or use composer dependency manager to install it in your project locally. 】

ざっくりした意味

「phpcs」を見つけることができなくて、ワークスペースの構成ができていないよ!
phpcsを自力でインストールするか、「Composer」を利用してインストールしてください。

解決するためにやったこと

  1. Composer をインストールする
    ➤Composerインストール先

  2. コマンドプロンプトを起動して以下の呪文を入力する

composer global require squizlabs/php_codesniffer

3.VScodeを再起動する

結果

ちゃんとエラーが消えました。

参考サイト

Cocotiie「Visual Studio CodeでPHPエラー」

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最近Webサイトに来た不審なアクセス(攻撃)のまとめ

グローバルIPアドレスを持つWebサーバーで自分のサイトを運営していますが、脆弱性を狙ったと思われる不審なアクセスが日常的に来ます。一度はまとめてみたい…と思っていたのですが、同じことをされている方の記事を見つけ、自分も同様の内容をまとめてみました。

不審なアクセスは種類を挙げればきりがありませんが、今回は主だったもの(件数が多いもの)について紹介したいと思います。最後には対策を挙げました。

ちなみに、数を数える時は、ログを一旦DBに入れSQLで集約して数えています。

環境

以下の通り。

  • VPS (仮想環境のLinuxサーバー)
  • WordPress

一応、WAF (Web Application Firewall)も導入したり、色々しています。
ちなみに、今回は11月の1か月分を対象に解析しています。

WordPressに対する(と思われる)もの

日頃からログを見ていても、WordPressを狙ったと思われるアクセス試行が最も多いように感じます。

プラグインが多いせいもありますが、JVN iPediaでも脆弱性の登録数は結構な数のようです。

簡単に導入できて便利ですが、プラグインに脆弱性があると入り口を増やしてしまいかねないのと、導入数が多いので狙われやすいようです。

以下、実際に自サイトに来た不正アクセス試行のログです。

ログインページの存在確認

言わずと知られたWordPressのログインページのURLですが、存在しているかを調査しに来ているようです。

GET [url]/wp-login.php
POST [url]/wp-login.php

404(存在しないページへのアクセス)を記録しているアクセスの中では一番多いのが上の二つです。
wp-login.phpを探しにくるアクセスは、月に250件ほどあります。

対策としては、ログインページのURL変更は基本だと思います。特定のIPアドレス(いわゆる踏み台)からのみアクセスを許可する方法も有効です。

プラグインを狙ったもの

WordPressのプラグインであるFile Managerを狙っているようです。readme.txtを取得しているのは、インストールしているかを確認するためでしょう。

GET /wp-content/plugins/wp-file-manager/readme.txt

FileManagerについては、最近、深刻な脆弱性として報告されていたと思います。

WordPress 用プラグイン File Manager の脆弱性について(JPCERT CC)

xmlrpc.phpにたいするもの

この、xmlrpc.phpに対するPOSTは月に400件ほど来るリクエストで、正直、うんざりするほど見ます。

POST /xmlrpc.php
POST /blog/xmlrpc.php
POST /xmlrpc.php

WordPressのセキュリティ対策としてxmlrpc.phpへのアクセスを抑止するのはよく知られたことだとは思いますが、WordPress以外でもxmlrpc.phpを置いているものもあるようです。

ちなみに、↑のPOSTリクエストからは様々なパラメータでアクセスされてきますが、例を挙げると下の通りです。このdie(@md5(文字列))は結構目にします。

Magento=die(@md5(Apri1))

MagentoはPHPで実装されたECプラットフォームのようです。

特定製品を狙った攻撃

NETGEAR製品の脆弱性を狙ったものです。

GET /currentsetting.htm

NETGEAR 製の複数製品にバッファオーバーフローの脆弱性 (JVN)

↓はDasan製(韓国企業)ルーターの脆弱性を狙ったもの。かなり以前から頻繁に見ます。

POST /GponForm/diag_Form?images/

Dasan GPON home router における認証に関する脆弱性 (JVN iPedia)

PHP絡み

PHPUnitの脆弱性を狙ったもの

これも月に40件程のアクセスがありました。
vendorはcomposerで使用するものですが、Laravelを使用しているサイトでも注意する必要がありそうです。

POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

このアクセスからは、下のようなパラメータが送られてきていました。

<?php  echo 'RCE_VULN|'; echo php_uname();?>

PHPUnitには、任意のコードが実行される脆弱性があるようです。

JVN iPedia - PHPUnit の Util/PHP/eval-stdin.php における任意の PHP コードを実行される脆弱性

Xdebugへのアクセス

XdebugはPHPのデバッグ用のツールで、PHPStormはPHP用のIDE。
まぁ、インストールはしていないので404を返しているわけですが、不正な試行であることに変わりないです。

GET /?XDEBUG_SESSION_START=phpstorm

権限の設定漏れを突いてくるもの

.env

これも404で残ってました。

GET /.env

どうも、node.jsで使用される環境設定用のファイルらしいですが、アクセスが多かったです。

Linuxでは、"."で開始するファイルは隠しファイル(もしくはディレクトリ)なわけですが、そこを読み取ろうとしてくるアクセス試行は他にもありました。具体的には、以下。

GET /.wp-config.php.swp 
GET /.local 
GET /.gitignore 
GET /.production 
GET /.meta 
GET /.remote 
GET /.DS_Store 
GET /.well-known/security.txt 
GET /.vscode/sftp.json 
GET /.addressbook 
GET /.proclog 
GET /.dockerignore 

その他

ログインページを探してくるアクセス

これもログインページを探す試行でしょうか。

HEAD /admindede/login.php
HEAD /manage/login.php
HEAD /admin/login.php
HEAD /manager/login.php
HEAD /dede/login.php
HEAD /htadmin/login.php

ちなみに、HEADメソッドはGETと違って本文を返しません。
存在の確認をしているのでしょう。

HEAD (MDN web docs)

同一IPからの大量リクエスト

こんな感じで瞬間的に大量アクセスを仕掛けられることも頻繁にあります(日時はアクセス日時)。

2020-11-02 07:54:57 GET /myadmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /MyAdmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /cacti/plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /weathermap/editor.php 
2020-11-02 07:55:01 GET /App/?content=die(md5(HelloThinkPHP)) 
2020-11-02 07:55:01 GET /index.php/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /index.php?s=/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /?a=fetch&content=<php>die(@md5(HelloThinkCMF))</php> 
2020-11-02 07:55:03 GET /joomla/ 
(中略)
2020-11-02 07:55:42 POST /my.php 
2020-11-02 07:55:42 POST /qq.php 
2020-11-02 07:55:46 POST /kpl.php 
2020-11-02 07:55:46 POST /hgx.php 
2020-11-02 07:55:46 POST /ppl.php 
2020-11-02 07:55:46 POST /tty.php 
(以下略)

実は以前、自サイト(WordPress)がダウンする事件がありました。
それがこの一連の大量アクセスで、該当のアクセス元IP(中国だった)は上のような大量アクセスをしてxmlrpc.phpまで来たところで、xmlrpc.phpに対して膨大なPOSTリクエストを送り付け、DBが死んだ…という。

この辺りの対策は、「WordPress xmlrpc.php」でググると出てきます。
プラグイン(Site Guard WP Pluginとか)でも対処できますし、fail2banとか、WAFを導入して対処することも可能だと思います。

しておくべき対策

自分なりに実施していることですが、

脆弱性情報は常に確認

IPA(情報処理推進機構)は脆弱性に関する情報を集約・発信しています。RSSでも取れるので、そこで重要な情報は拾えそうです。

また、JVN iPediaは脆弱性に関する情報をデータベースにして公開しています。

twitterアカウントをお持ちの方であれば、JVNやIPAのセキュリティ関連の最新ニュースは、以下のアカウントをフォローすれば自動的に目に飛び込んできます。

twitter

OS・ソフトウェアのアップデートは確認して怠らずにやる

更新があればWordPressのダッシュボードに通知されますし、SIte Guard WP Pluginを入れれば更新の通知をメールにも入れてくれます。

加えて、OS、Webサーバー、PHPの更新もきちんとやっておいたほうが良さそうです。

とは言いつつも、いきなり本番環境に適用なんて普通はせず、検証後の適用が普通と思います。なので、そう早急にはできないかもしれません。ですが、WAFの導入などで多層的な防御を可能にすることもできます。

上でも挙げましたが、ファイルに対するアクセス権限の設定も確認しておく必要がありますね。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webサイトに来る実際の攻撃(不審なアクセス)を実例付きで紹介してみる

グローバルIPアドレスを持つWebサーバーで自分のサイトを運営していますが、脆弱性を狙ったと思われる不審なアクセスが日常的に来ます。一度はまとめてみたい…と思っていたのですが、同じことをされている方の記事を見つけ、自分も同様の内容をまとめてみました。

不審なアクセスは種類を挙げればきりがありませんが、今回は主だったもの(件数が多いもの)について紹介したいと思います。最後には対策を挙げました。

ちなみに、数を数える時は、ログを一旦DBに入れSQLで集約して数えています。

環境

以下の通り。

  • VPS (仮想環境のLinuxサーバー)
  • WordPress

一応、WAF (Web Application Firewall)も導入したり、色々しています。
ちなみに、今回は11月の1か月分を対象に解析しています。

WordPressに対する(と思われる)もの

日頃からログを見ていても、WordPressを狙ったと思われるアクセス試行が最も多いように感じます。

プラグインが多いせいもありますが、JVN iPediaでも脆弱性の登録数は結構な数のようです。

簡単に導入できて便利ですが、プラグインに脆弱性があると入り口を増やしてしまいかねないのと、導入数が多いので狙われやすいようです。

以下、実際に自サイトに来た不正アクセス試行のログです。

ログインページの存在確認

言わずと知られたWordPressのログインページのURLですが、存在しているかを調査しに来ているようです。

GET [url]/wp-login.php
POST [url]/wp-login.php

404(存在しないページへのアクセス)を記録しているアクセスの中では一番多いのが上の二つです。
wp-login.phpを探しにくるアクセスは、月に250件ほどあります。

対策としては、ログインページのURL変更は基本だと思います。特定のIPアドレス(いわゆる踏み台)からのみアクセスを許可する方法も有効です。

プラグインを狙ったもの

WordPressのプラグインであるFile Managerを狙っているようです。readme.txtを取得しているのは、インストールしているかを確認するためでしょう。

GET /wp-content/plugins/wp-file-manager/readme.txt

FileManagerについては、最近、深刻な脆弱性として報告されていたと思います。

WordPress 用プラグイン File Manager の脆弱性について(JPCERT CC)

xmlrpc.phpにたいするもの

この、xmlrpc.phpに対するPOSTは月に400件ほど来るリクエストで、正直、うんざりするほど見ます。

POST /xmlrpc.php
POST /blog/xmlrpc.php
POST /xmlrpc.php

WordPressのセキュリティ対策としてxmlrpc.phpへのアクセスを抑止するのはよく知られたことだとは思いますが、WordPress以外でもxmlrpc.phpを置いているものもあるようです。

ちなみに、↑のPOSTリクエストからは様々なパラメータでアクセスされてきますが、例を挙げると下の通りです。このdie(@md5(文字列))は結構目にします。

Magento=die(@md5(Apri1))

MagentoはPHPで実装されたECプラットフォームのようです。

特定製品を狙った攻撃

NETGEAR製品の脆弱性を狙ったものです。

GET /currentsetting.htm

NETGEAR 製の複数製品にバッファオーバーフローの脆弱性 (JVN)

↓はDasan製(韓国企業)ルーターの脆弱性を狙ったもの。かなり以前から頻繁に見ます。

POST /GponForm/diag_Form?images/

Dasan GPON home router における認証に関する脆弱性 (JVN iPedia)

PHP絡み

PHPUnitの脆弱性を狙ったもの

これも月に40件程のアクセスがありました。
vendorはcomposerで使用するものですが、Laravelを使用しているサイトでも注意する必要がありそうです。

POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

このアクセスからは、下のようなパラメータが送られてきていました。

<?php  echo 'RCE_VULN|'; echo php_uname();?>

PHPUnitには、任意のコードが実行される脆弱性があるようです。

JVN iPedia - PHPUnit の Util/PHP/eval-stdin.php における任意の PHP コードを実行される脆弱性

Xdebugへのアクセス

XdebugはPHPのデバッグ用のツールで、PHPStormはPHP用のIDE。
まぁ、インストールはしていないので404を返しているわけですが、不正な試行であることに変わりないです。

GET /?XDEBUG_SESSION_START=phpstorm

権限の設定漏れを突いてくるもの

.env

これも404で残ってました。

GET /.env

どうも、node.jsで使用される環境設定用のファイルらしいですが、アクセスが多かったです。

Linuxでは、"."で開始するファイルは隠しファイル(もしくはディレクトリ)なわけですが、そこを読み取ろうとしてくるアクセス試行は他にもありました。具体的には、以下。

GET /.wp-config.php.swp 
GET /.local 
GET /.gitignore 
GET /.production 
GET /.meta 
GET /.remote 
GET /.DS_Store 
GET /.well-known/security.txt 
GET /.vscode/sftp.json 
GET /.addressbook 
GET /.proclog 
GET /.dockerignore 

その他

ログインページを探してくるアクセス

これもログインページを探す試行でしょうか。

HEAD /admindede/login.php
HEAD /manage/login.php
HEAD /admin/login.php
HEAD /manager/login.php
HEAD /dede/login.php
HEAD /htadmin/login.php

ちなみに、HEADメソッドはGETと違って本文を返しません。
存在の確認をしているのでしょう。

HEAD (MDN web docs)

同一IPからの大量リクエスト

こんな感じで瞬間的に大量アクセスを仕掛けられることも頻繁にあります(日時はアクセス日時)。

2020-11-02 07:54:57 GET /myadmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /MyAdmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /cacti/plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /weathermap/editor.php 
2020-11-02 07:55:01 GET /App/?content=die(md5(HelloThinkPHP)) 
2020-11-02 07:55:01 GET /index.php/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /index.php?s=/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /?a=fetch&content=<php>die(@md5(HelloThinkCMF))</php> 
2020-11-02 07:55:03 GET /joomla/ 
(中略)
2020-11-02 07:55:42 POST /my.php 
2020-11-02 07:55:42 POST /qq.php 
2020-11-02 07:55:46 POST /kpl.php 
2020-11-02 07:55:46 POST /hgx.php 
2020-11-02 07:55:46 POST /ppl.php 
2020-11-02 07:55:46 POST /tty.php 
(以下略)

実は以前、自サイト(WordPress)がダウンする事件がありました。
それがこの一連の大量アクセスで、該当のアクセス元IP(中国だった)は上のような大量アクセスをしてxmlrpc.phpまで来たところで、xmlrpc.phpに対して膨大なPOSTリクエストを送り付け、DBが死んだ…という。

この辺りの対策は、「WordPress xmlrpc.php」でググると出てきます。
プラグイン(Site Guard WP Pluginとか)でも対処できますし、fail2banとか、WAFを導入して対処することも可能だと思います。

しておくべき対策

自分なりに実施していることですが、

脆弱性情報は常に確認

IPA(情報処理推進機構)は脆弱性に関する情報を集約・発信しています。RSSでも取れるので、そこで重要な情報は拾えそうです。

また、JVN iPediaは脆弱性に関する情報をデータベースにして公開しています。

twitterアカウントをお持ちの方であれば、JVNやIPAのセキュリティ関連の最新ニュースは、以下のアカウントをフォローすれば自動的に目に飛び込んできます。

twitter

OS・ソフトウェアのアップデートは確認して怠らずにやる

更新があればWordPressのダッシュボードに通知されますし、SIte Guard WP Pluginを入れれば更新の通知をメールにも入れてくれます。

加えて、OS、Webサーバー、PHPの更新もきちんとやっておいたほうが良さそうです。

とは言いつつも、いきなり本番環境に適用なんて普通はせず、検証後の適用が普通と思います。なので、そう早急にはできないかもしれません。ですが、WAFの導入などで多層的な防御を可能にすることもできます。

上でも挙げましたが、ファイルに対するアクセス権限の設定も確認しておく必要がありますね。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webサイトに来る実際の攻撃(不審なアクセス)を公開してみる

グローバルIPアドレスを持つWebサーバーで自分のサイトを運営していますが、脆弱性を狙った不審なアクセスが毎日来ます。一度内容まとめたいと考えていたのですが、同様のことをされている方の記事を見つけ、自分もしてみました。

実際に来た不正アクセス試行は種類を挙げればきりがありませんが、件数が多いものを紹介したいと思います。有効な対策だと思われることについても言及しています。

ちなみに解析の方法は、ログを一旦DBに入れてSQLで集約しています。

インターネットにWebサーバーを公開すると、こんな攻撃が来る…という参考になればと思います。と考えると、はてなブログやレンタルサーバーはプロバイダーが責任を持ってくれるから安心ではありますね。

環境

  • VPS (Virtual Private Server: 仮想環境のLinuxサーバー)
  • WordPress

一応、WAF (Web Application Firewall)も導入したり、色々しています(詳細は伏せます)。分析ですが、今回は11月の直近の分のみを対象に解析しています。

WordPressに対する(と思われる)攻撃

日頃からログ・通知を見ていても、WordPressを標的にしたと思われるアクセス試行が最も多く感じます。

公開されているWordPressの脆弱性については、プラグインが多いせいもありますが、JVN iPediaでも結構な数が登録されています。

WPは導入が容易かつ機能拡張も楽にできるので便利ですが、プラグインに脆弱性があると不正アクセスの入り口を増やしてしまいますし、世界的にも導入数が多いこともあってターゲットになりやすいようです。

以下、実際に自サイトに来た不正アクセス試行のログです。

ログインページの存在確認

言わずと知られたWordPressのログインページ(wp-login.php)ですが、それがアクセス可能かを調査しに来ているようです。

GET [url]/wp-login.php
POST [url]/wp-login.php

404(存在しないページへのアクセス)を記録しているアクセスの中では一番多いのが上の二つです。wp-login.phpを探しにくるアクセスは、実は月に250件ほどあります。

対策としては、ログインページのURL変更が可能で、これは基本と思います。また、アクセス元を特定のIPアドレスのみに制限する方法も有効です(いわゆる踏み台の利用)。

プラグインの脆弱性を狙った攻撃

WordPressのプラグインであるFile Managerを狙ったもの。readme.txtを取得しているのは、インストールしているかを確認するためでしょう。

GET /wp-content/plugins/wp-file-manager/readme.txt

FileManagerについては、最近、深刻な脆弱性として報告されていました。

WordPress 用プラグイン File Manager の脆弱性について(JPCERT CC)

上でも書きましたが、WordPress本体に脆弱性がなくても、プラグインの脆弱性を突くことで攻撃ができてしまいます。WPは便利な反面、リスクも少なくありません。

xmlrpc.phpにたいする攻撃

この、xmlrpc.phpに対するPOSTは月に400件ほど来るリクエストで、正直、うんざりするほど見ます。

POST /xmlrpc.php
POST /blog/xmlrpc.php
POST /xmlrpc.php

xmlrpc.phpへのアクセス抑止はググれば結構な数のサイトで解説されています。

ちなみに、上のようなPOSTリクエストでは様々なパラメータ付きでアクセスがあります。例を挙げると下の通りです。このdie(@md5(文字列))は結構目にします。

Magento=die(@md5(Apri1))

MagentoはPHPで実装されたECプラットフォームのようです。WP以外にも同一名称のファイル名を使用したパッケージがあるようです。

特定製品を狙った攻撃

NETGEAR製品の脆弱性を狙ったものです。

GET /currentsetting.htm

NETGEAR 製の複数製品にバッファオーバーフローの脆弱性 (JVN)

↓はDasan製(韓国企業)ルーターの脆弱性を狙ったもの。かなり以前から頻繁に見ます。

POST /GponForm/diag_Form?images/

Dasan GPON home router における認証に関する脆弱性 (JVN iPedia)

この辺りの情報は、JVN iPediaにもありますし、IPAのセキュリティに関するニュースを確認していれば自動的に目に飛び込んでくると思います。

PHP絡み

PHPUnitの脆弱性を狙った攻撃

これも月に40件程のアクセスがありました。
vendorはcomposerで使用するものですが、Laravelを使用しているサイトでも注意が必要そうです。

POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

このアクセスからは、下のようなパラメータが送られてきていました。

<?php  echo 'RCE_VULN|'; echo php_uname();?>

PHPUnitには、任意のコードが実行される脆弱性があるようですので、そこを突いた攻撃でしょう。サーバーの情報を確認するコマンドを送っているようです。

JVN iPedia - PHPUnit の Util/PHP/eval-stdin.php における任意の PHP コードを実行される脆弱性

Xdebugへのアクセス

XdebugはPHPのデバッグ用のツールで、PHPStormはPHP用のIDE。
まぁ、インストールはしていないので404を返しているわけですが、不正な試行であることに変わりありません。

GET /?XDEBUG_SESSION_START=phpstorm

権限の設定漏れを突いてくる攻撃

.env

これもHTTPステータス404を返したログとして残ってました。

GET /.env

どうも、node.jsで使用される環境設定用のファイルらしいですが、アクセスが多かったです。

Linuxでは、"."で開始するファイルは隠しファイル(もしくはディレクトリ)なわけですが、そこを読み取ろうとしてくるアクセス試行は他にもありました。具体的には、以下。

GET /.wp-config.php.swp 
GET /.local 
GET /.gitignore 
GET /.production 
GET /.meta 
GET /.remote 
GET /.DS_Store 
GET /.well-known/security.txt 
GET /.vscode/sftp.json 
GET /.addressbook 
GET /.proclog 
GET /.dockerignore 

インストールや更新の後はディレクトリの中身が丸見えになっていないかや、アクセス権限の確認をきちんとしておく必要があります。

その他

ログインページの探索試行

下もログインページを探す試行でしょう。

HEAD /admindede/login.php
HEAD /manage/login.php
HEAD /admin/login.php
HEAD /manager/login.php
HEAD /dede/login.php
HEAD /htadmin/login.php

ちなみに、HEADメソッドはGETと違って本文を返しませんので、ファイルの存在の確認をしていると考えられます。

HEAD (MDN web docs)

同一IPからの大量リクエスト

下の感じで短時間に大量アクセスを仕掛けられることも頻繁にあります(日時はアクセス日時)。

2020-11-02 07:54:57 GET /myadmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /MyAdmin/scripts/db___.init.php 
2020-11-02 07:54:58 GET /plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /cacti/plugins/weathermap/editor.php 
2020-11-02 07:54:59 GET /weathermap/editor.php 
2020-11-02 07:55:01 GET /App/?content=die(md5(HelloThinkPHP)) 
2020-11-02 07:55:01 GET /index.php/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /index.php?s=/module/action/param1/${@die(md5(HelloThinkPHP))} 
2020-11-02 07:55:01 GET /?a=fetch&content=<php>die(@md5(HelloThinkCMF))</php> 
2020-11-02 07:55:03 GET /joomla/ 
(中略)
2020-11-02 07:55:42 POST /my.php 
2020-11-02 07:55:42 POST /qq.php 
2020-11-02 07:55:46 POST /kpl.php 
2020-11-02 07:55:46 POST /hgx.php 
2020-11-02 07:55:46 POST /ppl.php 
2020-11-02 07:55:46 POST /tty.php 
(以下略)

実は以前、自サイト(WordPress)がダウンする事件がありました。それが上の一連の大量アクセスで、該当のアクセス元(中国だった)は大量のリクエストを送付してxmlrpc.phpまで来たところで、xmlrpc.phpに対し膨大なPOSTリクエストを送り付け、DBが死んだ…ことがありました。

対策については、「WordPress xmlrpc.php」でググると出てきます。プラグイン(Site Guard WP Pluginとか)でも対処できますし、fail2banとか、WAFの導入も有効です(fail2banもWAFもわりとメモリを消費しますが)。

有効な対策

自分なりに実施していることです。

脆弱性情報は常に確認

IPA(情報処理推進機構)は脆弱性に関する情報を集約・発信しています。RSSも公開されているので、そこで重要な情報は拾えます。

また、JVN iPediaは脆弱性に関する情報をデータベース化し、公開しています。

また、twitterアカウントをお持ちであれば、JVNやIPAのセキュリティ関連の最新ニュースは、以下のアカウントをフォローすれば自動的に流れてきます(これが結構時短になるので、おすすめです)。

twitter

OS・ソフトウェアのアップデートは確認・怠らずに実施する

プラグインや本体の更新通知についてはWordPressのダッシュボードに通知されるはずですし、SIte Guard WP Pluginを使えば更新の通知をメールにも入れてくれます(他にも色々な対策が使用可能です)。

加えて、OS、Webサーバー、PHPの更新も可能な限り、きちんと実施したほうが良さそうです。

とは言いつつ、いきなり本番環境に適用は普通しませんし、検証後に適用するのが普通だと思います。ですので、そう早急にはできないかもしれません。この辺りは、WAFを導入することで多層的な防御を施すことも有効な施策になります。

ちなみに、yumでインストールしたWebサーバー(Apache HTTP Serverとか)のバージョンは、httpd -vで確認すると古いバージョンが見えますが、バックポート(古いバージョンのソフトウェアを更新すること)が施されているようなので、むやみにOSS版(ソースからコンパイルするやつ)にする必要はないかもしれません。

でも、RHEL版とCentOS版は同様のバックポートが施されているんですかね? 試しにApacheのバグフィックスに関してRHELのサイトとCentOSのRPMのChangelogを見たりしてみましたが、CentOS版にも施されているようには…見えましたが。詳しい方がいらっしゃれば、むしろ、この辺りは教えいて頂きたいです。

また、上でも挙げましたが、ファイルに対するアクセス権限の設定にも注意を払う必要がありますね。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

牛丼で分かるオブジェクト指向

はじめに

今年もアドベントカレンダーはじまりました!
この記事はHamee Advent Calendar 2020の1日目の記事です。
Hameeのエンジニアが自分の興味あるテーマについて自由に書いていきます。
毎年一緒に参加して盛り上げてくれる皆さんに感謝です :clap:
今年で6年目の参加となりました!

オブジェクト指向とは

僕の解釈ではデータを扱いやすいオブジェクト単位に設計し、それらを組み合わせることで効率よく開発をしていく指向のことだと思っています。
ただまぁ人によって解釈が違うし新卒や初心者にとっては「???」となることが多い考え方だと思います。
そこで、牛丼を例にオブジェクト指向を紐解いていこうと思います。

なぜ牛丼?

Hameeは小田原にある会社です。
小田原には狭いエリアに牛丼チェーンが3つもあります。
この松屋・すき家・吉野家は日々Hamee社員の胃袋を支えています。
私も利用者の1人ですが、この3店舗のレシートの出方を見て今回の記事を書こうと思いつきました。

スクリーンショット 2020-11-28 15.38.05.png

これがその問題のレシート

IMG_4494.JPG

左から松屋・すき家・吉野家
松屋「たっぷりネギたま牛めし」
すき家「牛丼弁当・ねぎ玉TP」
吉野家「牛丼・ねぎ玉子」

松屋は1品なのに対して、すき家と吉野家は2品でレシートが切られています。
ここにオブジェクト指向の違いがあります。
松屋は非オブジェクト指向、すき家と吉野家はオブジェクト指向です。

どういうことかclassで説明

Matsuya.php
class Matsuya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => new NegiTamaGyudon(),
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTamaGyudon{}

Matsuya::order('NegiTamaGyudon');
Sukiya.php
<?php
class Sukiya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => [new Gyudon(), new NegiTama()],
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTama{}

Sukiya::order('NegiTamaGyudon');

以降吉野家はすき家と同様なので略
違いが分かりますか?
このままだと「え、こんなの作りの好みじゃないの?」って言われそうなので、ここから新メニュー開発した場合を想定しましょう。
エンジニア的に言うと「仕様変更」です。

新メニュー「キムチ牛丼」

Matsuya.php
<?php
class Matsuya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => new NegiTamaGyudon(),
            'KimchiGyudon' => new KimchiGyudon(),
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTamaGyudon{}
class KimchiGyudon{}

Matsuya::order('KimchiGyudon');
Sukiya.php
<?php
class Sukiya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => [new Gyudon(), new NegiTama()],
            'KimchiGyudon' => [new Gyudon(), new Kimchi()],
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTama{}
class Kimchi{}

Sukiya::order('KimchiGyudon');

まだ違いが分からない?
じゃあ次の新メニューはどうでしょう。

新メニュー「ネギ玉キムチ牛丼」

Matsuya.php
<?php
class Matsuya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => new NegiTamaGyudon(),
            'KimchiGyudon' => new KimchiGyudon(),
            'NegitamaKimchiGyudon' => new NegitamaKimchiGyudon(),
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTamaGyudon{}
class KimchiGyudon{}
class NegitamaKimchiGyudon{}

Matsuya::order('NegitamaKimchiGyudon');
Sukiya.php
<?php
class Sukiya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => [new Gyudon(), new NegiTama()],
            'KimchiGyudon' => [new Gyudon(), new Kimchi()],
            'NegitamaKimchiGyudon' => [new Gyudon(), new NegiTama(), new Kimchi()],
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTama{}
class Kimchi{}

Sukiya::order('NegitamaKimchiGyudon');

ここで大きく異なるのはすき家の方は新しくクラス定義を追加していません。
既存のクラス(オブジェクト)の組み合わせで簡単に新メニューが作成できます。
それに対して松屋はどんな新メニューができても必ずクラス(オブジェクト)を追加する必要があります。
新メニュー開発はどちらが楽でしょう?
とどめにこんな新メニューはどうでしょうか。

「豚丼」追加、もちろん全トッピング可

Matsuya.php
<?php
class Matsuya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'NegiTamaGyudon' => new NegiTamaGyudon(),
            'KimchiGyudon' => new KimchiGyudon(),
            'NegitamaKimchiGyudon' => new NegitamaKimchiGyudon(),

            'Butadon'        => new Butadon(),
            'NegiTamaButadon' => new NegiTamaButadon(),
            'KimchiButadon' => new KimchiButadon(),
            'NegitamaKimchiButadon' => new NegitamaKimchiButadon(),
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class NegiTamaGyudon{}
class KimchiGyudon{}
class NegitamaKimchiGyudon{}

class Butadon{}
class NegiTamaButadon{}
class KimchiButadon{}
class NegitamaKimchiButadon{}

Matsuya::order('NegitamaKimchiButadon');
Sukiya.php
<?php
class Sukiya {
    public static function order($order){
        $menu = [
            'Gyudon'         => new Gyudon(),
            'Butadon'        => new Butadon(),
            'NegiTamaGyudon' => [new Gyudon(), new NegiTama()],
            'KimchiGyudon' => [new Gyudon(), new Kimchi()],
            'NegitamaKimchiGyudon' => [new Gyudon(), new NegiTama(), new Kimchi()],
            'NegiTamaButadon' => [new Butadon(), new NegiTama()],
            'KimchiButadon' => [new Butadon(), new Kimchi()],
            'NegitamaKimchiButadon' => [new Butadon(), new NegiTama(), new Kimchi()],
        ];

        return $menu[$order];
    }
}

class Gyudon{}
class Butadon{}
class NegiTama{}
class Kimchi{}

Sukiya::order('NegitamaKimchiButadon');

松屋はメニュー分のクラスが増えましたがすき家は豚丼クラスを追加しただけです。
これがオブジェクト指向です。
オブジェクトを使い回せる点が非常に強いです。
このあと鶏丼や羊丼が来ても簡単に対応できます。

ちなみに小話ですが、すき家と吉野家には豚丼がありますが、松屋にはありません。

まとめ

結構無理やり話をまとめたので異論はあると思いますがどうか温かい目で見ていただけるとw
少しでもオブジェクト指向の理解の助けになれば幸いです。

おわりに

アドカレ6年目です。
毎年この時期になると「あぁもうそんな時期か、1年って早いなぁ」と実感します。
この後もHameeのアドカレをお楽しみに!

ちなみにどのお店の牛丼もそれぞれ違った美味しさがあって、全部大変美味しかったです!
(撮影後はスタッフが美味しくいただきました。)

IMG_4492.JPG

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】ユーザー定義関数を初心者に説明してみる

初心者のみなさん、関数使ってますか?
普通に使ってるってツッコミありがとうございます笑

公式から

公式: ユーザー定義関数

公式の例から
<?php
function foo(引数1, 引数2, /* ..., */ 引数_n)
{
    echo '関数の例' . '<br>';
    return $retval;
}
?>

上の例は単に関数ってこんな形だよってだけの例ですね。
あんまり参考になりません。

ちょっと使える形にしてみます。

改造後
<?php
function add(num1, num2)
{
    $sum = num1 + num2;
    return $sum;
}
//call
$num1 = 5;
$num2 = 10;
echo add(num1, num2);
//-> 15
?>

使えない??
たしかに使えないですね笑
そもそも1行で書ける足し算を関数にしただけなので。

では関数ってどんなときに使うと効果的なんでしょうか?

関数を使う場面

例えばこんな処理
DBに接続してユーザーがいたら、
ユーザー情報を取ってきて、
選択した値によって表示するカラムを変更して出力。

関数を使った例
<?php
$input_column_int = 3;
$dbh = connectDb();
$user = findUser($dbh, $id));
if ($user) {
    outputColumn($user, $input_column_int);
} else {
    echo 'ユーザーが見つかりません';
}
connectDb();
//->PDOを使ってDBに接続する関数
findUser();
//SQLをPDOで走らせて$idに対応するユーザー情報を取ってくる関数
outputColumn()
//-> $userのカラム番号=$input_column_intの値を表示する関数

詳しいコードは書きませんが、関数を使っているとスッキリ見えますよね?
全部のコードをベタ書きすると読むのがウンザリするのはなぜでしょうか?

処理が2つ以上存在してくるとややこしくなる

なのでこういうことです!

  • 関数で処理を隠す
  • 関数名でなんの処理かわかるようにする
  • 一気に可読性があがる

関数を作るときに意識したいこと

関数は大きく分けて2種類に分かれます。

1.状態を変化させる
2.数式などで値やオブジェクトを返す

  • は例えば出力です。 やっている処理ブロック名前をつけてあげるイメージです。 ここでは1行の処理ですが20行ぐらいあって、それに適切な関数名をつけてあげたらどうでしょう? 関数名がコメントの役割を果たしてくれるってわけです!
1.状態を変化させる
function outputColumn($user, $id) {
    echo $user[$id];
}
  • 返り値のあるパターンです 引数に入力された値を元に計算やDB処理を行い、値やオブジェクトを返します。 関数はブラックボックスにすることができます。
2.数式などで値やオブジェクトを返す
function findUser($dbh, $id) {
    $sql = 'select * from users where ?';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([$id]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);
    return $user;
}

関数を1処理にすることのメリット

  • テストが容易になる
  • 使い回しやすくなる
  • 関数名が明確になり、コード内の可読性があがる

まとめ

・関数化するときに一番気をつけることは、読みやすくすることです。
・そして関数化した部分は十分なテストをし、ブラックボックス化して後々チェックする部分極力減らしましょう

最初から関数化しようとせずに、処理が増えてきた後にやるほうが無難です。
最初からやってやろうはあまりうまくいきません笑

お願い

LGTMお願いします!
ストックのついでにお願いします!
モチベーションがあがります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MAMPでphpのエラーをターミナルから問題を探す方法

MAMPでphpエラーが出た際にターミナルでエラーを探す方法

みなさんこんにちは。
今回はMAMP環境でエラーが出た時にエラーがどこから出てきているのかを確かめる方法を紹介します。

また、間違えや指摘等ございましたらコメントよろしくお願い致します。

環境

今回僕のパソコンはmacなのでwindowsの方には対応しておりません。

エラーの確認方法1

初心者の頃(現在も初心者)にエラーが出たらどこを見て、何が問題なのかを見つけることさえできていませんでした。

まず、エラーが出た際にはブラウザのディベロッパーツールを利用して確認してみましょう。プログラムを反映させたいページ(現在エラーが出ていて動いていないページ)で右クリック→検証→consoleを確認してみましょう。

こちらにエラーが出てきます。これは大体利用されていると思うので次の方法も紹介します。

エラーの確認方法2

ターミナルから確認する方法です。ターミナルは真っ黒の画面になっていて、cuiなので初めは操作やりにくいけど意外と慣れたら使いやすいかもです。

方法としてはまず、ターミナルを開いてエラーが出ているログが見れる場所まで移動します。

cd /Applications/MAMP/logs

cdコマンドで移動します。そして'tail -f'コマンドでエラーログをみにいきます。例えば、、、

tail -f php_error.log

これでphpのエラーログを見ることができます。 ターミナルのコマンドについてはこちらのサイトでたくさん記載されていたので紹介いたします。

ターミナルの基本コマンド

このようにログをみたりしながらエラーを解決していけばなんとかなることが多いです。

初めは何が悪いのかエラーをみてもわからないことがたくさんすぎて困りますよね。

この記事が誰かの助けに慣れば幸いです。 ありがとうございました。

変更

タグの修正を行いました。指摘していただきありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】コメント機能実装

学習内容を備忘録としてまとめます。
コメント機能を実装しましたので、作成方法を記載します。
tatteimasu.gif
投稿に対してコメントを行えるようになっています。

※動作画面に出てきているモーダル画面については下記をご参照ください。
【JavaScript】モーダル画面実装

実装方法

実装方法について記載していきます。

テーブル構成

image.png

まずはコメントテーブルを作成していきます。
それぞれのカラムの役割としては下記のようになっています。

カラム名 役割
id コメントID
text コメント内容
image コメントに添付される画像
comment_id コメントに対するコメントのID
user_id コメントをしたユーザーID
post_id コメントをした投稿ID
created_id コメントをした時刻

コメントボタン作成

コメントをするためのボタンを作成します。

<div class="comment_confirmation">
 <p class="modal_title" >この投稿にコメントしますか?</p>
 <p class="post_content"><?= nl2br($post['text']) ?></p>
 <form method="post" action="../comment/comment_add_done.php" enctype="multipart/form-data">
 <textarea class="textarea form-control" placeholder="コメントを入力ください" name="text"></textarea>
 <div class="comment_img">
  <input type="file" name="image_name" class="comment_image" accept="image/*" multiple>
 </div>
   <input type="hidden" name="id" value="<?= $post['id'] ?>">
   <div class="post_btn">
   <button class="btn btn-outline-danger" type="submit" name="comment" value="comment">コメント</button>
   <button class="btn btn-outline-primary modal_close" type="button">キャンセル</button>
   </div>
 </form>
</div>

コメント内容の記載と画像を添付できるようになっており、
comment_add.phpに遷移しコメントテーブルにINSERTをかけるような処理になっています。
※上記のコードは説明上不要な部分を省略しているので、トップの動作画面とは違いがあります。

<input type="hidden" name="id" value="<?= $post['id'] ?>">

こちらで投稿IDを取得してどの投稿に対してのコメントなのかを判断しています。
先ほど説明したcomment_add.phpについてみていきます。

コメント処理

comment_add.php
<?php
try
{
$date = new DateTime();
$date->setTimeZone(new DateTimeZone('Asia/Tokyo'));
$comment_text=$_POST['text'];
$comment_image_name=$_FILES['image_name'];
if(!empty($_POST['comment_id']))
{
$comment_id=$_POST['comment_id'];
}
$user_id=$_SESSION['user_id'];
$post_id=$_POST['id'];

if($comment_text=='')
{
    set_flash('danger','コメントが空です');
    reload();
} 

if($comment_image_name['size']>0)
{
    if($comment_image_name['size']>1000000)
    {
        set_flash('danger','画像が大きすぎます');
        reload();
    }
    else
    {
        move_uploaded_file($comment_image_name['tmp_name'],'./image/'.$comment_image_name['name']);

    }
}

$comment_text=htmlspecialchars($comment_text,ENT_QUOTES,'UTF-8');
$user_id=htmlspecialchars($user_id,ENT_QUOTES,'UTF-8');
$dsn = 'mysql:dbname=db;host=localhost;charset=utf8';
$user = 'root';
$password = '';
$dbh = new PDO($dsn,$user,$password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'INSERT INTO comment(text,image,user_id,created_at,post_id,comment_id) VALUES (?,?,?,?,?,?)';
$stmt = $dbh -> prepare($sql);
$data[] = $comment_text;
$data[] = $comment_image_name['name'];
$data[] = $user_id;
$data[] = $date->format('Y-m-d H:i:s');
$data[] = $post_id;
if(!empty($comment_id))
{
$data[] = $comment_id;
} 
else 
{
$data[] = '';
}
$stmt -> execute($data);
$dbh = null;

set_flash('sucsess','コメントを追加しました');
header('Location:../post/post_disp.php?post_id='.$post_id.'');

}   
catch (Exception $e)
{
print'ただいま障害により大変ご迷惑をお掛けしております。';
exit();
}

?>

コメント内容と添付されている画像を確認して、コメントテーブルにINSERTしています。

if(!empty($_POST['comment_id']))
{
$comment_id=$_POST['comment_id'];
}

こちらの行はcomment_idがPOSTされたときに、$comment_idに値を渡しています。
後ほど説明しますが、コメントにコメントされた場合に$comment_idに値を渡すようになっております。

$comments = get_comments($post['id']);
foreach($comments as $comment):
if(empty($comment['comment_id'])):
$comment_user = get_user($comment['user_id']);
<div class="comment">
  <div class="user_info">
    <img src="/user/image/<?= $comment_user['image'] ?>">
    <?php print''.$comment_user['name'].''; ?>
  </div>
<span class="comment_text"><?= $comment['text'] ?></span>
<?php
if(!empty($comment['image'])){
print'<p class="comment_image"><img src="../comment/image/'.$comment['image'].'"></p>';
}
print'<span class="comment_created_at">'.convert_to_fuzzy_time($comment['created_at']).'</span>';
endif;
?>

あとはお好みの箇所へコメント情報をブラウザに表示します。

if(empty($comment['comment_id'])):

comment_idが空であるかどうかでコメントのコメントなのかを判断して、
trueの場合は表示するようになっています。

$comments = get_comments($post['id']);
$comment_user = get_user($comment['user_id']);

get_comments関数で引数の投稿IDを元にデータからコメント情報を取得し、
get_userでコメントIDからコメントしたユーザー情報を取得しています。

それぞれの関数については下記になります。

function get_comments($post_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM comment
            WHERE post_id = :id";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':id' => $post_id));
    return $stmt->fetchAll();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}

function get_user($user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT id,name,password,profile,image
            FROM user
            WHERE id = :id AND delete_flg = 0 ";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':id' => $user_id));
    return $stmt->fetch();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}

コメントに返信する機能

<div class="reply_comment_confirmation">このコメントに返信しますか</p>
  <p class="post_content"><?= nl2br($comment['text']) ?></p>
  <form method="post" action="../comment/comment_add_done.php" enctype="multipart/form-data">
    <p>コメント内容を入力ください。</p>
    <input type="text" name="text">
    <p>画像を選んでください。</p>
    <input type="file" name="image_name">
    <input type="hidden" name="id" value="<?= $post['id'] ?>">
    <input type="hidden" name="comment_id" value="<?= $comment['id'] ?>">
    <button class="btn btn-outline-danger" type="submit" name="comment" value="comment">コメント</button>
    <button class="btn btn-outline-primary modal_close" type="button">キャンセル</button>
  </form>
</div>

コメントに返信する機能を実装します。
上記はその機能のボタンになっているのですが、ほぼ通常にコメントするボタンと変わりません。

<input type="hidden" name="comment_id" value="<?= $comment['id'] ?>">

こちらで返信対象のコメントのIDを取得しています。
これでcomment_idを用いてコメントに返信したコメントか、またコメントのコメントなのかを判断することができます。

<?php
$reply_comments = get_reply_comments($post['id'],$comment['id']); 
foreach($reply_comments as $reply_comment):
?>
<div class="reply">
<?php
  if($reply_comment['comment_id']==$comment['id']):
    $reply_comment_user = get_user($reply_comment['user_id']);
?>
    <div class="user_info">
      <img src="/user/image/<?= $reply_comment_user['image'] ?>">
      <?php print''.$reply_comment_user['name'].''; ?>
    </div>
  <span class="comment_text">'.$reply_comment['text'].'</span>';
  if(!empty($reply_comment['image'])){
  <p class="comment_image"><img src="../comment/image/'.$reply_comment['image'].'"></p>';
}

取得したコメントのコメント情報を元にブラウザに表示します。
こちらは対象になったコメントの中に記載しています。

$reply_comments = get_reply_comments($post['id'],$comment['id']); 

get_reply_comments関数で投稿IDとコメントIDを元に、コメントに返信したコメント情報を取得しています。
下記のような処理が行われています。

function get_reply_comments($post_id,$comment_id){

  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM comment
            WHERE post_id = :id AND comment_id = :comment_id";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':id' => $post_id , ':comment_id' => $comment_id));
    return $stmt->fetchAll();

  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}

投稿IDと対象のコメントIDを元にコメント情報を取得しています。
先ほどのブラウザに表示する画面に戻りまして、

$reply_comment_user = get_user($reply_comment['user_id']);

先ほどの$reply_commentで、コメントに返信したユーザー情報をget_user関数で取得しています。

soratobuman.gif

上記のようにコメントに返信することができるようになります。
※説明で余分な部分は省いているため、上の動作画面とは違う画面になる可能性があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP] エラー解決 php artisan migrate

Udemyにて学習した際にエラーにハマりましたのでその解決方法を投稿したいと思います。

今回のエラー文は下記になります。
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: nodename nor servname provided, or not known (SQL: select * from information_schema.tables where table_schema = laravel_task and table_name = migrations and table_type = 'BASE TABLE')
2020-11-25_12-34-39-47c2c1db8e1dbed6e5db458f9a1dcb3e.png

エラー分の解説

データベースに接続する際、拒否されています。
つまりlaravelの.envにてはポート番号 password データベースの名前が間違っている可能性が
高いとなります。

.env ポート番号 phpMyadmin を確認しよう

env.php
//laravelの .envになります。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_task
DB_USERNAME=laravel_user //phpMyadminの特権にusernameがあります。
DB_PASSWORD=hosthost

2020-11-25_12-51-33-0e26f83ec25a51da85872d4b77c8f59b.png
e7e7520802da32fab0aae676b6e86f1d.png

エラー解決

DB_HOSTを確認すると`phpMyadminでは[%] .envでは[127.0.0.1]
これがエラーの原因となります。こちらを下記に書き換えます。

env.php
//laravelの .envになります。
DB_CONNECTION=mysql
DB_HOST=localhost  //ここを書き換え
DB_PORT=3306
DB_DATABASE=laravel_task
DB_USERNAME=laravel_user //phpMyadminの特権にusernameがあります。
DB_PASSWORD=hosthost

また、念のためですがdatabase.phpも下記に書き換えます。

database.php
// 54行目を書き換えます
'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock', 
        //'unix_socketを上記のように書き換え
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

最後にターミナルでphp artisan cache:clear php artisan config:cacheを実行。
するとphp artisan migrateを実行すると下記のように解決できます。
“スクリーンショット” 2020-11-28 13.17.21.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】任意のPHPバージョンをphpbrewで取得してみた

はじめに

MacはデフォでPHPがインストールされているけど、開発する際に指定したPHPバージョンを使いたい!

しかし、、WindowsのようにPHP公式サイトでダウンロードしPATHを通すことが難しい。。
Homebrewだとバージョンの[x.x.★]の★部分まで選べない。。

phpbrewならバージョンの指定ができ、複数のバージョンを切り替えることができる為、
phpbrewを使って好きなバージョンを取得してみました。

環境

  • macOS 10.15.7
  • Homebrew 2.5.11
  • phpbrew 1.26.0

事前準備

Homebrewというツールを使用するので事前にインストールしておきます。
インストール方法は以下サイトを参考にしました。

Mac Homebrewインストール手順

phpbrew本体のインストール

公式ドキュメントを参考に以下コマンドを実行していきます。

// ダウンロード
$ curl -L -O https://github.com/phpbrew/phpbrew/releases/latest/download/phpbrew.phar
$ chmod +x phpbrew.phar

// $PATH の通っているディレクトリにファイルを移動
$ sudo mv phpbrew.phar /usr/local/bin/phpbrew

// セットアップ
$ phpbrew init
$ echo 'source ~/.phpbrew/bashrc' >> ~/.bashrc
$ phpbrew lookup-prefix homebrew

以上を実行したら、$ phpbrewで確認します。

$ phpbrew

  ______ _   _ ____________                   
  | ___ \ | | || ___ \ ___ \                  
  | |_/ / |_| || |_/ / |_/ /_ __ _____      __
  |  __/|  _  ||  __/| ___ \ '__/ _ \ \ /\ / /
  | |   | | | || |   | |_/ / | |  __/\ V  V / 
  \_|   \_| |_/\_|   \____/|_|  \___| \_/\_/  

Brew your latest php!

この表示が出ればOKです。

インストール可能なPHPバージョンの確認

$ phpbrew knownを実行すると、インストール可能なバージョンが一覧で表示されます。

$ phpbrew known

7.4: 7.4.12, 7.4.11, 7.4.10, 7.4.9, 7.4.8, 7.4.7, 7.4.6, 7.4.5 ...
7.3: 7.3.24, 7.3.23, 7.3.22, 7.3.21, 7.3.20, 7.3.19, 7.3.18, 7.3.17 ...
7.2: 7.2.34, 7.2.33, 7.2.32, 7.2.31, 7.2.30, 7.2.29, 7.2.28, 7.2.27 ...
7.1: 7.1.33, 7.1.32, 7.1.31, 7.1.30, 7.1.29, 7.1.28, 7.1.27, 7.1.26 ...
7.0: 7.0.33, 7.0.32, 7.0.31, 7.0.30, 7.0.29, 7.0.28, 7.0.27 ...
5.6: 5.6.40, 5.6.39, 5.6.38, 5.6.37, 5.6.36, 5.6.35, 5.6.34, 5.6.33 ...
5.5: 5.5.38, 5.5.37, 5.5.36, 5.5.35, 5.5.34, 5.5.33, 5.5.32, 5.5.31 ...
5.4: 5.4.45, 5.4.44, 5.4.43, 5.4.42, 5.4.41, 5.4.40, 5.4.39, 5.4.38 ...

今回は " 7.4.5 " をインストールしていきます。

指定したバージョンのPHPバージョンをインストール

$ phpbrew installコマンドを実行し、php-7.4.5をインストールします。
(デフォルトを7.4.5にしたいので、+defaultを末尾に付け足します。)

$ phpbrew install 7.4.5 +default

===> phpbrew will now build 7.4.5
You haven't enabled any variants. The default variant will be enabled: 
[bcmath, bz2, calendar, cli, ctype, dom, fileinfo, filter, ipc, json, mbregex, mbstring, mhash, pcntl, pcre, pdo, pear, phar, posix, readline, sockets, tokenizer, xml, curl, openssl, zip]
Please run 'phpbrew variants' for more information.

===> Loading and resolving variants...
Homebrew prefix "/usr/local/Cellar/libxml2/2.9.10" does not exist.
Homebrew prefix "/usr/local/Cellar/bzip2/1.0.8" does not exist.
Homebrew prefix "/usr/local/Cellar/mhash/0.9.9.9" does not exist.
Homebrew prefix "/usr/local/Cellar/curl/7.68.0" does not exist.
Downloading https://www.php.net/distributions/php-7.3.14.tar.bz2 via curl extension
dose not exist.

ソフトウェアが存在しないと言われてしまいました。。
足りないソフトウェアをbrew installコマンドでまとめてインストールします。

$ brew install libxml2 bzip2 mhash curl

よしよし、再度チャレンジです。

改めてインストール

インストール完了するまでに時間がかかるので、休憩がてら猫と戯れてました。

$ phpbrew install 7.4.5 +default

(略)
* WARNING:
  You haven't setup your .bashrc file to load phpbrew shell script yet!
  Please run 'phpbrew init' to see the steps!

To use the newly built PHP, try the line(s) below:

    $ phpbrew use php-7.4.5

Or you can use switch command to switch your default php to php-7.3.14:

    $ phpbrew switch php-7.4.5

Enjoy!

インストール完了です!

再起動

インストールしたらコマンドプロンプトを再起動してsourceコマンドを実行します。

$ source ~/.phpbrew/bashrc

インストール済みのPHPバージョンを確認

listコマンドを実行してインストールされたか改めて確認します。

$ phpbrew list

* php-7.4.5

さらにphp -vでバージョンが7.4.5になっているか確認します。

$ php -v

PHP 7.4.5 (cli) (built: Nov 25 2020 23:12:04) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

php-7.4.5を使えるようになりました!

他バージョンの確認・切り替え

インストールした別のバージョンを確認し切り替えます。

1. インストール済みのバージョンを一覧で確認

$ phpbrew list

  php-7.4.6
* php-7.4.5

"*"は現在使用されているバージョンです。
7.4.6に切り替えてみる。

2. バージョンの切り替え

// 7.4.6を使用する
$ phpbrew use php-7.4.6

// 切り替わったか確認
$phpbrew list

* php-7.4.6
  php-7.4.5

切り替わりました!
最後にphp -vでバージョンが7.4.6になっているか確認

$ php -v

PHP 7.4.6 (cli) (built: Nov 26 2020 00:53:12) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

7.4.6に切り替えることができました!

最後に

ここまで行き着くのに2日かかりました (›´ω`‹ )
これからコマンド慣れしていかねば、、!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP]フォームのデータを受け取る

PHPでフォームのデータを受け取る

PHP
 <?php echo $_POST['name属性に指定した値']; ?>
 <div class="form-item"> 内容</div>

$_POST['name']

フォームで送信した値を受け取るには「$_POST」を使用する。
「$_POST」は連想配列になっていて[ ]の中に、とのname属性に指定した値を入れることで、それぞれの送信した値を受け取ることが出来る

以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む