Rubygems.org里达到远程代码执行目的方法教程

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

{C}          YQpLE0PCRKGCmSnsWr3p0PW000S56G5eQ91cv9oDpScPC8YyRIG18WOMmGD7
          /1X1AV+XPlQ='
实现末了一步操纵以后,咱们还要转头挪用#sort办法。在末了一步操纵中,咱们能够获得一个ActiveSupport::Cache::Entry对象。这个对象饰演着一个异常重要的脚色,因为当#value办法被挪历时,@compressed的值为true,而它将会挪用Marshal.load办法,并对入侵攻击者所供给的数据停止剖析处置。这也就意味着,它将卖力履行入侵攻击者所供给的代码。这里所应用的数据提取办法在以前已经先容过了,感兴趣的同窗能够参考【这篇文章】。可怜的是,咱们无奈应用YAML来对这个对象停止反序列化处置并实现代码履行,因为它几乎没有供给任何能够间接挪用的办法,包含设置实例变量的办法在内。在这类环境下,咱们必必要应用Marshal.load来实现对象的加载。
接下来,ActiveSupport::Cache::MemoryStore对象会在一个名叫@data的哈希变量中寄存咱们的歹意对象。在其父类ActiveSupport::Cache::Store中,界说了一个名叫#read的办法,这个办法能够在MemoryStore中挪用#read_entry办法。简略说来,#read_entry办法的感化便是从@data寄存的数据中提掏出entry并将其前往给挪用者。
针对MemoryStore#read办法的挪用来自于针对Gem::Package::TarReader::Entry#read的挪用,而它本身又会被Gem::Package::TarReader#each办法带哦用。当读取的成果前往给挪用者以后,#size办法将会在前往值中被挪用,并终极履行咱们的歹意Payload(歹意对象)。
末了,因为Gem::Package::TarReader界说了“include Enumerable”,挪用其#sort办法的对象还会挪用其#each办法,并开启上述的全体入侵攻击链。
总结
对我来讲,这一次的研讨让我深入地认识到了YAML其功能强大的地方。在将来,YAML.load办法极可能会被修改为只容许接管白名单中界说的类来看成可选参数了,而这也会让对繁杂对象的反序列化操纵变成为了一种可选行动。
就今朝的环境来看,YAML.load办法确切应当改名为YAML.unsafe_load之类的,如许一来宽大用户在应用这个办法的时刻就会晓得它其实是一种异常不网安的办法,而用户应当应用的是YAML.safe_load...:D
末了,感激rubygems.org的团队能够或许对我提交的网安申报予以疾速响应,假如他们没有设立如许一个高效的漏洞破绽bug嘉奖筹划的话,这是不可能办到的,这一点值得其余社区名目团队和大型企业进修。