Railsアプリの脆弱性パターン / vulnerability patterns for Rails app

Railsアプリの脆弱性パターン / vulnerability patterns for Rails app

集え、Rubyist ~著名Rubyistから学ぼう~ iCARE Dev Meetup #14
https://icare.connpass.com/event/189356/

Ecad9d801d79f6c6e5df93094690685e?s=128

Takumi Shotoku

October 21, 2020
Tweet

Transcript

  1. Railsアプリの脆弱性パターン iCARE Dev Meetup #14 2020/10/21 1

  2. 自己紹介 • 名前: 神速 • 会社: メドピア株式会社 • 所属: CTO室SRE

    • GitHub: @sinsoku (画像右上) • Twitter: @sinsoku_listy (画像右下) • Rails歴: 8年くらい 2
  3. 著書の同人誌1 1 表紙のイラスト: Ixyさん 3

  4. 9月の流行 • ドコモ口座を悪用した不正送金についてまとめてみた2 • 【お詫び】IPアドレスが他者からも確認できてしまう不具合に ついて3 3 https://note.jp/n/n3e6451c9b147 2 https://piyolog.hatenadiary.jp/entry/2020/09/08/054431

    4
  5. Railsアプリ x セキュリティ 5

  6. 15分では全てを話せない • セッションハイジャック • CSRF, XSS, CSP • オープンリダイレクト •

    SQLインジェクション • OSコマンドインジェクショ ン • Dos攻撃, DDos攻撃 • ブルートフォース • IPスプーフィング • タイミング攻撃 • DNSリバインディング • SSRF攻撃 6
  7. 何を話すか... 7

  8. Railsセキュリティガイド4 4 https://railsguides.jp/security.html 8

  9. 9

  10. 徳丸浩さん「Railsエンジニアのためのウェブセキュリティ入門」5 5 https://www.slideshare.net/ockeghem/ruby-on-rails-security-142250872 10

  11. セキュリティ専門家ではない 11

  12. 今日はなすこと 1. 脆弱なコード • ! 未検証なparamsの使用 • ! 個人情報(IPアドレスなど)の流出 2.

    脆弱な仕様 • ! 認証に関わる仕様の事例 • ! 未ログインで使えるフォーム 12
  13. ! 未検証なparamsの使用 13

  14. 例: 記事にタグをつける機能 • 自分の記事にタグをつける 14

  15. テーブルの設計6 # db/migrate/xxx_create_tags.rb def change create_table :tags do t.references :article,

    comment: "هࣄID" t.string :name, comment: "λά໊" end end 6 本当はタグと関連テーブルを別にした方が良い。 15
  16. コントローラー # POST /tags def create @tag = Tag.new(tag_params) if

    @tag.save flash[:notice] = "λάΛ࡞੒͠·ͨ͠ɻ" else flash[:notice] = "λάͷ࡞੒ʹࣦഊ͠·ͨ͠ɻ" end redirect_to article_path(@tag.article) end def tag_params params.require(:tag).permit(:article_id, :name) end 16
  17. 画面のイメージ 17

  18. 記事の詳細ページ # GET /articles/:id def show @article = current_user.articles.find(params[:id]) @tag

    = Tag.new(article: @article) end ビューのコード例 = form_with @tag, local: true do |f| = f.hidden_field :article_id = f.text_field :name = f.submit 18
  19. Chromeの開発者ツールで書き換え 19

  20. 他人の記事にタグをつけられる # POST /tags def create @tag = Tag.new(tag_params) if

    @tag.save flash[:notice] = "λάΛ࡞੒͠·ͨ͠ɻ" else flash[:notice] = "λάͷ࡞੒ʹࣦഊ͠·ͨ͠ɻ" end redirect_to article_path(@tag.article) end def tag_params params.require(:tag).permit(:article_id, :name) end 20
  21. この問題の直し方 • パラメータを検証する • ルーティング設計を見直す 21

  22. 修正案1. パラメータを検証する # POST /tags def create # ࣗ෼ͷهࣄҎ֎͸404ʹ͢Δɻ unless

    current_user.articles.exists?(id: tag_params[:article_id]) raise ActiveRecord::NotFound end @tag = Tag.new(tag_params) # ུ end def tag_params params.require(:tag).permit(:article_id, :name) end 22
  23. 修正案2. ルーティング設計を見直す # POST /articles/:article_id/tags def create @article = current_user.articles.find(params[:article_id])

    @tag = @article.tags.new(tag_params) # ུ end def tag_params params.require(:tag).permit(:name) end 23
  24. Administrateの脆弱性の事例8 8 https://github.com/thoughtbot/administrate/commit/3ab838b83c5f565fba50e0c6f66fe4517f98eed3 24

  25. directionを使った攻撃 GET /admin/users?direction="asc,10" で以下のようなSQLを実 行できる。 SELECT * FROM users ORDER

    BY users.created_at asc,10 カラム数より大きい値だとエラーになるため、攻撃者はテーブル のカラム数を特定できる。 25
  26. ! 個人情報(IPアドレスなど)の流出 26

  27. noteのIP流出事件の概要 • フロントエンドはVue.js, Nuxt.js • APIで記事情報のJSONを返す • 著者の情報を含む • last_sign_in_ip

    も含まれていた • Deviseを使っているかは真偽不明 27
  28. 情報流出しそうなコード 記事情報を返すAPIを実装したぞ! Railsなら2行で実装できて便利!! # GET /api/v1/articles/:id def show article =

    Article.find(params[:id]) render json: article.as_json(include: :user) # { # "id": 1, "title": "foo", ..., # "user": { "id: 1, "name": "bar", ... }, # } end 28
  29. (1年後...) ! 新しいアクセス元のときに警告を出そう 29

  30. アクセス元IPをDBに保存しよう # db/migrate/xxxx_add_last_sign_in_ip_to_users.rb def change add_column :users, :last_sign_in_ip, :string end

    30
  31. 情報流出するコード # GET /api/v1/articles/:id def show article = Article.find(params[:id]) render

    json: article.as_json(include: :user) end JSONに新しく追加したカラムが含まれてしまう。 31
  32. 修正する方法の例 # GET /api/v1/users/:id ARTICLE_ATTRIBUTES = %i[id title ...] USER_ATTRIBUTES

    = %i[id name ...] def show article = Article.find(params[:id]) json = article.as_json( only: ARTICLE_ATTRIBUTES, include: { user: { only: USER_ATTRIBUTES } } ) render json: json end 32
  33. jb9を使った例 # app/views/articles/show.json.jb user = @article.user { id: @article.id, title:

    @article.title, ..., user: { id: user.id, name: user.name, ..., } } 9 https://github.com/amatsuda/jb 33
  34. テストを書いておくと良い before { get "/api/v1/articles/#{article.id}" } it "ݸਓ৘ใΛؚ·ͳ͍ΧϥϜͷΈΛJSONͰฦ͢͜ͱ" do json

    = JSON.parse(response.body) expect(json).to contain_exactly("id", "title", ...) end 最近だとOpenAPI + committee10 で検証するのが良さそう。 10 https://github.com/interagent/committee 34
  35. 関連して... 35

  36. 秘匿情報をログに書き込まない Railsには秘匿値を[FILTERED]にする機能がある。 Started POST "/users" for ::1 at 2020-10-21 16:34:52

    +0900 Processing by UsersController#create as HTML Parameters: {"authenticity_token"=>"6pxi6...g==", \ "user"=>{"name"=>"sinsoku", "password"=>"[FILTERED]"}, "commit"=>"Create User"} ↳ app/controllers/users_controller.rb:30:in `block in create' ↳ app/controllers/users_controller.rb:30:in `block in create' ↳ app/controllers/users_controller.rb:30:in `block in create' Redirected to http://localhost:3000/users/1 Completed 302 Found in 9ms (ActiveRecord: 2.1ms | Allocations: 2968) 36
  37. ログに個人情報を書き込まない # config/initializers/filter_parameter_logging.rb # https://github.com/rails/rails/pull/34218 ͷ௥Ճ෼Λ൓өࡁΈɻ Rails.application.config.filter_parameters += [ :password,

    :secret, :token, :_key, :auth, :crypt, :salt, :certificate, :otp, :access, :private, :protected, :ssn ] 37
  38. 外部サービスに個人情報を送らない # config/initializers/sentry.rb Raven.configure do |config| config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) config.sanitize_http_headers

    = ["Via", "Referer", "User-Agent", "Server", "From"] end # config/initializers/rollbar.rb Rollbar.configure do |config| config.scrub_fields |= Rails.application.config.filter_parameters config.scrub_headers |= ["X-Access-Token"] end 38
  39. ! 認証に関わる仕様の事例 39

  40. ログイン失敗時のエラーメッセージ 40

  41. メールアドレスの存在確認ができる 41

  42. メールの存在確認による攻撃 • フィッシングメールの準備 • メールアドレスの持ち主の嗜好が分かる • ブルートフォース攻撃の準備 • パスワードスプレー攻撃の準備 42

  43. 修正方法 43

  44. ブルートフォース攻撃11 11 辞書攻撃 44

  45. ブルートフォース攻撃の対策 • ! 連続でログイン失敗したIPアドレスを弾く • IPアドレスは変えられるので効果は薄い • " 簡単なパスワードを禁止する •

    # ログインに連続で失敗したらアカウントをロックする 45
  46. redis-object12を使った認証失敗のカウント class User has_secure_password counter :login_fails, expiration: 1.day def authenticate(value)

    super.tap do |user_or_false| user_or_false ? login_fails.clear : login_fails.increment end end end 12 https://github.com/nateware/redis-objects 46
  47. パスワードスプレー攻撃 47

  48. パスワードスプレー攻撃の対策 • ! アカウントロックができない • 各アカウントで1回ずつしか失敗しない • " 簡単なパスワードを禁止する13 •

    # 多要素認証 13 1 Passwordの愛好者なので64文字まで許可して欲しい 48
  49. ! 未ログインで使えるフォーム 49

  50. お問い合わせフォームを使った攻撃の事例14 14 https://www.security-next.com/115212 50

  51. Slackがスパムで埋まる事例 51

  52. 対策: ハニーポット 不可視のフィールドを用意すると、botは入力してくる。 <form action="/contacts" method="post"> <input type="text" style="display: none;"

    name="contact[pot]" /> </form> def create head :ok if contact_params[:pot].present? contact = Contact.new(contact_params) # ...ུ 52
  53. 今日はなしたこと 1. 脆弱なコード • ! 未検証なparamsの使用 • ! 個人情報(IPアドレスなど)の流出 2.

    脆弱な仕様 • ! 認証に関わる仕様の事例 • ! 未ログインで使えるフォーム 53
  54. まとめ • ! 今日の話はセキュリティの一部 • Railsガイドを読もう " • セキュリティに関する書籍 "

    • # 脆弱な仕様を断るのもエンジニアの仕事 • $ セキュリティは難しい 54