技術備忘録

環境構築によるトラブルの解決方法、知った技術のまとめなどを自分のためにも書き連ねていきます。あわよくば誰かの参考になればと思います。

脆弱性のあるWebアプリを直すイベントに参加した

先日リクルートが開催する 脆弱性を直して学ぶ!Webセキュリティハンズオン に参加しました。 コロナという事情もありオンラインで開催していたので、地方の自分としては嬉しかったです。

このイベントでは、事前課題として脆弱性のあるWEBアプリを直すというものが渡され当日はその解説を行うというものでした。

この脆弱性のあるアプリは、昨年の新人研修で行ったWebセキュリティの研修で使ったらしいです。

直していくアプリケーションはコードが公開されておりこちらです→GitHub - ommadawn46/badsns2019: Bad SNS 2019 Web Application Hardening (Boot Camp 2019) - Speaker Deck

今回せっかく学んだことを忘れないためにもこのブログに残したいと思います。

ただ、脆弱性が20個もあり全てを書くととても長くなるので、一部のみ書いていこうと思います。

今回書くこと

今回は以下の3点について書いていこうと思います。

パストラバーサル

今回のアプリケーションではパストラバーサルが発生するような状況になっていました。

このアプリはユーザ登録時にアイコンが設定できるようになっています。

また、アプリは以下の仕様を持っています。

  • 新規登録時にアイコンは非同期でアップロードを行い、アイコンが保存できればファイル名を返す

  • ファイル名は任意にファイル名をパラメータとして送ることが出来る

  • イコン画像の取得のAPI(/users/:id/icon )にアクセスするとファイルがダウンロードされる

攻撃シナリオ

  1. 新規登録時にファイルをアップロード(APIからファイル名 A.png が返ってくる)

  2. 攻撃者はファイル名を ../../../../../../../etc/passwd などに変更

  3. /users/:id/icon にアクセスするとサーバ上の/etc/passwdが取得できる

修正が必要な箇所は、アイコン画像取得のAPIで修正前のコードはこちらです。 badsns2019/users_controller.rb at master · ommadawn46/badsns2019 · GitHub

良くない修正例

アイコンのファイル名に対して以下のようなバリデーションを新規登録時にかけることによって、不正なファイル名を防ぐようにしました。

files/sns/app/model/user.rb

before_save :verify_valid_user

def verify_valid_user
  if self.icon_file_name != sanitize_filename(self.icon_file_name)
    errors.add(:base, 'アイコン名が不正です')
  end
  return false if errors.any?
end

def sanitize_filename(filename)
  return unless filename
  filename.gsub! /^.*(\\|\/)/, ''
  filename.gsub! /[^A-Za-z0-9\.\-]/, '_'
end

../ などの文字をフィルターで変換して防御するパターンは、攻撃不能か確認するのが難しく完璧に防ごうとするとロジックが複雑になるという欠点を持っています。

なので、このようなパストラバーサルに対しての修正は、カノニカルパス(RealPath)を使用した検証を推奨というアドバイスをいただきました。

より良い修正例

Rubyでは、realpathを返してくれるメソッドFile.realpath (Ruby 2.7.0 リファレンスマニュアル)があるのでそちらを使って実装します。

What is directory traversal, and how to prevent it? | Web Security Academy を参考に実装しました。

Below is an example of some simple Java code to validate the canonical path of a file based on user input:

File file = new File(BASE_DIRECTORY, userInput);

