- プロダクトマネージャー
- Webエンジニア
- カスタマーサポート
- 他59件の職種
- 開発
-
ビジネス
- プロダクトマネージャー
- プロダクトマネージャー
- サステナビリティ
- 広報
- カルチャー推進・浸透
- 知財戦略立案・推進・発明発掘
- リスクマネジメント統括本部
- AML/CFTコンプライアンス
- AML・金融犯罪対策Ops
- 金融コンプライアンス
- ビジネス採用担当
- 経営企画(予実・IR)
- HRBP
- 法務
- 債権管理/MFK
- 法人営業
- インサイドセールス
- フィールドセールス
- インサイドセールス SDR
- インサイドセールス企画
- オンラインセールス
- SaaS営業、MFBC
- インサイドセールス MFBC
- セールス MFBC
- マーケティングリサーチャー
- マーケター
- データマーケター
- BtoBマーケティングリーダー
- CRMスペシャリスト
- イベントマーケター
- その他
こんにちは、金子です。
普段はRailsを書いたりしています。
今回は2016/4/6に発表された、RubyGems.orgの脆弱性についてまとめました。
脆弱性について
RubyGems.org gem replacement vulnerability and mitigation をざっと読んでみると、
特定の状況で、RubyGems.orgにupdateされているファイルの内容が不正に書き換えられる可能性があった特定の状況とは、2014-6-11から2016-4-2までの間に登録されたgemのうち、’blank-blank’のように名前に'-' (dash)が入っているものただし2015-2-8以降に登録されたgemはRubyGems.orgがsha256 checksumを計算しており、それと実際のファイルの突合をして、書き換えられていないことを確認ずみ
つまり、2014-6-11から2015-2-7の間に登録されたgemが、今回の脆弱性の範囲にあたるということです。
自分が使っているgemのうち、影響のありそうなものを調べる
自分の使っているgemの一覧を取得する
例えばRailsでアプリケーションの開発を行っており、bundlerでgemの管理をしているようなプロジェクトでは、Gemfile.lockに現在使用しているgemの名前とversionが記録されています。
例えば以下のようなコードで、名前に'-'が入っているgemの一覧が取得できます。
lockfile = Bundler::LockfileParser.new(Bundler.read_file("path_to_Gemfile.lock"))
gems = lockfile.specs.select {|s| s.name =~ /-/}.map {|s| [s.name, s.version.to_s]}
gemの名前とversionから登録日時を調べる
RubyGems.org Data Dumps からRubyGems.orgのPostgreSQLのデータが入手できるので、こちら を参考に、自分の環境にDBを作成します。
直接DBを操作してもいいのですが、今回はRailsでラップしてみます。
rails _4.2.6_ new -d postgresql rubygems などで新規にプロジェクトを作成し、
app/models/rubygem.rbとapp/models/version.rbを作成します。
# app/models/rubygem.rb
class Rubygem < ActiveRecord::Base
has_many :versions
end
# app/models/version.rb
class Version < ActiveRecord::Base
belongs_to :rubygem
end
またデフォルトではrubygemsという名前のdatabaseにデータが入るので、config/database.ymlも調整します。
# config/database.yml
development:
<<: *default
database: rubygems
準備ができたら以下のようなスクリプトを実行します。gem listの内容をもとにチェックしたいというケースもあるかと思いますので、GemVersionというクラスも用意しました。
class DangerousGemValidator
def initialize
refresh
end
def invalid_gems(gems)
_invalid_gems(gems).map do |name, number, rubygem, version|
[name, number, version.created_at.strftime('%Y-%m-%d %H:%M:%S')]
end
end
def excepted_records
{
rubygems_has_no_records: @rubygems_has_no_records.sort.uniq,
rubygems_has_multiple_records: @rubygems_has_multiple_records.sort.uniq,
versions_has_not_just_one_records: @versions_has_not_just_one_records.sort.uniq
}
end
private
def _invalid_gems(gems)
return @invalid_gems if @invalid_gems
@invalid_gems = gems.each_with_object([]) do |(name, number), array|
rubygems = Rubygem.where(name: name)
if rubygems.count == 0
@rubygems_has_no_records << "#{name} has no record"
next
end
if rubygems.count != 1
@rubygems_has_multiple_records << "#{name} has #{rubygems.count} record(s)"
next
end
rubygem = rubygems.first
versions = rubygem.versions.where(number: number)
case
when versions.count == 1
version = versions.first
when versions.where(platform: 'ruby').exists?
version = versions.find_by(platform: 'ruby')
else
@versions_has_not_just_one_records << "#{name} has #{versions.count} versions record(s) (number: #{number}, rubygem.id: #{rubygem.id})"
next
end
if (Time.utc(2014, 6, 11) <= version.created_at) && (version.created_at <= Time.utc(2015, 2, 18))
array << [name, number, rubygem, version]
end
end
end
def refresh
@invalid_gems = nil
@rubygems_has_no_records = []
@rubygems_has_multiple_records = []
@versions_has_not_just_one_records = []
end
end
class GemVersion
def initialize(file_path)
@file_path = file_path
end
def gems
File.read(@file_path).each_line.each_with_object([]) do |line, array|
line =~ /([\w-]*) \((.*)\)/
gem_name = $1
versions = $2.split(', ')
next if gem_name !~ /-/
array.concat versions.map {|v| [gem_name, v]}
end
end
end
gem_version = GemVersion.new("path_to_gem_list_text")
validator = DangerousGemValidator.new
# gem listをもとに検索する
validator.invalid_gems(gem_version.gems)
# or Gemfile.lockをもとに検索する
lockfile = Bundler::LockfileParser.new(Bundler.read_file("path_to_Gemfile.lock"))
gems = lockfile.specs.select {|s| s.name =~ /-/}.map {|s| [s.name, s.version.to_s]}
validator.invalid_gems(gems)
ためしに、今つくったRailsアプリ(rubygemsアプリのこと、Rails 4.2.6の標準設定)のGemfile.lockに対して実行してみます。
lockfile = Bundler::LockfileParser.new(Bundler.read_file(File.join(Rails.root, '/Gemfile.lock')))
gems = lockfile.specs.select {|s| s.name =~ /-/}.map {|s| [s.name, s.version.to_s]}
validator.invalid_gems(gems)
=> [["rack-test", "0.6.3", "2015-01-09 17:58:13"], ["rails-deprecated_sanitizer", "1.0.3", "2014-09-25 16:33:44"]]
おや、rails-deprecated_sanitizerがひっかかりました。rubygems.org をみてみると、確かに。。。
gemの内容を確認する
RubyGems.org gem replacement vulnerability and mitigation の”WHAT SHOULD I DO?”をもとに、rails-deprecated_sanitizerの1.0.3を確認してみましょう。
まずはgemをインストールして、unpackします。
$ gem install rails-deprecated_sanitizer -v 1.0.3
Successfully installed rails-deprecated_sanitizer-1.0.3
1 gem installed
$ gem unpack rails-deprecated_sanitizer -v 1.0.3
Unpacked gem: '/.../rails-deprecated_sanitizer-1.0.3'
次に、コードをダウンロードし、リリース時のコミットをチェックアウトします。
$ git clone git@github.com:rails/rails-deprecated_sanitizer.git
$ cd rails-deprecated_sanitizer
$ git checkout v1.0.3
diffを取ります。
$ diff -r rails-deprecated_sanitizer-1.0.3/ rails-deprecated_sanitizer
Only in rails-deprecated_sanitizer: .git
Only in rails-deprecated_sanitizer: .gitignore
Only in rails-deprecated_sanitizer: .travis.yml
Only in rails-deprecated_sanitizer: Gemfile
Only in rails-deprecated_sanitizer: LICENSE.txt
Only in rails-deprecated_sanitizer: Rakefile
Only in rails-deprecated_sanitizer: rails-deprecated_sanitizer.gemspec
rails-deprecated_sanitizerにしかないファイルが複数ありますが、これはrails-deprecated_sanitizer.gemspecの設定で、パッケージに含めていないものなので、問題なさそうです。
まとめ
マネーフォワードの本番環境で使用している全gemを対象に上記の絞り込みを行い、差分の確認をし、全gemに問題がないことを確かめました。
最後に
マネーフォワードでは、RubyやRailsをいじり倒すのが大好きというエンジニアを募集しています。
ご応募お待ちしています。
【採用サイト】
■マネーフォワード採用サイト
■Wantedly | マネーフォワード
【プロダクト一覧】
■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』
■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad
■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android
■クラウド型会計ソフト『MFクラウド会計』
■クラウド型請求書管理ソフト『MFクラウド請求書』
■クラウド型給与計算ソフト『MFクラウド給与』
■経費精算システム『MFクラウド経費』
■消込ソフト・システム『MFクラウド消込』
■マイナンバー対応『MFクラウドマイナンバー』
■創業支援トータルサービス『MFクラウド創業支援サービス』
■お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』