浅析Ruby类污染及其在Sinatra框架下的利用
和JavaScript中的原型链污染类似,Ruby中也存在类似的概念——类污染,两者都是对象进行不安全的递归合并导致的。
网上也没有相关的分析文章,只有下面这篇文章应该是第一次谈到这个问题
Class Pollution in Ruby: A Deep Dive into Exploiting Recursive Merges · Doyensec’s Blog
刚打完的长城杯,ezruby一题应该就是基于这篇文章的进一步利用。
Ruby类介绍
在Ruby中也是万物皆对象,下面定义一个Person类
1 | class Person |
类变量(类似Java中类的静态变量)使用 @@ 前缀,实例变量使用@前缀,在类内部才用这种前缀来访问。
冒号前缀表示符号(Symbol)。Ruby中符号是轻量级的、不可变的字符串,通常用于表示标识符、方法名或键。符号的优点是它们在内存中只存储一次,因此在需要频繁比较或使用相同字符串的情况下,使用符号可以提高性能。
Ruby对象的一些特殊的方法:
attr_accessor:定义实例变量的getter和setter方法,用于在类外部访问实例变量initialize:类的构造方法to_s:toString方法inspect:和to_s差不多,常用于debugmethod_missing:类似PHP的__call__方法,当调用一个不存在的方法时会触发respond_to?:检测对象是否有某个方法或属性send:根据方法名来调用(包括私有方法)public_send:根据方法名调用公开方法
Ruby对象的一些特殊的属性(类也算对象)
class:当前对象的类superclass:父类subclasses:子类数组instance_variables:实例变量名的数组class_variables:类变量名的数组
当然不止这些,具体就不展开了。
在 Ruby 中,所有类的顶层父类是 BasicObject。BasicObject 是 Ruby 类层次结构中的根类,所有其他类都直接或间接地继承自它。
1 | class MyClass |
在实际污染中,用到的就是class、superclass、subclasses,先从当前对象找到当前类,回溯到父类Object,锁定要污染的变量所在的类,在从父类一层层找子类。
不安全的递归合并
Doyensec的文章中给出下面的merge函数,也介绍了两个实际案例,分别是Ruby on Rails的内置组件ActiveSupport提供的``deep_merge,以及Hashie库提供的deep_merge`,感兴趣可以看原文。
1 | def recursive_merge(original, additional, current_obj = original) |
recursive_merge用于递归地合并两个对象original和additional
- 遍历
additional对象中的每个键值对。 - 处理嵌套的哈希:如果值是一个哈希,它会检查
current_obj(初始为original)是否响应该键。如果响应,则递归合并嵌套的哈希。如果不响应,则创建一个新对象,将其设置为实例变量,并为其创建访问器。 - 处理非哈希值:如果值不是哈希,则直接在
current_obj上设置该值为实例变量,并为其创建访问器。
下面举个例子
污染当前对象
1 | class A |
若能污染protected_methods,其返回值就能传入instance_eval进行代码执行。
1 | a = A.new(1) |
当然这种污染的是当前的对象的属性,不影响父类以及其他实例对象。
污染父类
1 | class Base |
对于这种情况,可以污染父类的cmd变量,但这里实际上是给父类Base增加了一个实例变量@cmd,从而通过Base.cmd访问时,实例变量@cmd遮盖了类变量@@cmd
1 | c = Cmder.new |
污染其他类
1 | class Cmder |
能否通过Innocent污染到Cmder呢?
subclasses可以获取到Object的子类,但返回的是数组,可以利用数组的sample方法,随机返回一个元素
通过多次污染,总有几率污染到Cmder这个子类
1 | 1000.times do |
Doyensec的文章中提到了污染Person的url变量来进行SSRF,以及污染KeySigner的signing_key变量来实现伪造签名数据。但我们追求的是通过类污染来实现RCE。
注意文章中给出的情景,使用的是Sinatra这个web框架,能否污染框架中的关键变量来实现RCE或者文件读取之类的操作呢。
类污染设置静态目录
Sinatra框架中是通过如下配置来设置静态目录的
1 | set :public_folder, File.dirname(__FILE__) + '/static' |
跟进set方法可以发现他实际就是给Sinatra::Base设置了一个属性的getter、setter
class_eval给类动态定义方法
因此可以污染public_folder这个属性来修改静态目录
1 | {"class":{"superclass":{"superclass":{"subclasses":{"sample":{"public_folder": "E:/Server"}}}}}} |
类污染写ERB模板
调试可知Sinatra::Base有个templates属性,类型是哈希,猜测他是存放模板的
Sinatra 默认模板位于./views目录,也支持通过如下语句定义模板
1 | template :index do |
可以看到template方法实际就是给templates这个属性赋值(block是个代码块,需要返回模板内容的字符串)
有如下渲染模板的路由
1 | get('/') do |
ERB (Embedded Ruby) 是 Ruby 标准库自带的,它允许在文本文件中嵌入 Ruby 代码,通常用于生成 HTML 文件,就是一个模板引擎。
可以污染templates属性,覆盖hello模板,通过ERB模板实现RCE
1 | {"class":{"superclass":{"superclass":{"subclasses":{"sample":{"templates": {"hello": "<%= `calc` %>"}}}}}}} |
参考文章
Class Pollution in Ruby: A Deep Dive into Exploiting Recursive Merges · Doyensec’s Blog



