[{"data":1,"prerenderedAt":2518},["ShallowReactive",2],{"tag-security-1":3},{"count":4,"content":5},3,[6,342,2090],{"id":7,"title":8,"body":9,"category":325,"createdAt":327,"description":328,"extension":329,"index":330,"meta":331,"navigation":332,"path":333,"publish":332,"seo":334,"series":330,"seriesTitle":330,"stem":335,"tag":336,"thumbnail":340,"updatedAt":330,"__hash__":341},"articles\u002Farticles\u002Fsecurity-incident-by-git-deplay.md","ドキュメントルート配下のコンテンツをGitを用いてデプロイする時のセキュリティ上の注意点",{"type":10,"value":11,"toc":310},"minimark",[12,21,24,27,46,51,54,59,69,72,75,78,81,100,106,110,120,135,138,142,145,148,151,154,158,167,177,184,187,207,222,228,232,243,246,255,258,261,267,271,277,283,286,292,295,301,304],[13,14,15,16,20],"p",{},"こんにちはjunです。最近関わっているプロジェクトではGitを用いた本番環境での運用を行っています。本番環境にgitを置くことによって",[17,18,19],"code",{},"git pull","するだけで改修したコードをすぐに反映できます。さらにブランチを分ければABテストや選択的なデプロイなども行えます。そのためシステム開発においてはGitによる本番環境での運用は欠かせません。",[13,22,23],{},"Laravelの様なシステム以外にも最近はwordpressのカスタムテーマやカスタムプラグインをgit管理し、本番環境にpullすることがあります。しかしその際の設定によってはプライベートのソースコードが流失したり、書き換えられたりなどセキュリティインシデントを犯しかねない設定になることがあります。私が公開前に止められたヒヤリハットとしてぜひ共有したいと思います。",[13,25,26],{},"今回解説するGitの管理と運用は以下のとおりです。",[28,29,30,34,37,40,43],"ul",{},[31,32,33],"li",{},"プライベートリポジトリ",[31,35,36],{},"リモートはgithub",[31,38,39],{},"管理しているソースはwordpressのカスタムテーマ",[31,41,42],{},".gitがドキュメントルート配下にある",[31,44,45],{},"サーバはxserver（法人用レンタルサーバ）",[47,48,50],"h2",{"id":49},"今回何が危なかったのか","今回何が危なかったのか？",[13,52,53],{},"まず結論から先に述べますと「パーソナルアクセストークンを用いたgit設定がドキュメントルート配下にあった」ことです。わかる人は結構やべーとわかるかもしれません。取り合えず解説していきます。",[55,56,58],"h3",{"id":57},"パーソナルアクセストークンpatとは","パーソナルアクセストークン（PAT）とは",[13,60,61,62],{},"パーソナルアクセストークンとはgithubにて使用されるパスワードの代わりとなるアクセストークンです。",[63,64,68],"a",{"href":65,"rel":66},"https:\u002F\u002Fdocs.github.com\u002Fja\u002Fauthentication\u002Fkeeping-your-account-and-data-secure\u002Fcreating-a-personal-access-token",[67],"nofollow","参考",[13,70,71],{},"一昔前はプライベートリポジトリにURL（HTTP）でアクセスする場合はドメインの部分にGithubアカウントのIDとパスワードを合わせる方法がとられていました（Basic認証）。ただしその方法はセキュリティ上危ないので、廃止され現在はパーソナルアクセストークン という予測がしづらく、行える操作スコープと使用期限が設定されたトークンを発行してパスワードの代わりに使用する様になりました。",[13,73,74],{},"そしてgitはpull,pushなどを行う際にそのorigin（リモートリポジトリ）のURL（HTTP）かSSHを使用します。SSHは環境によって難しかったり設定が大変なこともあり、PAT付きのURLを用いて接続すると簡単にプライベートリポジトリに接続ができます。基本的にパーソナルアクセストークン はこの様にgithubのアカウントが必要なプライベートリポジトリの読み取り、リポジトリ操作を行う際に使用します。",[13,76,77],{},"今回のテーマファイルのリポジトリはもちろんプライベートなので、トークン付きのURLかSSHで接続する必要があります。そして今回はPATをメインに使用して、gitの操作を行っていました。",[55,79,80],{"id":80},"git設定ファイルとは",[13,82,83,84,87,88,91,92,95,96,99],{},"git設定ファイルとはリモートリポジトリ の接続先、ブランチ構成、ユーザーなどgit管理に必要な設定ファイルのことです。",[17,85,86],{},"git init","してローカルリポジトリ を作成した際に",[17,89,90],{},"ls -la","と打つと、",[17,93,94],{},".git","という隠しディレクトリが表示されます。それが設定ディレクトリであり、中に移動すると",[17,97,98],{},".git\u002Fconfig","という設定ファイルがあります。",[13,101,102,103,105],{},"先ほどのパーソナルアクセストークンを用いたURLなどはその",[17,104,98],{},"に書かれます。実際にlessやcatを使用してみてみると、リモートのURLなどが記載されています。",[55,107,109],{"id":108},"なぜ読み取れた","なぜ読み取れた？",[13,111,112,113,116,117,119],{},"今回gitで運用しようとしていたwordpressのテーマファイルはドキュメントルート配下に設定します。ルートからみて　",[17,114,115],{},"\u002Fwp-content\u002Fthemes\u002Fcustom","というリポジトリ名にもなるテーマディレクトリを作成してそこに",[17,118,86],{},"をして接続先の情報を記載しました。上記のとおりPATを用いています。",[13,121,122,123,126,127,130,131,134],{},"インフラに詳しい人ならわかると思いますが、基本的にドキュメントルート配下のファイルは",[17,124,125],{},".htaccess","や",[17,128,129],{},"Apache","で何かしら設定されていない場合、自由にみることができます。本来アクセスされない様なファイルにも例えば、",[17,132,133],{},"https:\u002F\u002Fexample.comm\u002Fwp-content\u002Fthemes\u002Fcustom\u002F.git\u002Fconfig","とURLでアクセスすると読み取れることがあります。",[13,136,137],{},"xserverの場合はconfigファイルがダウンロードされ、もちろん接続先情報は記載されていました。そのためパーソナルアクセストークン が記載されたgitの設定ファイルが第三者にダウンロード可能な状態で公開しうるとこでした。",[55,139,141],{"id":140},"どうゆうことが起きかねる","どうゆうことが起きかねる？",[13,143,144],{},"仮に公開し、攻撃者がこの存在に気づくと何が起こり得るでしょうか？まず、パーソナルアクセストークン が盗まれリポジトリに対して不正アクセスされます。まだwebサーバを通じて設定ファイルを読み取っただけなので、gitコマンドを打たれて本番環境を破壊されることはないと思いますが、プライベートリポジトリに何かしらの攻撃をされるでしょう。本来プライベートなリポジトリ の情報を取得されてしまうのです。",[13,146,147],{},"またパーソナルアクセストークン には操作範囲を設定できます。今回のはリポジトリに対する読み書き操作でしたが、他にも管理者権限レベルの操作を付与できるスコープもあります。つまり、権限の強いトークンの場合はリポジトリ の内容が盗まれたり改変されるだけでなく、アカウント全体に影響が出かねないものになります。",[47,149,150],{"id":150},"対策",[13,152,153],{},"ドキュメントルート配下にリポジトリを設定する場合は設定ファイルにアクセスできない様にする必要があります。またはPATを記載しないことです。",[55,155,157],{"id":156},"gitディレクトリへのアクセスを404にする",".gitディレクトリへのアクセスを404にする",[13,159,160,161,163,164,166],{},"まず手取り早くできるのは",[17,162,125],{},"にて",[17,165,94],{},"を含むURLがあったら404にしてしまうことです。",[168,169,174],"pre",{"className":170,"code":172,"language":173},[171],"language-text","RedirectMatch 404 \u002F\\.git\n","text",[17,175,172],{"__ignoreMap":176},"",[13,178,179],{},[63,180,183],{"href":181,"rel":182},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F6142437\u002Fmake-git-directory-web-inaccessible",[67],"こちらのStacoverflowが役に立ちました。",[13,185,186],{},"解説によると",[28,188,189,192,201,204],{},[31,190,191],{},"ドキュメントルート配下すべての.gitディレクトリ （設定ファイル）へのアクセスを禁止できる。つまり複数のリポジトリがあっても一気に対応可能。",[31,193,194,126,197,200],{},[17,195,196],{},".gitignore",[17,198,199],{},".gitmodules","にも対応できる。",[31,202,203],{},"これから新しく追加される.gitにも対応できる。",[31,205,206],{},"404にすることでリポジトリ の存在を悟らせない。",[13,208,209,210,212,213,215,216,218,219,221],{},"実際に",[17,211,125],{},"で設定すると、",[17,214,133],{},"へのアクセスは404になりました。上記の設定の場合はURLに",[17,217,94],{},"が含まれた瞬間、404になるのがコツです。さすがにないと思いますがURLに",[17,220,94],{},"を使用するコンテンツがある場合も404になるのでそこは注意が必要です。",[13,223,224,225,227],{},"webサーバによる読み取りとgitコマンドは関係ないので、上記の設定をしたとしても",[17,226,19],{},"など操作は引き続き使えます。",[55,229,231],{"id":230},"sshに切り替える","SSHに切り替える",[13,233,234,235,238,239,242],{},"また可能であればSSHによる接続に切り替えることです。SSHによる接続のURLは",[17,236,237],{},"git@github.com:example:repository.git","となります。前半の",[17,240,241],{},"git@github.com","はsshのgitユーザーでgitub.comに接続するという意味です。そのユーザーで接続する際の秘密鍵やそのパスの設定はwebサーバーからは読み取ることができません。（上記のURLに直に書いてればべつだけど）",[13,244,245],{},"そのためパーソナルアクセストークンよりかはSSHでGitに接続するほうが安全性的にかなりベストです。PATより堅牢な方法です。",[13,247,248,249,254],{},"ちなみに本番環境からリポジトリへSSHで接続する際は",[63,250,253],{"href":251,"rel":252},"https:\u002F\u002Fdocs.github.com\u002Fja\u002Fauthentication\u002Fkeeping-your-account-and-data-secure\u002Freviewing-your-deploy-keys",[67],"デプロイキー","を使用するべきです。デプロイキーは特定のリポジトリのみのアクセスに限定するとともに、読み込み専用にすることができます。",[13,256,257],{},"PATでは読みに加えて書き込み権限と付与すればその他の管理者権限が使用できます。つまり盗まれた際の被害が甚大に対して、デプロイキーは特定のリポジトリ の読み込み権限のみを許可出るので被害が小さく済みます。",[55,259,260],{"id":260},"対策まとめ",[13,262,263,264,266],{},"一番はwebサーバーで",[17,265,94],{},"に対するアクセス権限をなくすことです。これでパーソナルアクセストークンであっても外部から見られる心配はありません。ただし可能な限りまずはより安全なデプロイキーによるSSHで接続できる様にし、かつwebサーバの設定を行うことがベストです。",[47,268,270],{"id":269},"うちは大丈夫チェック方法linux","うちは大丈夫？チェック方法(linux)",[13,272,273,274,276],{},"まずはドキュメントルート配下に",[17,275,94],{},"がいるかをチェックしましょう。Linuxの場合はドキュメントルート配下に以下のコマンドで探せます。",[168,278,281],{"className":279,"code":280,"language":173},[171],"find \u002FDOCUMENT\u002FROOT\u002F -name .git -type d\n",[17,282,280],{"__ignoreMap":176},[13,284,285],{},"もしあった場合",[168,287,290],{"className":288,"code":289,"language":173},[171],"\u002FDOCUMENT\u002FROOT\u002Fwp-content\u002Fthemes\u002Fexample\u002F.git\n",[17,291,289],{"__ignoreMap":176},[13,293,294],{},"このように結果が出てきますので、URLに載せて",[168,296,299],{"className":297,"code":298,"language":173},[171],"https:\u002F\u002Fexample.com\u002Fwp-content\u002Fthemes\u002Fexample\u002F.git\nhttps:\u002F\u002Fexample.com\u002Fwp-content\u002Fthemes\u002Fexample\u002F.git\u002Fconfig\n",[17,300,298],{"__ignoreMap":176},[13,302,303],{},"の２種類にアクセスしてディレクトリ 、configが閲覧されるかをチェックしましょう。サーバによっては対策済みの場合があります。",[13,305,306,307,309],{},"見れてしまったらまず",[17,308,125],{},"で閲覧できなくしましょう。中を見てパスワードやパーソナルアクセストークンがあったら今すぐ対処が必要です！！",{"title":176,"searchDepth":4,"depth":4,"links":311},[312,319,324],{"id":49,"depth":313,"text":50,"children":314},2,[315,316,317,318],{"id":57,"depth":4,"text":58},{"id":80,"depth":4,"text":80},{"id":108,"depth":4,"text":109},{"id":140,"depth":4,"text":141},{"id":150,"depth":313,"text":150,"children":320},[321,322,323],{"id":156,"depth":4,"text":157},{"id":230,"depth":4,"text":231},{"id":260,"depth":4,"text":260},{"id":269,"depth":313,"text":270},[326],"devstack","2022-04-21","wordpressテーマやドキュメントルート配下にGitを置く際の注意点","md",null,{},true,"\u002Farticles\u002Fsecurity-incident-by-git-deplay",{"title":8,"description":328},"articles\u002Fsecurity-incident-by-git-deplay",[337,338,339],"security","infrastructure","git","_common\u002Fsecurity.jpg","zHB7HHf-61vEYna3ziFZoIxx8rS0KxIKcw1QiPSv20k",{"id":343,"title":344,"body":345,"category":2078,"createdAt":2079,"description":2080,"extension":329,"index":330,"meta":2081,"navigation":332,"path":2082,"publish":332,"seo":2083,"series":330,"seriesTitle":330,"stem":2084,"tag":2085,"thumbnail":2088,"updatedAt":330,"__hash__":2089},"articles\u002Farticles\u002Fimplement-recaptcha.md","reCAPTCHAのフロントエンド実装とバックエンド実装（PHP・Laravel）をスクラッチで行う方法",{"type":10,"value":346,"toc":2064},[347,350,353,356,359,363,366,369,372,375,379,388,393,400,403,406,417,420,423,630,633,644,653,657,660,903,917,920,923,1425,1436,1458,1461,1464,1467,1470,1481,1484,1685,1696,1721,1739,1742,1781,1792,1795,1798,1843,1850,1853,1861,1886,1894,1903,1911,1917,1925,1928,2048,2051,2054,2057,2060],[13,348,349],{},"こんにちはjunです。皆さんはフォームを作成する時にBot・スパム対策を行っていますか？フォームというのは少し知識があれば、簡単にbot的に送信することができます。",[13,351,352],{},"curlでPOSTすることもあれば、javascriptを実行して機械的にフォームを送信することがきるので、スパム（嫌がらせ）やサーバーへの過剰負荷の原因となります。",[13,354,355],{},"この様な機械的な操作を防ぐために、よく「ロボットではありません」「画像に表示されている文字を入力してください」みたいなbotでは簡単に処理できないものを用意します。",[13,357,358],{},"しかしこの様な機能は自分で実装するのは大変です。そんな時に便利なのがreCAPTCHAです。",[47,360,362],{"id":361},"recaptchaとは","reCAPTCHAとは？",[13,364,365],{},"reCAPTCHAはGoogleが無料で提供しているBot対策ツールです。現在v3までリリースされており、v2は画像を選択させたりチェックを入れるといったユーザーのアクションでbotかどうかを検証します。v3はその様なアクションを必要とせず、必要なスクリプトを入れるだけで検証ができます。これからの実装の場合はv3を入れることをお勧めします。",[47,367,368],{"id":368},"実装内容",[13,370,371],{},"今回の解説ではv3でのフロントエンドの実装とバックエンドの実装を解説していきます。そして利用するreCAPTCHAはエンタープライズではなく、無料のものを利用します。バックエンドにはLaravel6(php)を用いて説明します。バックエンドは基本的にやることはどの言語・フレームワークでも特に差異はありません。reCAPTCHAを使う時にライブラリを使用することもありますが、いうてそれほど難しくないので今回はライブラリを使用しないスクラッチで実装します。",[13,373,374],{},"それでは解説を始めます。",[47,376,378],{"id":377},"recaptchaのキーを手に入れる","reCAPTCHAのキーを手に入れる",[13,380,381,382,387],{},"reCAPTCHAはGooglenのAPIを使用することでbotか検証を行います。reCAPTCHAを利用するためにGoogleアカウントとreCAPTCHAのキーを登録します。",[63,383,386],{"href":384,"rel":385},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fadmin\u002Fcreate",[67],"reCAPTCHAの登録ページ","にて保護対象のドメインを設定します。",[389,390],"image-render",{":src":391,":width":392},"'_mix\u002Fsch-2021-05-21 23.16.06.png'","'100%'",[13,394,395,396,399],{},"ラベルは管理用の名前、タイプはv3を使用します。保護対象のドメインを登録します。ドメインは複数設定できます。この時、本番のドメインと",[17,397,398],{},"localhost","を登録しておくと開発・本番で利用できます。",[13,401,402],{},"設定を終わって「保存」しますと、キーが表示されますので保存しておきます。",[389,404],{":src":405,":width":392},"'_mix\u002Frecaptcha-key.jpeg'",[13,407,408,412,413,416],{},[409,410,411],"em",{},"このサイトキーは、ユーザー表示するHTMLコードで利用します。"," という方のキーはタグで利用し、正直みられても問題ありません。フロント側のキーはドメインと合わせて保護対処のサイトであるかのチェックのためにあるだけだからです。逆にバックの方である ",[409,414,415],{},"このサイトキーは、サイトとreCAPTCHA間の通信で利用します。"," は漏れてはいけません。",[47,418,419],{"id":419},"フロントエンドの実装",[13,421,422],{},"それではフロントエンドを実装していきます。かなり簡略化して書いています。以下の様なフォームがあったとしましょう。",[168,424,428],{"className":425,"code":426,"language":427,"meta":176,"style":176},"language-html shiki shiki-themes material-theme-ocean","\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\">\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html",[17,429,430,450,459,469,476,486,492,502,528,570,601,611,620],{"__ignoreMap":176},[431,432,435,439,443,447],"span",{"class":433,"line":434},"line",1,[431,436,438],{"class":437},"sAklC","\u003C!",[431,440,442],{"class":441},"s-wAU","DOCTYPE",[431,444,446],{"class":445},"sJ14y"," html",[431,448,449],{"class":437},">\n",[431,451,452,455,457],{"class":433,"line":313},[431,453,454],{"class":437},"\u003C",[431,456,427],{"class":441},[431,458,449],{"class":437},[431,460,461,464,467],{"class":433,"line":4},[431,462,463],{"class":437},"    \u003C",[431,465,466],{"class":441},"head",[431,468,449],{"class":437},[431,470,472],{"class":433,"line":471},4,[431,473,475],{"class":474},"sC9rS","      \u003C!---省略-->\n",[431,477,479,482,484],{"class":433,"line":478},5,[431,480,481],{"class":437},"    \u003C\u002F",[431,483,466],{"class":441},[431,485,449],{"class":437},[431,487,489],{"class":433,"line":488},6,[431,490,491],{"emptyLinePlaceholder":332},"\n",[431,493,495,497,500],{"class":433,"line":494},7,[431,496,463],{"class":437},[431,498,499],{"class":441},"body",[431,501,449],{"class":437},[431,503,505,508,511,514,517,520,524,526],{"class":433,"line":504},8,[431,506,507],{"class":437},"        \u003C",[431,509,510],{"class":441},"form",[431,512,513],{"class":445}," method",[431,515,516],{"class":437},"=",[431,518,519],{"class":437},"\"",[431,521,523],{"class":522},"sfyAc","post",[431,525,519],{"class":437},[431,527,449],{"class":437},[431,529,531,534,537,540,542,544,546,548,551,553,555,558,560,563,565,568],{"class":433,"line":530},9,[431,532,533],{"class":437},"            \u003C",[431,535,536],{"class":441},"input",[431,538,539],{"class":445}," type",[431,541,516],{"class":437},[431,543,519],{"class":437},[431,545,173],{"class":522},[431,547,519],{"class":437},[431,549,550],{"class":445}," name",[431,552,516],{"class":437},[431,554,519],{"class":437},[431,556,557],{"class":522},"test",[431,559,519],{"class":437},[431,561,562],{"class":445}," value",[431,564,516],{"class":437},[431,566,567],{"class":437},"\"\"",[431,569,449],{"class":437},[431,571,573,575,577,579,581,583,586,588,590,592,594,597,599],{"class":433,"line":572},10,[431,574,533],{"class":437},[431,576,536],{"class":441},[431,578,539],{"class":445},[431,580,516],{"class":437},[431,582,519],{"class":437},[431,584,585],{"class":522},"submit",[431,587,519],{"class":437},[431,589,562],{"class":445},[431,591,516],{"class":437},[431,593,519],{"class":437},[431,595,596],{"class":522},"送信",[431,598,519],{"class":437},[431,600,449],{"class":437},[431,602,604,607,609],{"class":433,"line":603},11,[431,605,606],{"class":437},"        \u003C\u002F",[431,608,510],{"class":441},[431,610,449],{"class":437},[431,612,614,616,618],{"class":433,"line":613},12,[431,615,481],{"class":437},[431,617,499],{"class":441},[431,619,449],{"class":437},[431,621,623,626,628],{"class":433,"line":622},13,[431,624,625],{"class":437},"\u003C\u002F",[431,627,427],{"class":441},[431,629,449],{"class":437},[13,631,632],{},"reCAPTCHAのフロントエンド 実装では",[28,634,635,638,641],{},[31,636,637],{},"reCAPTCHAのソースを読み込む",[31,639,640],{},"送信ボタンを押したらreCAPTCHAと通信してトークンを手に入れるスクリプトを書く",[31,642,643],{},"reCAPTCHAのトークンをフォーム内容と一緒にバックエンドに送信する。",[13,645,646,647,652],{},"以上の実装を必要とします。",[63,648,651],{"href":649,"rel":650},"https:\u002F\u002Fdevelopers.google.com\u002Frecaptcha\u002Fdocs\u002Fv3#programmatically_invoke_the_challenge",[67],"本家のドキュメント","を参考にして進めていきましょう。",[55,654,656],{"id":655},"recaptchaのスクリプト読み込みとhtml調整","reCAPTCHAのスクリプト読み込みとHTML調整",[13,658,659],{},"まずはreCAPTCHAを有効にするためのスクリプトを読み込みます。そして一部フォームを編集します。",[168,661,663],{"className":425,"code":662,"language":427,"meta":176,"style":176},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n        \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\u003C!---追加-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[17,664,665,675,683,691,695,725,733,737,745,777,811,849,877,885,894],{"__ignoreMap":176},[431,666,667,669,671,673],{"class":433,"line":434},[431,668,438],{"class":437},[431,670,442],{"class":441},[431,672,446],{"class":445},[431,674,449],{"class":437},[431,676,677,679,681],{"class":433,"line":313},[431,678,454],{"class":437},[431,680,427],{"class":441},[431,682,449],{"class":437},[431,684,685,687,689],{"class":433,"line":4},[431,686,463],{"class":437},[431,688,466],{"class":441},[431,690,449],{"class":437},[431,692,693],{"class":433,"line":471},[431,694,475],{"class":474},[431,696,697,699,702,705,707,709,712,714,717,719,722],{"class":433,"line":478},[431,698,507],{"class":437},[431,700,701],{"class":441},"script",[431,703,704],{"class":445}," src",[431,706,516],{"class":437},[431,708,519],{"class":437},[431,710,711],{"class":522},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY",[431,713,519],{"class":437},[431,715,716],{"class":437},">\u003C\u002F",[431,718,701],{"class":441},[431,720,721],{"class":437},">",[431,723,724],{"class":474},"\u003C!---追加-->\n",[431,726,727,729,731],{"class":433,"line":488},[431,728,481],{"class":437},[431,730,466],{"class":441},[431,732,449],{"class":437},[431,734,735],{"class":433,"line":494},[431,736,491],{"emptyLinePlaceholder":332},[431,738,739,741,743],{"class":433,"line":504},[431,740,463],{"class":437},[431,742,499],{"class":441},[431,744,449],{"class":437},[431,746,747,749,751,753,755,757,759,761,764,766,768,771,773,775],{"class":433,"line":530},[431,748,507],{"class":437},[431,750,510],{"class":441},[431,752,513],{"class":445},[431,754,516],{"class":437},[431,756,519],{"class":437},[431,758,523],{"class":522},[431,760,519],{"class":437},[431,762,763],{"class":445}," id",[431,765,516],{"class":437},[431,767,519],{"class":437},[431,769,770],{"class":522},"test-form",[431,772,519],{"class":437},[431,774,721],{"class":437},[431,776,724],{"class":474},[431,778,779,781,783,785,787,789,791,793,795,797,799,801,803,805,807,809],{"class":433,"line":572},[431,780,533],{"class":437},[431,782,536],{"class":441},[431,784,539],{"class":445},[431,786,516],{"class":437},[431,788,519],{"class":437},[431,790,173],{"class":522},[431,792,519],{"class":437},[431,794,550],{"class":445},[431,796,516],{"class":437},[431,798,519],{"class":437},[431,800,557],{"class":522},[431,802,519],{"class":437},[431,804,562],{"class":445},[431,806,516],{"class":437},[431,808,567],{"class":437},[431,810,449],{"class":437},[431,812,813,815,817,819,821,823,826,828,830,832,834,837,839,841,843,845,847],{"class":433,"line":603},[431,814,533],{"class":437},[431,816,536],{"class":441},[431,818,539],{"class":445},[431,820,516],{"class":437},[431,822,519],{"class":437},[431,824,825],{"class":522},"hidden",[431,827,519],{"class":437},[431,829,550],{"class":445},[431,831,516],{"class":437},[431,833,519],{"class":437},[431,835,836],{"class":522},"recaptcha",[431,838,519],{"class":437},[431,840,562],{"class":445},[431,842,516],{"class":437},[431,844,567],{"class":437},[431,846,721],{"class":437},[431,848,724],{"class":474},[431,850,851,853,855,857,859,861,863,865,867,869,871,873,875],{"class":433,"line":613},[431,852,533],{"class":437},[431,854,536],{"class":441},[431,856,539],{"class":445},[431,858,516],{"class":437},[431,860,519],{"class":437},[431,862,585],{"class":522},[431,864,519],{"class":437},[431,866,562],{"class":445},[431,868,516],{"class":437},[431,870,519],{"class":437},[431,872,596],{"class":522},[431,874,519],{"class":437},[431,876,449],{"class":437},[431,878,879,881,883],{"class":433,"line":622},[431,880,606],{"class":437},[431,882,510],{"class":441},[431,884,449],{"class":437},[431,886,888,890,892],{"class":433,"line":887},14,[431,889,481],{"class":437},[431,891,499],{"class":441},[431,893,449],{"class":437},[431,895,897,899,901],{"class":433,"line":896},15,[431,898,625],{"class":437},[431,900,427],{"class":441},[431,902,449],{"class":437},[13,904,905,908,909,912,913,916],{},[17,906,907],{},"\u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>","の",[17,910,911],{},"YOUR_FRONT_KEY","に先ほど取得したフロント用のキーを入れます。\n",[17,914,915],{},"\u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">","にはreCAPTCHAのトークンを挿入してバックエンドに送ります。HTMLフォームであればこの様にしますが、axiosなどの場合はトークンの値をjsを用いて送信するので、このHTMLは要りません。",[55,918,919],{"id":919},"トークンを取得するスクリプトを記述",[13,921,922],{},"「送信ボタン」を押した時にreCAPTCHAにAPIを飛ばしてbotかどうかの判定用トークンを取得します。以下の様なスクリプトを記述します。",[168,924,926],{"className":425,"code":925,"language":427,"meta":176,"style":176},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n      \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\n    \u003Cscript>\n        function checkCaptcha(e) {\n            e.preventDefault();\n            grecaptcha.ready(function() {\n                grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n                    document.getElementById(\"recaptcha\").value=token;\n                    document.getElementById(\"test-form\").submit();\n                });\n            });\n        }\n        document.getElementById(\"test-form\").addEventListener('submit', checkCaptcha);\n        \u003C\u002Fscript>\n\u003C\u002Fhtml>\n",[17,927,928,938,946,954,958,981,989,993,1001,1031,1065,1101,1129,1137,1145,1149,1158,1181,1200,1220,1282,1314,1341,1351,1361,1367,1407,1416],{"__ignoreMap":176},[431,929,930,932,934,936],{"class":433,"line":434},[431,931,438],{"class":437},[431,933,442],{"class":441},[431,935,446],{"class":445},[431,937,449],{"class":437},[431,939,940,942,944],{"class":433,"line":313},[431,941,454],{"class":437},[431,943,427],{"class":441},[431,945,449],{"class":437},[431,947,948,950,952],{"class":433,"line":4},[431,949,463],{"class":437},[431,951,466],{"class":441},[431,953,449],{"class":437},[431,955,956],{"class":433,"line":471},[431,957,475],{"class":474},[431,959,960,963,965,967,969,971,973,975,977,979],{"class":433,"line":478},[431,961,962],{"class":437},"      \u003C",[431,964,701],{"class":441},[431,966,704],{"class":445},[431,968,516],{"class":437},[431,970,519],{"class":437},[431,972,711],{"class":522},[431,974,519],{"class":437},[431,976,716],{"class":437},[431,978,701],{"class":441},[431,980,449],{"class":437},[431,982,983,985,987],{"class":433,"line":488},[431,984,481],{"class":437},[431,986,466],{"class":441},[431,988,449],{"class":437},[431,990,991],{"class":433,"line":494},[431,992,491],{"emptyLinePlaceholder":332},[431,994,995,997,999],{"class":433,"line":504},[431,996,463],{"class":437},[431,998,499],{"class":441},[431,1000,449],{"class":437},[431,1002,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027,1029],{"class":433,"line":530},[431,1004,507],{"class":437},[431,1006,510],{"class":441},[431,1008,513],{"class":445},[431,1010,516],{"class":437},[431,1012,519],{"class":437},[431,1014,523],{"class":522},[431,1016,519],{"class":437},[431,1018,763],{"class":445},[431,1020,516],{"class":437},[431,1022,519],{"class":437},[431,1024,770],{"class":522},[431,1026,519],{"class":437},[431,1028,721],{"class":437},[431,1030,724],{"class":474},[431,1032,1033,1035,1037,1039,1041,1043,1045,1047,1049,1051,1053,1055,1057,1059,1061,1063],{"class":433,"line":572},[431,1034,533],{"class":437},[431,1036,536],{"class":441},[431,1038,539],{"class":445},[431,1040,516],{"class":437},[431,1042,519],{"class":437},[431,1044,173],{"class":522},[431,1046,519],{"class":437},[431,1048,550],{"class":445},[431,1050,516],{"class":437},[431,1052,519],{"class":437},[431,1054,557],{"class":522},[431,1056,519],{"class":437},[431,1058,562],{"class":445},[431,1060,516],{"class":437},[431,1062,567],{"class":437},[431,1064,449],{"class":437},[431,1066,1067,1069,1071,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099],{"class":433,"line":603},[431,1068,533],{"class":437},[431,1070,536],{"class":441},[431,1072,539],{"class":445},[431,1074,516],{"class":437},[431,1076,519],{"class":437},[431,1078,825],{"class":522},[431,1080,519],{"class":437},[431,1082,550],{"class":445},[431,1084,516],{"class":437},[431,1086,519],{"class":437},[431,1088,836],{"class":522},[431,1090,519],{"class":437},[431,1092,562],{"class":445},[431,1094,516],{"class":437},[431,1096,567],{"class":437},[431,1098,721],{"class":437},[431,1100,724],{"class":474},[431,1102,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1123,1125,1127],{"class":433,"line":613},[431,1104,533],{"class":437},[431,1106,536],{"class":441},[431,1108,539],{"class":445},[431,1110,516],{"class":437},[431,1112,519],{"class":437},[431,1114,585],{"class":522},[431,1116,519],{"class":437},[431,1118,562],{"class":445},[431,1120,516],{"class":437},[431,1122,519],{"class":437},[431,1124,596],{"class":522},[431,1126,519],{"class":437},[431,1128,449],{"class":437},[431,1130,1131,1133,1135],{"class":433,"line":622},[431,1132,606],{"class":437},[431,1134,510],{"class":441},[431,1136,449],{"class":437},[431,1138,1139,1141,1143],{"class":433,"line":887},[431,1140,481],{"class":437},[431,1142,499],{"class":441},[431,1144,449],{"class":437},[431,1146,1147],{"class":433,"line":896},[431,1148,491],{"emptyLinePlaceholder":332},[431,1150,1152,1154,1156],{"class":433,"line":1151},16,[431,1153,463],{"class":437},[431,1155,701],{"class":441},[431,1157,449],{"class":437},[431,1159,1161,1164,1168,1171,1175,1178],{"class":433,"line":1160},17,[431,1162,1163],{"class":445},"        function",[431,1165,1167],{"class":1166},"sdLwU"," checkCaptcha",[431,1169,1170],{"class":437},"(",[431,1172,1174],{"class":1173},"s7ZW3","e",[431,1176,1177],{"class":437},")",[431,1179,1180],{"class":437}," {\n",[431,1182,1184,1188,1191,1194,1197],{"class":433,"line":1183},18,[431,1185,1187],{"class":1186},"s0W1g","            e",[431,1189,1190],{"class":437},".",[431,1192,1193],{"class":1166},"preventDefault",[431,1195,1196],{"class":441},"()",[431,1198,1199],{"class":437},";\n",[431,1201,1203,1206,1208,1211,1213,1216,1218],{"class":433,"line":1202},19,[431,1204,1205],{"class":1186},"            grecaptcha",[431,1207,1190],{"class":437},[431,1209,1210],{"class":1166},"ready",[431,1212,1170],{"class":441},[431,1214,1215],{"class":445},"function",[431,1217,1196],{"class":437},[431,1219,1180],{"class":437},[431,1221,1223,1226,1228,1231,1233,1236,1238,1240,1243,1246,1249,1252,1255,1257,1259,1262,1264,1266,1269,1271,1273,1275,1278,1280],{"class":433,"line":1222},20,[431,1224,1225],{"class":1186},"                grecaptcha",[431,1227,1190],{"class":437},[431,1229,1230],{"class":1166},"execute",[431,1232,1170],{"class":441},[431,1234,1235],{"class":437},"'",[431,1237,911],{"class":522},[431,1239,1235],{"class":437},[431,1241,1242],{"class":437},",",[431,1244,1245],{"class":437}," {",[431,1247,1248],{"class":441},"action",[431,1250,1251],{"class":437},":",[431,1253,1254],{"class":437}," '",[431,1256,585],{"class":522},[431,1258,1235],{"class":437},[431,1260,1261],{"class":437},"}",[431,1263,1177],{"class":441},[431,1265,1190],{"class":437},[431,1267,1268],{"class":1166},"then",[431,1270,1170],{"class":441},[431,1272,1215],{"class":445},[431,1274,1170],{"class":437},[431,1276,1277],{"class":1173},"token",[431,1279,1177],{"class":437},[431,1281,1180],{"class":437},[431,1283,1285,1288,1290,1293,1295,1297,1299,1301,1303,1305,1308,1310,1312],{"class":433,"line":1284},21,[431,1286,1287],{"class":1186},"                    document",[431,1289,1190],{"class":437},[431,1291,1292],{"class":1166},"getElementById",[431,1294,1170],{"class":441},[431,1296,519],{"class":437},[431,1298,836],{"class":522},[431,1300,519],{"class":437},[431,1302,1177],{"class":441},[431,1304,1190],{"class":437},[431,1306,1307],{"class":1186},"value",[431,1309,516],{"class":437},[431,1311,1277],{"class":1186},[431,1313,1199],{"class":437},[431,1315,1317,1319,1321,1323,1325,1327,1329,1331,1333,1335,1337,1339],{"class":433,"line":1316},22,[431,1318,1287],{"class":1186},[431,1320,1190],{"class":437},[431,1322,1292],{"class":1166},[431,1324,1170],{"class":441},[431,1326,519],{"class":437},[431,1328,770],{"class":522},[431,1330,519],{"class":437},[431,1332,1177],{"class":441},[431,1334,1190],{"class":437},[431,1336,585],{"class":1166},[431,1338,1196],{"class":441},[431,1340,1199],{"class":437},[431,1342,1344,1347,1349],{"class":433,"line":1343},23,[431,1345,1346],{"class":437},"                }",[431,1348,1177],{"class":441},[431,1350,1199],{"class":437},[431,1352,1354,1357,1359],{"class":433,"line":1353},24,[431,1355,1356],{"class":437},"            }",[431,1358,1177],{"class":441},[431,1360,1199],{"class":437},[431,1362,1364],{"class":433,"line":1363},25,[431,1365,1366],{"class":437},"        }\n",[431,1368,1370,1373,1375,1377,1379,1381,1383,1385,1387,1389,1392,1394,1396,1398,1400,1402,1405],{"class":433,"line":1369},26,[431,1371,1372],{"class":1186},"        document",[431,1374,1190],{"class":437},[431,1376,1292],{"class":1166},[431,1378,1170],{"class":1186},[431,1380,519],{"class":437},[431,1382,770],{"class":522},[431,1384,519],{"class":437},[431,1386,1177],{"class":1186},[431,1388,1190],{"class":437},[431,1390,1391],{"class":1166},"addEventListener",[431,1393,1170],{"class":1186},[431,1395,1235],{"class":437},[431,1397,585],{"class":522},[431,1399,1235],{"class":437},[431,1401,1242],{"class":437},[431,1403,1404],{"class":1186}," checkCaptcha)",[431,1406,1199],{"class":437},[431,1408,1410,1412,1414],{"class":433,"line":1409},27,[431,1411,606],{"class":437},[431,1413,701],{"class":441},[431,1415,449],{"class":437},[431,1417,1419,1421,1423],{"class":433,"line":1418},28,[431,1420,625],{"class":437},[431,1422,427],{"class":441},[431,1424,449],{"class":437},[13,1426,1427,1428,1431,1432,1435],{},"フォームの送信ボタンが押された時（submitイベント発火時）に",[17,1429,1430],{},"checkCaptcha","の関数が実行される様に設定します。",[17,1433,1434],{},"e.preventDefault();","を使用してそのままフォームが送信されない様にします。",[13,1437,1438,1439,1442,1443,1446,1447,1450,1451,1453,1454,1457],{},"reCAPTCHAのスクリプトによって",[17,1440,1441],{},"grecaptcha","というオブジェクトが使用できる様になり、その中の",[17,1444,1445],{},"grecaptcha.execute()","にてAPIを実行します。第一引数にフロントのキー、第二引数にアクションを入力します。Promiseなので",[17,1448,1449],{},"then(token)","内のコールバックでトークンを",[17,1452,915],{},"に突っ込みます。そしてフォームを",[17,1455,1456],{},"submit()","にて送信します。",[13,1459,1460],{},"これでフロントの実装は完了です。フロントでの動きをみてreCAPTCHAはbotかどうかを判断し、この送信を一意なトークンで保存しているのです。トークンはバックエンドでの検証で利用します。",[47,1462,1463],{"id":1463},"バックエンドの実装",[13,1465,1466],{},"それではバックエンドの実装をすすめます。Laravelのコントローラーでの記述を想定しています。バリデーションなどは各自設定してください。",[13,1468,1469],{},"バックエンドで行うことは",[28,1471,1472,1475,1478],{},[31,1473,1474],{},"フロントからきたトークンをreCAPTCHAのAPIに送信",[31,1476,1477],{},"reCAPTCHAの結果を取得する",[31,1479,1480],{},"結果（スコア）を用いてbotかの判断をする",[13,1482,1483],{},"以上となります。コードは以下の通りです。",[168,1485,1489],{"className":1486,"code":1487,"language":1488,"meta":176,"style":176},"language-php shiki shiki-themes material-theme-ocean","class Controller extends BaseController\n{\n    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n\n    public function checkRecaptcha(Request $request){\n        try {\n            $client = new \\GuzzleHttp\\Client([\n                'headers' => [\n                    'Content-Type' => 'application\u002Fjson',\n                ],\n            ]);\n    \n            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n            [\n                'form_params' =>[\n                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n                    'response'=>$request->recaptcha\n                ]\n            ]);\n    \n            $res = Promise\\Utils::settle($promise)->wait();\n            $isFulfilled = isset($res[0]['value']);\n            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n    \n            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n            \n            if(isset($result['error-codes'])){\n                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n            }\n\n            return $result['score'] > 0.5 && $result['success'];\n        }catch (\\Exception $e) {\n            report($e);\n            return false;\n        }\n    }\n}\n","php",[17,1490,1491,1496,1501,1506,1510,1515,1520,1525,1530,1535,1540,1545,1550,1555,1560,1565,1570,1575,1580,1584,1588,1593,1598,1603,1607,1612,1617,1622,1627,1633,1639,1644,1650,1656,1662,1668,1673,1679],{"__ignoreMap":176},[431,1492,1493],{"class":433,"line":434},[431,1494,1495],{},"class Controller extends BaseController\n",[431,1497,1498],{"class":433,"line":313},[431,1499,1500],{},"{\n",[431,1502,1503],{"class":433,"line":4},[431,1504,1505],{},"    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n",[431,1507,1508],{"class":433,"line":471},[431,1509,491],{"emptyLinePlaceholder":332},[431,1511,1512],{"class":433,"line":478},[431,1513,1514],{},"    public function checkRecaptcha(Request $request){\n",[431,1516,1517],{"class":433,"line":488},[431,1518,1519],{},"        try {\n",[431,1521,1522],{"class":433,"line":494},[431,1523,1524],{},"            $client = new \\GuzzleHttp\\Client([\n",[431,1526,1527],{"class":433,"line":504},[431,1528,1529],{},"                'headers' => [\n",[431,1531,1532],{"class":433,"line":530},[431,1533,1534],{},"                    'Content-Type' => 'application\u002Fjson',\n",[431,1536,1537],{"class":433,"line":572},[431,1538,1539],{},"                ],\n",[431,1541,1542],{"class":433,"line":603},[431,1543,1544],{},"            ]);\n",[431,1546,1547],{"class":433,"line":613},[431,1548,1549],{},"    \n",[431,1551,1552],{"class":433,"line":622},[431,1553,1554],{},"            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n",[431,1556,1557],{"class":433,"line":887},[431,1558,1559],{},"            [\n",[431,1561,1562],{"class":433,"line":896},[431,1563,1564],{},"                'form_params' =>[\n",[431,1566,1567],{"class":433,"line":1151},[431,1568,1569],{},"                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[431,1571,1572],{"class":433,"line":1160},[431,1573,1574],{},"                    'response'=>$request->recaptcha\n",[431,1576,1577],{"class":433,"line":1183},[431,1578,1579],{},"                ]\n",[431,1581,1582],{"class":433,"line":1202},[431,1583,1544],{},[431,1585,1586],{"class":433,"line":1222},[431,1587,1549],{},[431,1589,1590],{"class":433,"line":1284},[431,1591,1592],{},"            $res = Promise\\Utils::settle($promise)->wait();\n",[431,1594,1595],{"class":433,"line":1316},[431,1596,1597],{},"            $isFulfilled = isset($res[0]['value']);\n",[431,1599,1600],{"class":433,"line":1343},[431,1601,1602],{},"            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n",[431,1604,1605],{"class":433,"line":1353},[431,1606,1549],{},[431,1608,1609],{"class":433,"line":1363},[431,1610,1611],{},"            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n",[431,1613,1614],{"class":433,"line":1369},[431,1615,1616],{},"            \n",[431,1618,1619],{"class":433,"line":1409},[431,1620,1621],{},"            if(isset($result['error-codes'])){\n",[431,1623,1624],{"class":433,"line":1418},[431,1625,1626],{},"                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n",[431,1628,1630],{"class":433,"line":1629},29,[431,1631,1632],{},"                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n",[431,1634,1636],{"class":433,"line":1635},30,[431,1637,1638],{},"            }\n",[431,1640,1642],{"class":433,"line":1641},31,[431,1643,491],{"emptyLinePlaceholder":332},[431,1645,1647],{"class":433,"line":1646},32,[431,1648,1649],{},"            return $result['score'] > 0.5 && $result['success'];\n",[431,1651,1653],{"class":433,"line":1652},33,[431,1654,1655],{},"        }catch (\\Exception $e) {\n",[431,1657,1659],{"class":433,"line":1658},34,[431,1660,1661],{},"            report($e);\n",[431,1663,1665],{"class":433,"line":1664},35,[431,1666,1667],{},"            return false;\n",[431,1669,1671],{"class":433,"line":1670},36,[431,1672,1366],{},[431,1674,1676],{"class":433,"line":1675},37,[431,1677,1678],{},"    }\n",[431,1680,1682],{"class":433,"line":1681},38,[431,1683,1684],{},"}\n",[13,1686,1687,1688,1691,1692,1695],{},"recaptchaとのAPI通信には",[17,1689,1690],{},"Guzzle","を使用していますが、とにかくAPI通信ができれば大丈夫です。APIは",[17,1693,1694],{},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify","にPOSTを送信します。POSTには以下の値が必要です、",[168,1697,1699],{"className":1486,"code":1698,"language":1488,"meta":176,"style":176},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n    'response'=>$request->recaptcha\n]\n",[17,1700,1701,1706,1711,1716],{"__ignoreMap":176},[431,1702,1703],{"class":433,"line":434},[431,1704,1705],{},"'form_params' =>[\n",[431,1707,1708],{"class":433,"line":313},[431,1709,1710],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[431,1712,1713],{"class":433,"line":4},[431,1714,1715],{},"    'response'=>$request->recaptcha\n",[431,1717,1718],{"class":433,"line":471},[431,1719,1720],{},"]\n",[13,1722,1723,1726,1727,1730,1731,1733,1734,1738],{},[17,1724,1725],{},"env('RECAPTCHA_SERVER_KEY')","はバックエンドで使用するrecaptchaキーです。",[17,1728,1729],{},"$request->recaptcha","は",[17,1732,915],{},"で挿入されたフロントで取得したrecaptchaのトークンです。このトークンとキーを合わせて、 ",[1735,1736,1737],"strong",{},"保護対象のサーバーであり、検証を行うフォーム送信","　を判別しています。",[13,1740,1741],{},"通信が成功すると以下の様なレスポンスが戻ります。",[168,1743,1745],{"className":1486,"code":1744,"language":1488,"meta":176,"style":176},"[\n  \"success\"=> true, \n  \"score\"=> 0.8,\n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n]\n",[17,1746,1747,1752,1757,1762,1767,1772,1777],{"__ignoreMap":176},[431,1748,1749],{"class":433,"line":434},[431,1750,1751],{},"[\n",[431,1753,1754],{"class":433,"line":313},[431,1755,1756],{},"  \"success\"=> true, \n",[431,1758,1759],{"class":433,"line":4},[431,1760,1761],{},"  \"score\"=> 0.8,\n",[431,1763,1764],{"class":433,"line":471},[431,1765,1766],{},"  \"action\"=> string,\n",[431,1768,1769],{"class":433,"line":478},[431,1770,1771],{},"  \"challenge_ts\"=> timestamp,\n",[431,1773,1774],{"class":433,"line":488},[431,1775,1776],{},"  \"hostname\"=> string,\n",[431,1778,1779],{"class":433,"line":494},[431,1780,1720],{},[13,1782,1783,1784,1787,1788,1791],{},"一番重要なのは",[17,1785,1786],{},"\"score\"=> 0.8","です。このスコアは入力したリクエストがbotか人間かのスコアを示しており、1に近いほど人間が入力しています。逆に0.1あたりはbotの入力です。どこまで厳しくするかはお任せしますが、私は0.5以上であれば人間のリクエストであるとしています。",[17,1789,1790],{},"return $result['score'] > 0.5"," としてfalseであればリクエストを拒否したり、エラーを返す様にします。フォーム系で汎用的に使用できる様に私はサービスプロバイダにしています。",[55,1793,1794],{"id":1794},"エラー処理",[13,1796,1797],{},"エラーの場合は以下の様なレスポンスがきます。（例です）",[168,1799,1801],{"className":1486,"code":1800,"language":1488,"meta":176,"style":176},"[\n  \"success\"=> false, \n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n  \"error-codes\": [\n      0=>'timeout-or-duplicate'\n  ] \n]\n",[17,1802,1803,1807,1812,1816,1820,1824,1829,1834,1839],{"__ignoreMap":176},[431,1804,1805],{"class":433,"line":434},[431,1806,1751],{},[431,1808,1809],{"class":433,"line":313},[431,1810,1811],{},"  \"success\"=> false, \n",[431,1813,1814],{"class":433,"line":4},[431,1815,1766],{},[431,1817,1818],{"class":433,"line":471},[431,1819,1771],{},[431,1821,1822],{"class":433,"line":478},[431,1823,1776],{},[431,1825,1826],{"class":433,"line":488},[431,1827,1828],{},"  \"error-codes\": [\n",[431,1830,1831],{"class":433,"line":494},[431,1832,1833],{},"      0=>'timeout-or-duplicate'\n",[431,1835,1836],{"class":433,"line":504},[431,1837,1838],{},"  ] \n",[431,1840,1841],{"class":433,"line":530},[431,1842,1720],{},[13,1844,1845,1846,1849],{},"このエラーはAPIの通信が失敗したり、必要なパラメーターが不足していたりなどのエラーです。 ",[1735,1847,1848],{},"リクエストがBotである"," という意味ではないので注意。Botかの判定はあくまで成功時に取得するscoreで判定します。",[55,1851,1852],{"id":1852},"エラーの説明",[13,1854,1855,1858,1860],{},[1735,1856,1857],{},"missing-input-secret",[17,1859,1725],{},"のようなサーバー側のrecaptchaのキーを忘れている。",[168,1862,1864],{"className":1486,"code":1863,"language":1488,"meta":176,"style":176},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'), \u002F\u002F このへん\n    'response'=>$request->recaptcha\n]\n",[17,1865,1866,1870,1878,1882],{"__ignoreMap":176},[431,1867,1868],{"class":433,"line":434},[431,1869,1705],{},[431,1871,1872,1875],{"class":433,"line":313},[431,1873,1874],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),",[431,1876,1877],{}," \u002F\u002F このへん\n",[431,1879,1880],{"class":433,"line":4},[431,1881,1715],{},[431,1883,1884],{"class":433,"line":471},[431,1885,1720],{},[13,1887,1888,1891,1893],{},[1735,1889,1890],{},"invalid-input-secret",[17,1892,1725],{},"が不正。間違っているキーを使用している。キーが正しいか、保護対象のドメインとして登録しているかを確認。",[13,1895,1896,1899,1902],{},[1735,1897,1898],{},"missing-input-response",[17,1900,1901],{},"'response'=>$request->recaptcha","を忘れている、空文字。",[13,1904,1905,1908,1910],{},[1735,1906,1907],{},"invalid-input-response",[17,1909,1901],{},"の値が不正。型などを確認。",[13,1912,1913,1916],{},[1735,1914,1915],{},"bad-request","\nPOSTで送っているかを確認。",[13,1918,1919,1922,1924],{},[1735,1920,1921],{},"timeout-or-duplicate",[17,1923,1901],{},"の値を二回送っているか、フロントのトークンが２分以上経過した。",[13,1926,1927],{},"フロントで取得したトークンはバックエンドでのこの検証を行うともう一度利用することができません。またこのトークンは",[168,1929,1933],{"className":1930,"code":1931,"language":1932,"meta":176,"style":176},"language-javascript shiki shiki-themes material-theme-ocean","grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n    document.getElementById(\"recaptcha\").value=token;\n    document.getElementById(\"test-form\").submit();\n});\n","javascript",[17,1934,1935,1985,2014,2040],{"__ignoreMap":176},[431,1936,1937,1939,1941,1943,1945,1947,1949,1951,1953,1955,1957,1959,1961,1963,1965,1967,1969,1971,1973,1975,1977,1979,1981,1983],{"class":433,"line":434},[431,1938,1441],{"class":1186},[431,1940,1190],{"class":437},[431,1942,1230],{"class":1166},[431,1944,1170],{"class":1186},[431,1946,1235],{"class":437},[431,1948,911],{"class":522},[431,1950,1235],{"class":437},[431,1952,1242],{"class":437},[431,1954,1245],{"class":437},[431,1956,1248],{"class":441},[431,1958,1251],{"class":437},[431,1960,1254],{"class":437},[431,1962,585],{"class":522},[431,1964,1235],{"class":437},[431,1966,1261],{"class":437},[431,1968,1177],{"class":1186},[431,1970,1190],{"class":437},[431,1972,1268],{"class":1166},[431,1974,1170],{"class":1186},[431,1976,1215],{"class":445},[431,1978,1170],{"class":437},[431,1980,1277],{"class":1173},[431,1982,1177],{"class":437},[431,1984,1180],{"class":437},[431,1986,1987,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012],{"class":433,"line":313},[431,1988,1989],{"class":1186},"    document",[431,1991,1190],{"class":437},[431,1993,1292],{"class":1166},[431,1995,1170],{"class":441},[431,1997,519],{"class":437},[431,1999,836],{"class":522},[431,2001,519],{"class":437},[431,2003,1177],{"class":441},[431,2005,1190],{"class":437},[431,2007,1307],{"class":1186},[431,2009,516],{"class":437},[431,2011,1277],{"class":1186},[431,2013,1199],{"class":437},[431,2015,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038],{"class":433,"line":4},[431,2017,1989],{"class":1186},[431,2019,1190],{"class":437},[431,2021,1292],{"class":1166},[431,2023,1170],{"class":441},[431,2025,519],{"class":437},[431,2027,770],{"class":522},[431,2029,519],{"class":437},[431,2031,1177],{"class":441},[431,2033,1190],{"class":437},[431,2035,585],{"class":1166},[431,2037,1196],{"class":441},[431,2039,1199],{"class":437},[431,2041,2042,2044,2046],{"class":433,"line":471},[431,2043,1261],{"class":437},[431,2045,1177],{"class":1186},[431,2047,1199],{"class":437},[13,2049,2050],{},"の実行から２分以内で利用する必要があります。そのためページがリロードされた瞬間ときに実行していると、フォーム入力中に時間切れになったります。そのためsubmit時に実行することをお勧めします。",[47,2052,2053],{"id":2053},"実装まとめ",[13,2055,2056],{},"以上がrecaptchaの実装方法です。recaptchaはあくまでBotかどうかの判断のみをしているので、実際にリクエストを通すかはアプリケーション側の仕事です。フロントとバックでの実装が少し面倒ですが、recaptchaの機能を自前で実装しようとするとそこそこ、面倒なのと実績のあるGoogle様に検証してもらうのも結構安心です。",[13,2058,2059],{},"バックエンドはLaravelを想定しますが、他のフレームワークや言語でもやることは特に変わりません。上手くご自身の環境に置き換えてください。",[2061,2062,2063],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}",{"title":176,"searchDepth":4,"depth":4,"links":2065},[2066,2067,2068,2069,2073,2077],{"id":361,"depth":313,"text":362},{"id":368,"depth":313,"text":368},{"id":377,"depth":313,"text":378},{"id":419,"depth":313,"text":419,"children":2070},[2071,2072],{"id":655,"depth":4,"text":656},{"id":919,"depth":4,"text":919},{"id":1463,"depth":313,"text":1463,"children":2074},[2075,2076],{"id":1794,"depth":4,"text":1794},{"id":1852,"depth":4,"text":1852},{"id":2053,"depth":313,"text":2053},[326],"2021-05-21","reCAPTCHAのbot検証をフロントエンド とLaravelでのバックエンドの実装を行います。",{},"\u002Farticles\u002Fimplement-recaptcha",{"title":344,"description":2080},"articles\u002Fimplement-recaptcha",[1488,2086,2087,337],"js","laravel","_mix\u002Flaravel-recaptcha.jpg","AYarFTQUZE-KIUOU5A3co7uCUDW9hqT091VEnFYUN6Q",{"id":2091,"title":2092,"body":2093,"category":2507,"createdAt":2509,"description":2092,"extension":329,"index":330,"meta":2510,"navigation":332,"path":2511,"publish":332,"seo":2512,"series":330,"seriesTitle":330,"stem":2513,"tag":2514,"thumbnail":2516,"updatedAt":330,"__hash__":2517},"articles\u002Farticles\u002Fvps-ssh-first.md","VPSでやっておきたいSSHの最低限のセキュティ設定。",{"type":10,"value":2094,"toc":2495},[2095,2098,2101,2104,2118,2133,2136,2140,2143,2149,2152,2155,2164,2167,2170,2181,2184,2187,2190,2196,2199,2205,2212,2218,2248,2252,2264,2271,2277,2280,2283,2286,2289,2295,2298,2304,2311,2317,2326,2332,2340,2346,2353,2370,2373,2376,2383,2389,2407,2410,2416,2419,2425,2428,2433,2436,2439,2442,2448,2451,2454,2460,2470,2476,2479,2485,2488,2492],[13,2096,2097],{},"こんにちはjunです。webエンジニアとして働いていますが会社の人数が少なく、インフラの構築をすることがあります。と言ってもLAMP環境を作成するぐらいですけど。",[13,2099,2100],{},"構築は慣れてきましたがその中で、私が一番考えるのはセキュリティーです。構築するアプリケーションによってはセキュリティー設定はことなりますが、今回は自分でサーバを構築する時に最低限やった方がいいSSHの設定をメモがてら記事にしようと思います。",[13,2102,2103],{},"今回行う設定は以下の通りです。",[28,2105,2106,2109,2112,2115],{},[31,2107,2108],{},"rootユーザーのリモートログイン禁止化",[31,2110,2111],{},"パスワード認証禁止と鍵認証化",[31,2113,2114],{},"鍵認証の実装方法",[31,2116,2117],{},"ポート変更とfirewallの設定",[2119,2120,2124,2125],"div",{"className":2121},[2122,2123],"alert","alert-success","\nなお今回説明する環境\n",[28,2126,2127,2130],{},[31,2128,2129],{},"サービス：GMO conoha VPS",[31,2131,2132],{},"OS：centos8",[13,2134,2135],{},"この設定をすべき理由や背景などから話すため、さっさと方法を知りたい場合は「root以外のユーザーを作成していく」から見てください。",[47,2137,2139],{"id":2138},"sshはめちゃくちゃ狙われている","SSHはめちゃくちゃ狙われている",[13,2141,2142],{},"SSHはリモートでサーバーを操作できる口のため、一番厳重にしなければなりません。あまり運用していないサーバー、購入したばかりでドメインと紐づけられていなくても世界中からBOTによる不正アクセス試行が発生します。私がVPSで購入してイメージが立った時も早速ログを見たところ以下の様になっていました。",[168,2144,2147],{"className":2145,"code":2146,"language":173},[171],"April  8 12:10:04 xxx-xxx-xx-xxx sshd[6887]: Failed password for root from 209.141.36.197 port 54506 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6891]: Failed password for invalid user oracle from 209.141.36.197 port 54518 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6892]: Failed password for invalid user test from 209.141.36.197 port 54512 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6889]: Failed password for root from 209.141.36.197 port 54520 ssh2\nApril  8 12:10:06 xxx-xxx-xx-xxx sshd[6904]: Failed password for root from 222.187.238.136 port 58496 ssh2\nApril  8 12:10:10 xxx-xxx-xx-xxx sshd[6904]: Failed password for root from 222.187.238.136 port 58496 ssh2\nApril  8 12:16:26 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:30 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:33 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:37 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:41 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:45 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:49 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:16:53 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:16:56 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:27:50 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\nApril  8 12:27:54 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\nApril  8 12:27:57 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\n",[17,2148,2146],{"__ignoreMap":176},[13,2150,2151],{},"これはSSHの接続失敗の履歴です、同じIPで定間隔でrootユーザーによるパスワード接続を試みています。これはパスワードリスト攻撃・総当たり攻撃というものです。確率は低いですがrootのパスワードが万が一にあった場合、サーバの最上権限であるrootが第三者に掌握されます。",[13,2153,2154],{},"ドメインに紐づけられておらずIPだけでもこの様にBOTがインターネットを徘徊して、脆弱なサーバを狙っています。VPSを購入したらすぐにデフォルトの設定を変えて、比較的安全なものに変更しましょう。",[2119,2156,2159,2160,2163],{"className":2157},[2122,2158],"alert-info","\nターミナルで",[17,2161,2162],{},"whois","コマンドを使用してIPのwois情報を調べられます。登録された国などもわかります。ちなみに上記のIPのほとんどが中国でした。\n",[47,2165,2166],{"id":2166},"安全にするために変更する設定",[13,2168,2169],{},"デフォルトの設定から以下の変更を行います。",[28,2171,2172,2175,2178],{},[31,2173,2174],{},"rootによるリモートログインを禁止にする。",[31,2176,2177],{},"SSHをパスワード認証でなく、鍵認証にする",[31,2179,2180],{},"ポートを変更する",[13,2182,2183],{},"これらの設定は最低限行うべきものです。より強固にする場合は、色々と他に設定しますがまずはこれでいきましょう。",[55,2185,2186],{"id":2186},"root以外のユーザーを作成していく",[13,2188,2189],{},"最終的にはrootのリモートログインを禁止するため別の操作ユーザーを作成していきます。まずはrootでログインしましましょう。",[168,2191,2194],{"className":2192,"code":2193,"language":173},[171],"ssh root@123.456.78.901\n（IPなどは自分のものに置き換えてください）\n",[17,2195,2193],{"__ignoreMap":176},[13,2197,2198],{},"開発用・SSH接続用ユーザーを作成します。",[168,2200,2203],{"className":2201,"code":2202,"language":173},[171],"# useradd develop\n# passwd develop\n",[17,2204,2202],{"__ignoreMap":176},[13,2206,2207,2208,2211],{},"これでユーザーとそのパスワードが設定されました。次にそのユーザーを",[17,2209,2210],{},"wheel","グループに所属させます。",[168,2213,2216],{"className":2214,"code":2215,"language":173},[171],"# usermod -G wheel develop\n",[17,2217,2215],{"__ignoreMap":176},[13,2219,2220,2222,2223,2226,2227,2230,2231,2234,2235,2238,2239,2241,2242,2244,2245,2247],{},[17,2221,2210],{},"グループに所属することで",[17,2224,2225],{},"develop","が",[17,2228,2229],{},"sudo","を使用できる様になり、また",[17,2232,2233],{},"su","にてrootになることができます。",[17,2236,2237],{},"\u002Fetc\u002Fsudoers","では",[17,2240,2210],{},"グループで行える操作などを定義してあり、基本的にはrootを使用せずdevelopでsshログインをして操作します。yumで何かインストールしたい時とかは",[17,2243,2229],{},"したり",[17,2246,2233],{},"でrootになります。",[2249,2250,2251],"h4",{"id":2251},"wheelのみがsuできる様にする",[13,2253,2254,2255,2257,2258,2260,2261,2263],{},"デフォルトではsuでどのユーザーもrootになれます。",[17,2256,2225],{},"意外にもユーザは存在し、そのユーザーが狙われることもあります。そのため",[17,2259,2210],{},"グループに属しているユーザーだけが",[17,2262,2233],{},"を用いてrootになれる様にしましょう。",[13,2265,2266,2267,2270],{},"suの設定は",[17,2268,2269],{},"\u002Fetc\u002Fpam.d\u002Fsu","に記述されています。",[168,2272,2275],{"className":2273,"code":2274,"language":173},[171],"# vi \u002Fetc\u002Fpam.d\u002Fsu\n\n--------\n# コメントアウトしているこの箇所を外す\n# auth            required        pam_wheel.so use_uid\n↓\nauth            required        pam_wheel.so use_uid\n--------\n",[17,2276,2274],{"__ignoreMap":176},[13,2278,2279],{},"コメントアウトされている箇所を外します。これでsuでrootになるためにwheelグループに属したユーザーから行うという制限ができました。",[55,2281,2282],{"id":2282},"develop用のssh鍵を作成",[13,2284,2285],{},"現在sshはパスワードでログインできますが、最終的にはパスワード認証を禁止にします。そしてよりセキュアな鍵認証式に変更します。基本的にログインの手間やセキュリティ的に鍵認証にした方がいいです。鍵認証の原理は今回は省きます。それではdevelop用の鍵を作成します。",[13,2287,2288],{},"クライアント側で秘密鍵と公開書きを作成します。",[168,2290,2293],{"className":2291,"code":2292,"language":173},[171],"% ssh-keygen\nGenerating public\u002Fprivate rsa key pair.\nEnter file in which to save the key (\u002FUsers\u002Fyou\u002F.ssh\u002Fid_rsa): #id_rsaというファイル名でよければそのままEnter\nEnter passphrase (empty for no passphrase): 　#パースフレーズを設定する場合は入力。なければEnter\nEnter same passphrase again:\n",[17,2294,2292],{"__ignoreMap":176},[13,2296,2297],{},"これで秘密鍵と公開鍵が生成されます。秘密鍵をクライアントに置いておき、公開鍵をサーバに転送します。",[168,2299,2302],{"className":2300,"code":2301,"language":173},[171],"scp .\u002Fid_rsa.pub develop@123.456.78.902:~\u002F\n",[17,2303,2301],{"__ignoreMap":176},[13,2305,2306,2307,2310],{},"サーバー側に戻ります。developのホームディレクトリに",[17,2308,2309],{},".ssh","というディレクトリがあるかを確認します。",[168,2312,2315],{"className":2313,"code":2314,"language":173},[171],"$ ls -a ~\u002F\n",[17,2316,2314],{"__ignoreMap":176},[13,2318,2319,2321,2322,2325],{},[17,2320,2309],{},"は隠しフォルダなので",[17,2323,2324],{},"ls -a","を使用します。無い場合はディレクトリを作成します。今回は無かったとしましょう。",[168,2327,2330],{"className":2328,"code":2329,"language":173},[171],"$ mkdir ~\u002F.ssh\n$ chmod 700 ~\u002F.ssh \n",[17,2331,2329],{"__ignoreMap":176},[13,2333,2334,2336,2337,2339],{},[17,2335,2309],{},"は権限を700にしましょう。そうで無いと権限エラーによってdevelopに対するsshを行うことができません。そしてクライアントから転送された公開側の名前を変更して",[17,2338,2309],{},"配下に置いておきます。",[168,2341,2344],{"className":2342,"code":2343,"language":173},[171],"$ cp ~\u002Fid_rsa.pub ~\u002F.ssh\u002Fauthorized_keys\n$ chmod 600 ~\u002F.ssh\u002Fauthorized_keys\n$ rm ~\u002Fid_rsa.pub\n",[17,2345,2343],{"__ignoreMap":176},[13,2347,2348,2349,2352],{},"ここでも",[17,2350,2351],{},"authorized_keys","は600権限を付けないと使用できません。",[2119,2354,2356,2359,2367],{"className":2355},[2122,2158],[13,2357,2358],{},"authorized_keysにリネームする理由",[13,2360,2361,2362,1177],{},"The authorized_keys file in SSH specifies the SSH keys that can be used for logging into the user account for which the file is configured.(",[63,2363,2366],{"target":2364,"href":2365},"_blank","https:\u002F\u002Fwww.ssh.com\u002Facademy\u002Fssh\u002Fauthorized-keys-file#:~:text=The%20authorized_keys%20file%20in%20SSH,keys%20and%20needs%20proper%20management.","参照",[13,2368,2369],{},"authorized_keysファイルはその公開鍵に紐づく秘密鍵を用いてログインできるユーザーを特定します。簡単な話、authorized_keysにしておけばsshが自動的に鍵を判別してくれるのでその名前にしておく。",[13,2371,2372],{},"これでdevelopは鍵認証を用いてログインできる様になりました。",[55,2374,2375],{"id":2375},"rootのリモートログインとパスワード認証を禁止にする",[13,2377,2378,2379,2382],{},"developで鍵認証で接続できることを確認したら次はrootのリモートログインとパスワード認証を禁止にします。",[17,2380,2381],{},"\u002Fetc\u002Fssh\u002Fsshd_config","を編集していきます。",[168,2384,2387],{"className":2385,"code":2386,"language":173},[171],"# vi \u002Fetc\u002Fssh\u002Fsshd_config\n\n---------- 以下行を追加 -----------\nPermitRootLogin no\n---------- 以上行を追加 -----------\n\n--------------以下を変更--------------\nPasswordAuthentication yes\n#RSAAuthentication yes\n#PubkeyAuthentication yes\n--------------↓--------------\nPasswordAuthentication no\nRSAAuthentication yes\nPubkeyAuthentication yes\n------------------------------------------\n",[17,2388,2386],{"__ignoreMap":176},[13,2390,2391,2394,2395,2398,2399,2402,2403,2406],{},[17,2392,2393],{},"PermitRootLogin no","という記述をつけます。名の通りルートのログインを禁止にさせました。そして",[17,2396,2397],{},"PasswordAuthentication no","としパスワード認証を禁止。",[17,2400,2401],{},"RSAAuthentication yes","と",[17,2404,2405],{},"PubkeyAuthentication yes","のコメントアウトを外して鍵認証のみでつなげる様に明示的に設定します。",[13,2408,2409],{},"これであとはsshdをリスタートさせれば反映させます。",[168,2411,2414],{"className":2412,"code":2413,"language":173},[171],"# systemctl restart sshd\n",[17,2415,2413],{"__ignoreMap":176},[13,2417,2418],{},"試しにチェックしましょう。",[168,2420,2423],{"className":2421,"code":2422,"language":173},[171],"ssh root@xxx.xxx.xxx\nroot@xxx.xxx.xxx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).\n\nssh develop@xxx.xxx.xxx\ndevelop@xxx.xxx.xxx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).\n",[17,2424,2422],{"__ignoreMap":176},[13,2426,2427],{},"OKです。逆にdevelopで鍵認証で接続したのに弾かれたら設定をミスっています。",[2119,2429,2432],{"className":2430},[2122,2431],"alert-danger","\n接続系を設定するときは2つのターミナルタブを開いていておき、片方はずっとroot化できる状態でサーバーにログインしておき、片方で接続テストをしましょう。もし失敗してどのユーザー・方法でも接続できなくなると二度とサーバーに接続できなくなります。\n",[55,2434,2435],{"id":2435},"sshの接続ポートを変更する",[13,2437,2438],{},"sshはデフォルトで22のポートで接続します。しかしこのポートはwell-knownポートとして知られており、攻撃者もデフォルトのポートを狙ってきます。そこでsshのポートを変更してさらに攻撃されにくくします。sshに接続するためにはユーザー名、鍵・パスワード、ポートが合わないといけないからです。",[13,2440,2441],{},"まずはsshdのデフォルトポートを変更します。変更するポート番号は49513～65535あたりから自由に設定しましょう。",[168,2443,2446],{"className":2444,"code":2445,"language":173},[171],"# vi \u002Fetc\u002Fssh\u002Fsshd_config\n--------------以下を変更--------------\n#Port 22    \n--------------↓--------------\nPort 49510\n------------------------------------------\n",[17,2447,2445],{"__ignoreMap":176},[13,2449,2450],{},"そしてfirewallが有効な場合、sshによる22は許可していてもカスタムしたポートは許可していないことがあります。これではカスタムポートへ接続する前にfirewallで弾かれるので設定します。",[13,2452,2453],{},"まずはsshdのfirewallの設定ファイルをコピーします。",[168,2455,2458],{"className":2456,"code":2457,"language":173},[171],"cp \u002Fusr\u002Flib\u002Ffirewalld\u002Fservices\u002Fssh.xml \u002Fetc\u002Ffirewalld\u002Fservices\u002F\n",[17,2459,2457],{"__ignoreMap":176},[13,2461,2462,2465,2466,2469],{},[17,2463,2464],{},"\u002Fetc\u002Ffirewalld\u002Fservices\u002F","にて追加の設定を行うことができます。コピーした",[17,2467,2468],{},"ssh.xml","を編集します。",[168,2471,2474],{"className":2472,"code":2473,"language":173},[171],"# vi \u002Fetc\u002Ffirewalld\u002Fservices\u002F\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n  \u003Cservice>\n    \u003Cshort>SSH\u003C\u002Fshort>\n    \u003Cdescription>Secure Shell (SSH) is a protocol ...\u003C\u002Fdescription>\n    \u003Cport protocol=\"tcp\" port=\"22\"\u002F>\n    \u003Cport protocol=\"tcp\" port=\"49510\"\u002F> # 追加\n\u003C\u002Fservice>\n",[17,2475,2473],{"__ignoreMap":176},[13,2477,2478],{},"上記の設定でsshサービスによる49510ポートの接続が許可されました。\nそして設定が終わったらfirewallをリロードします。またsshdも設定を変えたのでリロードします。",[168,2480,2483],{"className":2481,"code":2482,"language":173},[171],"# firewall-cmd --reload\n# systemctl restart sshd\n",[17,2484,2482],{"__ignoreMap":176},[13,2486,2487],{},"これでOKです。ポートをカスタムのものに指定して接続して通れば問題ありません。そして22（デフォルト）で接続して弾かれるかも確認しましょう。",[47,2489,2491],{"id":2490},"以上","以上！",[13,2493,2494],{},"以上がsshの最低限必要な設定です。インフラ系では結構基礎的な内容だそうです。sshは狙われており、適切に設定することで安全に使用することができます。",{"title":176,"searchDepth":4,"depth":4,"links":2496},[2497,2498,2506],{"id":2138,"depth":313,"text":2139},{"id":2166,"depth":313,"text":2166,"children":2499},[2500,2503,2504,2505],{"id":2186,"depth":4,"text":2186,"children":2501},[2502],{"id":2251,"depth":471,"text":2251},{"id":2282,"depth":4,"text":2282},{"id":2375,"depth":4,"text":2375},{"id":2435,"depth":4,"text":2435},{"id":2490,"depth":313,"text":2491},[2508],"ministack","2021-04-25",{},"\u002Farticles\u002Fvps-ssh-first",{"title":2092,"description":2092},"articles\u002Fvps-ssh-first",[337,338,2515],"network","_mix\u002Fcyber-security-3400657_640.jpg","0p7STSITYfCR0ETcCcYcMeNhmXEtc_cRA-W1MKHcN_8",1780987150205]