Ruby语言存在反序列化漏洞导致Ruby 2.x任意命令执行
本文主要讲解Ruby编程语言任意反序列化的漏洞利用,同时发布了一个公共通用的工具链,从而实现Ruby 2.x的任意命令执行。首先将详细说明Ruby语言中存在的反序列化问题,然后讲解Gadget链的发现过程,最后描述漏洞利用的方式。
背景
序列化是将对象转换为一系列字节的过程,随后就可以将其通过网络传输,或者是存储在文件系统或数据库中。这些字节中包含重建原始对象所需的所有相关信息。而这种重建的过程,就被称为反序列化。每种编程语言都有其独特的序列化格式,也有一些编程语言不将这一过程称为序列化和反序列化。在Ruby中,该过程通常被称为编组(Marshalling)和解组(Unmarshalling)。
Marshal类中包含类方法“dump”和“load”,其使用方式如下:
$ irb
>> class Person
>> attr_accessor :name
>> end
=> nil
>> p = Person.new
=> #
>> p.name = "Luke Jahnke"
=> "Luke Jahnke"
>> p
=> #
>> Marshal.dump(p)
=> "\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET"
>> Marshal.load("\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET")
=> #
不受信任数据反序列化问题
尽管序列化对象是以不透明的二进制格式进行传输,但如果开发人员错误的认为攻击者无法查看或篡改序列化对象,就会出现常见的安全漏洞。这样一来,就可能导致存储在对象内的任何敏感信息(例如凭据、应用程序密钥)被泄露给攻击者。如果序列化对象中包含随后用于权限检查的实例变量,那么攻击者可能会利用这一漏洞实现权限提升。例如,有一个包含用户名实例变量的User对象,该对象经过序列化后,可能被攻击者篡改。攻击者可以轻而易举地修改序列化数据,并将username变量更改为较高权限用户的用户名,例如admin。尽管这些攻击方式非常强大,但它们具有高度上下文敏感性,并且从技术角度来看可能不会引起注意,所以我们在本文中不对其展开讨论。
此外,代码重用攻击也有可能发生,我们可以通过执行一些已知的可用代码(被称为Gadget),从而实现诸如任意命令执行这样的操作。由于反序列化可以将实例变量设置为任意值,因此攻击者可以控制Gadget去操纵某些数据。这样一来,也允许攻击者使用Gadget来调用第二个Gadget,因为存在方法能够调用存储在实例变量中的对象。当一系列Gadget以这种方式连接在一起时,就被称为一个Gadget链。
此前已有的Payload
不安全的反序列化是2017年OWASP十大最严重Web应用程序安全风险的第8名,但是,目前关于为Ruby构建Gadget链的技术细节还非常有限。我们可以在Phrack的文章《Attacking Ruby on Rails Applications》中得到非常多的参考,其中Phenoelit的joernchen在2.1节中描述了Charlie Somerville发现的Gadget链,它实现了任意代码执行。为简洁起见,我们不再详述该技术,只提出其先决条件如下:
1、必须安装并加载ActiveSupport gem;
2、必须加载标准库中的ERB(默认情况下,Ruby不会加载);
3、反序列化后,必须在反序列化对象上调用不存在的方法。
尽管这些先决条件会在绝大多数Ruby on Rails Web应用程序的上下文中实现,但实际上其他Ruby应用程序很少能够实现这些先决条件。
因此,我们不再使用这个Payload,试图跳脱出这些先决条件,寻找仍然可以实现任意代码执行的方法。
寻找Gadget
由于我们希望制造一个没有依赖关系的Gadget,因此Gadget只能从标准库中获取。需要注意的是,并非所有的标准库都会被默认加载,这也极大地限制了我们拥有的Gadget数量。举例来说,我们在测试的Ruby 2.5.3中,发现默认加载了358个类,尽管数量很多,但经过仔细观察后发现,其中有196个类没有定义任何自己的实例方法。这些空类中的大多数都是Exception类的唯一命名子类,用于区分可以捕获到的异常。
只拥有有限数量的可用类,我们就能更加轻松的找到用于增加已加载标准库数量的Gadget或技术。其中的一种技术就是,当使用require调用另一个库时,寻找相应的Gadget。这个过程非常有用,因为即使require在某个模块和(或)类的范围内,它实际上也会污染全局命名空间。
调用require(lib/rubygems.rb)的方法示例:
module Gem
...
def self.deflate(data)
require 'zlib'
Zlib::Deflate.deflate data
end
...
End
如果上面的Gem.deflate方法包含在Gadget链中,那么就会加载Ruby标准库中的Zlib库,如下所示。
被污染的全局命名空间示例:
$ irb
>> Zlib
NameError: uninitialized constant Zlib
...
>> Gem.deflate("")
=> "x\x9C\x03\x00\x00\x00\x00\x01"
>> Zlib
=> Zlib
尽管标准库中存在大量动态加载标准库其他部分的情况,但如果已经在系统上安装了第三方库,那么就会发现有一个实例正在尝试加载第三方库。
标准库SortedSet加载第三方RBTree库(lib/set.rb):
...
class SortedSet
在请求未安装的库(包括其他库目录)时,将会对众多位置进行搜索。当Ruby尝试在没有安装RBTree的默认系统上加载RBTree时,来自strace的输出内容如下:
$ strace -f ruby -e 'require "set"; SortedSet.setup' |& grep -i rbtree | nl
1 [pid 32] openat(AT_FDCWD, "/usr/share/rubygems-integration/all/gems/did_you_mean-1.2.0/lib/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
2 [pid 32] openat(AT_FDCWD, "/usr/local/lib/site_ruby/2.5.0/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
本文主要讲解Ruby编程语言任意反序列化的漏洞利用,同时发布了一个公共通用的工具链,从而实现Ruby 2.x的任意命令执行。首先将详细说明Ruby语言中存在的反序列化问题,然后讲解Gadget链的发现过程,最后描述漏洞利用的方式。
背景
序列化是将对象转换为一系列字节的过程,随后就可以将其通过网络传输,或者是存储在文件系统或数据库中。这些字节中包含重建原始对象所需的所有相关信息。而这种重建的过程,就被称为反序列化。每种编程语言都有其独特的序列化格式,也有一些编程语言不将这一过程称为序列化和反序列化。在Ruby中,该过程通常被称为编组(Marshalling)和解组(Unmarshalling)。
Marshal类中包含类方法“dump”和“load”,其使用方式如下:
$ irb
>> class Person
>> attr_accessor :name
>> end
=> nil
>> p = Person.new
=> #
>> p.name = "Luke Jahnke"
=> "Luke Jahnke"
>> p
=> #
无奈人生安全网
>> Marshal.dump(p)
=> "\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET"
>> Marshal.load("\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET")
=> #
不受信任数据反序列化问题
尽管序列化对象是以不透明的二进制格式进行传输,但如果开发人员错误的认为攻击者无法查看或篡改序列化对象,就会出现常见的安全漏洞。这样一来,就可能导致存储在对象内的任何敏感信息(例如凭据、应用程序密钥)被泄露给攻击者。如果序列化对象中包含随后用于权限检查的实例变量,那么攻击者可能会利用这一漏洞实现权限提升。例如,有一个包含用户名实例变量的User对象,该对象经过序列化后,可能被攻击者篡改。攻击者可以轻而易举地修改序列化数据,并将username变量更改为较高权限用户的用户名,例如admin。尽管这些攻击方式非常强大,但它们具有高度上下文敏感性,并且从技术角度来看可能不会引起注意,所以我们在本文中不对其展开讨论。
此外,代码重用攻击也有可能发生,我们可以通过执行一些已知的可用代码(被称为Gadget),从而实现诸如任意命令执行这样的操作。由于反序列化可以将实例变量设置为任意值,因此攻击者可以控制Gadget去操纵某些数据。这样一来,也允许攻击者使用Gadget来调用第二个Gadget,因为存在方法能够调用存储在实例变量中的对象。当一系列Gadget以这种方式连接在一起时,就被称为一个Gadget链。 copyright 无奈人生
此前已有的Payload
不安全的反序列化是2017年OWASP十大最严重Web应用程序安全风险的第8名,但是,目前关于为Ruby构建Gadget链的技术细节还非常有限。我们可以在Phrack的文章《Attacking Ruby on Rails Applications》中得到非常多的参考,其中Phenoelit的joernchen在2.1节中描述了Charlie Somerville发现的Gadget链,它实现了任意代码执行。为简洁起见,我们不再详述该技术,只提出其先决条件如下:
1、必须安装并加载ActiveSupport gem;
2、必须加载标准库中的ERB(默认情况下,Ruby不会加载);
3、反序列化后,必须在反序列化对象上调用不存在的方法。
尽管这些先决条件会在绝大多数Ruby on Rails Web应用程序的上下文中实现,但实际上其他Ruby应用程序很少能够实现这些先决条件。
因此,我们不再使用这个Payload,试图跳脱出这些先决条件,寻找仍然可以实现任意代码执行的方法。
寻找Gadget
由于我们希望制造一个没有依赖关系的Gadget,因此Gadget只能从标准库中获取。需要注意的是,并非所有的标准库都会被默认加载,这也极大地限制了我们拥有的Gadget数量。举例来说,我们在测试的Ruby 2.5.3中,发现默认加载了358个类,尽管数量很多,但经过仔细观察后发现,其中有196个类没有定义任何自己的实例方法。这些空类中的大多数都是Exception类的唯一命名子类,用于区分可以捕获到的异常。
只拥有有限数量的可用类,我们就能更加轻松的找到用于增加已加载标准库数量的Gadget或技术。其中的一种技术就是,当使用require调用另一个库时,寻找相应的Gadget。这个过程非常有用,因为即使require在某个模块和(或)类的范围内,它实际上也会污染全局命名空间。
调用require(lib/rubygems.rb)的方法示例:
module Gem
...
def self.deflate(data)
require 'zlib'
Zlib::Deflate.deflate data
end
...
End
如果上面的Gem.deflate方法包含在Gadget链中,那么就会加载Ruby标准库中的Zlib库,如下所示。
被污染的全局命名空间示例:
$ irb
>> Zlib
NameError: uninitialized constant Zlib
...
>> Gem.deflate("")
=> "x\x9C\x03\x00\x00\x00\x00\x01"
>> Zlib
=> Zlib
尽管标准库中存在大量动态加载标准库其他部分的情况,但如果已经在系统上安装了第三方库,那么就会发现有一个实例正在尝试加载第三方库。
标准库SortedSet加载第三方RBTree库(lib/set.rb): 无奈人生安全网
...
class SortedSet
在请求未安装的库(包括其他库目录)时,将会对众多位置进行搜索。当Ruby尝试在没有安装RBTree的默认系统上加载RBTree时,来自strace的输出内容如下:
$ strace -f ruby -e 'require "set"; SortedSet.setup' |& grep -i rbtree | nl
1 [pid 32] openat(AT_FDCWD, "/usr/share/rubygems-integration/all/gems/did_you_mean-1.2.0/lib/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
2 [pid 32] openat(AT_FDCWD, "/usr/local/lib/site_ruby/2.5.0/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
无奈人生安全网