Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

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

Takumi Shotoku

October 21, 2020
Tweet

More Decks by Takumi Shotoku

Other Decks in Technology

Transcript

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

    • GitHub: @sinsoku (画像右上) • Twitter: @sinsoku_listy (画像右下) • Rails歴: 8年くらい 2
  2. 15分では全てを話せない • セッションハイジャック • CSRF, XSS, CSP • オープンリダイレクト •

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

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

    脆弱な仕様 • ! 認証に関わる仕様の事例 • ! 未ログインで使えるフォーム 12
  5. テーブルの設計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
  6. コントローラー # 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
  7. 記事の詳細ページ # 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
  8. 他人の記事にタグをつけられる # 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
  9. 修正案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
  10. 修正案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
  11. directionを使った攻撃 GET /admin/users?direction="asc,10" で以下のようなSQLを実 行できる。 SELECT * FROM users ORDER

    BY users.created_at asc,10 カラム数より大きい値だとエラーになるため、攻撃者はテーブル のカラム数を特定できる。 25
  12. 情報流出しそうなコード 記事情報を返す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
  13. 情報流出するコード # GET /api/v1/articles/:id def show article = Article.find(params[:id]) render

    json: article.as_json(include: :user) end JSONに新しく追加したカラムが含まれてしまう。 31
  14. 修正する方法の例 # 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
  15. 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
  16. テストを書いておくと良い 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
  17. 秘匿情報をログに書き込まない 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
  18. 外部サービスに個人情報を送らない # 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
  19. 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
  20. 対策: ハニーポット 不可視のフィールドを用意すると、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
  21. 今日はなしたこと 1. 脆弱なコード • ! 未検証なparamsの使用 • ! 個人情報(IPアドレスなど)の流出 2.

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

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