if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) { // process file }

files/sns/app/controllers/users_controller.rb

def icon
  user = User.find_by id: params[:id]
  send_data File.read "#{Rails.root}/public/images/default.png", disposition: 'inline' and return if user.nil?
  begin
    # 追加
    base_dir = "#{Rails.root}/public/icons/"
    raise unless File.realpath(user[:icon_file_name], base_dir).start_with?(base_dir)
    
    send_data File.read "#{Rails.root}/public/icons/#{user[:icon_file_name]}", disposition: 'inline'
  rescue
    presets = Dir["#{Rails.root}/public/icons/presets/*"].sort
    send_data File.read presets[user.id % presets.length], disposition: 'inline'
  end
end

加えたのは、こちらの2行です。

base_dir = "#{Rails.root}/public/icons/"
raise unless File.realpath(user[:icon_file_name], base_dir).start_with?(base_dir)

File.realpath(user[:icon_file_name], base_dir)絶対パスを取ることによってディレクトリを決定し、 その時のディレクトリがbase_dirから始まるものでなければ例外を発生させるという処理です。

このようにすることでパストラバーサルを回避することが出来ました。

SSRF

この脆弱性については聞いたことすらありませんでした。

アプリケーションサーバからOSコマンドインジェクションなどを行うことで、内部のネットワークのサーバに対してアクセス出来るという脆弱性だそうです。 今回の場合だとOpenGraphを取得してくる処理が問題で起きるようです。

該当コード: badsns2019/open_graph_controller.rb at master · ommadawn46/badsns2019 · GitHub

ユーザからの投稿したURLからOGタグを取得するのですが、こちらから内部のftpサーバにアクセスも出来るようでそこを突いた脆弱性でした。 (当日のイベントではSSRF自体聞いたことなく、メモを取る暇がなかったのでコードの修正は割愛させていただきます🙇‍♂️)

1, 2年前にEC2やGCEからcredentialsが取得できると話題になった脆弱性らしいです。

SSRF脆弱性を利用したGCE/GKEインスタンスへの攻撃例 | “>はい

SSRF(Server Side Request Forgery)徹底入門 | 徳丸浩の日記

暗号利用モード

該当コード: badsns2019/password_reset_controller.rb at master · ommadawn46/badsns2019 · GitHub

この OpenSSL::Cipher.new(‘aes-256-ecb’) が問題のコードです。 class OpenSSL::Cipher (Ruby 2.7.0 リファレンスマニュアル) によるとブロック暗号の方式は

  • CBC

  • “CFB”

  • “ECB”

  • “OFB”

の4パターンが利用できるようですが、今回のこのアプリケーションでは、ECBモードが採用されていました。

このECBモードは、同じ平文ブロックに対しては、同じ暗号ブロックに変換するという仕様で暗号化は出来ているものの(他の暗号モードと比べ)見破りやすくなっています。

暗号を使う時には公式のドキュメントを見て、正しく暗号化のモードを理解しなければいけないなと思った脆弱性でした。

まとめ

今回紹介した脆弱性以外にも、SQLインジェクションXSSなどなど…様々な脆弱性を解説していただきました。 大変勉強になりました、ありがとうございました!

今回のイベントで、セキュリティについての興味を持ち勉強する良いきっかけになりました。 次回似たようなイベントが開催された時には、全部解けるぐらいの知識をつけられるよう勉強していきたいと思います。

Burp Suite - Cybersecurity Software from PortSwigger (このツールは便利そうだったので、使ってみたい…)

Rancherの Component controller-manager is unhealthy への対処

Rancherを使っているのですが、その時に Alert Component controller-manager is unhealthy というエラーがでました。 その時の対処をメモがてら残しておきたいと思います。

この記事の対処法は一時的な対処であり根本的な対処法ではないです

Rancherの構成

Kubernetesバージョン v1.17.2-rancher1-2

クラスタ内には、master nodeが1台、worker nodeが複数台という構成です。 ノードは全てクラウドは使わず、VMやらのオンプレの環境で行っています。

エラー内容

こちらのIssueコメントの写真と同じような状況になっていました。

Unhealthy controller manager and scheduler after leaving it running overnight · Issue #14036 · rancher/rancher · GitHub

対処方法

master nodeに直接sshで入り、dockerを再起動(systemctl restart docker)することで直りました。

再起動を行う前に、クラスタ以外のコンテナが動いていないことを確認して行った方が良いと思います

色々調べてみたのですが、クラスタの再構成したら直ったとかなどはあったのですが根本的な解決方法は探しきれませんでした。

また、docker自体を再起動する前に kube-controll-managerのコンテナだけを再起動したのですが、反応がなく再起動できなくなったのでdockerのシステムごと再起動をしたら直ったという経緯です。

まとめてきなやつ

このバグが発生した原因は、(推測ですが)オペレーションミスにより大量のJobを断続的に投げたことしまったことじゃないかなと思っています。

(それによりschedulerやらcomponent managerなどが正常に動かなくなった?)

今回は検証環境だったので良かったのですが、本番環境とかだったらと思うと…まだまだ知識が足りないなーと思わせるバグでした。

Slackで2日おきに通知を設定する方法

タイトルの通りで、Slackの/remind を使って2日おきに通知(remind)をしたかったのですが、その様なコマンドはなく単純にはできませんでした。 ですが、(擬似的に)2日おきに通知がくるようにできたので以下メモとして残しておきます。

考え方

隔週の特定の曜日 + 日付 で通知を行います。

一例

今週: 月, 水, 金
来週: 日, 火, 木, 土

(設定した日の)第一週目は 月, 水, 金

第二週目は 日, 火, 木, 土

第三週目は 第一周目の設定と同じ(隔週で設定しているので) 月, 水, 金

第四週目は 第二周目の設定と同じ(隔週で設定しているので) 日, 火, 木, 土 ... ...

上記のような感じで2日通知が来るように設定できます。

具体例

具体的な例は以下のとおりです。

下のようなカレンダーがあるとして、仮に本日の日付が 2019/05/26 だとします。

      May 2019
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

     June 2019
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30

以下のようにコマンドを1つずつ行うことで2日おきで通知設定ができます。

/remind @channel プログラミングしましたか!! at 19:00 every other Tuesday on 05/28/2019
/remind @channel プログラミングしましたか!! at 19:00 every other Thursday on 05/30/2019
/remind @channel プログラミングしましたか!! at 19:00 every other Saturday on 06/01/2019


/remind @channel プログラミングしましたか!! at 19:00 every other Monday on 06/03/2019
/remind @channel プログラミングしましたか!! at 19:00 every other Wednesday on 06/05/2019
/remind @channel プログラミングしましたか!! at 19:00 every other Friday on 06/07/2019
/remind @channel プログラミングしましたか!! at 19:00 every other Sunday on 06/09/2019