Oj库反序列化攻击面分析

2022-12-08 04:32吴红
网络安全技术与应用 2022年3期
关键词:序列化字符串调用

◆吴红

Oj库反序列化攻击面分析

◆吴红

(国网眉山供电公司 四川 620010)

Oj是一个快速的JSON解析Ruby库,由于它极其高效且易用,根据Github统计,有上万项目以其作为依赖项。Oj库默认使用一个类型系统来解析Ruby对象,这允许应用代码在反序列化JSON字符串时恢复原始的对象,但这种能力可能导致安全漏洞,如远程代码执行或拒绝服务。本文将分析Oj库反序列化时可能造成的漏洞,并给出消除此类漏洞的建议。

Oj;反序列化漏洞;类型系统

Oj(https://github.com/ohler55/oj)是一个快速的JSON解析及对象序列化器。它可以序列化绝大多数的Ruby对象,在反序列化时则解析JSON字符串中约定的格式来恢复对象信息。不安全的反序列化作为OWASP TOP10中的一种通用漏洞类型[1],在各类编程语言中广泛存在,近年来在Java应用中尤其严重,一些知名Java组件,如FastJSON[2]、XStream等频频爆出反序列化漏洞,这类漏洞特点是利用简单、危害巨大,通常能造成远程代码执行。由此看来,Oj提供的反序列化对象的能力同样是一种攻击面,值得深入研究。

1 Oj用例分析

Oj提供两个核心API用户序列化与反序列化过程:

(1)Oj.load(json,options={}){ ... }

(2)Oj.dump(obj,options={})

Oj.load方法用于反序列化一个字符串,返回类型取决于具体配置。可以全局配置Oj选项,存储在Oj.default_options变量中。我们仅关注反序列化时可能造成安全风险的一些配置:

(1)auto_define

(2)mode

(3)create_additions与create_id

auto_define选项指定反序列化时类名不存在时是否进行自动定义;mode选项指定Oj模式;create_additions与create_id选项为一组,指定特定情况下反序列化对象的键名,之后会详细说明。

Oj.dump方法用于将一个Ruby对象序列化为JSON字符串。

1.1 模式

Oj当前支持6种模式[3]:strict、null、compat、rails、object、custom,我们对object模式感兴趣,这也是默认的模式,因为该模式允许将Ruby对象进行序列化与反序列化。当使用object模式时,Oj遵循一组特定的编码来确定JSON字符串中哪些部分需要重建为对象或类。具体规则如下:

(1)原生JSON类型,true,false,nil,String,Hash,Array,Numbers正常编码。

(2)Symbol类型编码为以“:”开头的字符串。

(3)以“^”开头的键名表示这是一个特殊的键。

(4)以':','^i'或'^r开头的字符串,将这三种前缀编码为unicode字符串形式。

(5)一个"^c "的JSON对象键表示该值应该被转换为一个Ruby类。

(6)一个"^t"的JSON对象键表示该值应该被转换为Ruby Time。

(7)一个"^o"的JSON对象键表示该值应该被转换为Ruby对象。JSON对象中的第一个条目必须是一个带有"^o"键的类。在这之后,每个条目都被视为Object的一个变量,其中的键是没有前面的'@'的变量名。

(8)一个"^u"JSON对象的键表示该值应该被转换为Ruby Struct。JSON对象中的第一个条目必须是一个带有"^u"键的类。之后,每个条目在结构中被赋予一个数字位置,并被用作JSON对象的键。

(9)当对一个对象进行编码时,如果变量名称不是以'@'字符开始,那么名称前面会有一个'~'字符。

(10)如果一个Hash条目有一个不是字符串或符号的键,那么该条目将被编码为"^#n"形式的键,其中n是一个十六进制数字。值是一个数组,其中第一个元素是Hash中的键,第二个元素是值。

(11)在一个对象或数组中的"^i"JSON条目是被编码的Ruby对象的ID。当circular标志被设置时被使用。它可以出现在一个JSON对象或JSON数组中。在一个对象中,"^i"键有一个相应的引用。在一个数组中,该序列将包括一个嵌入的引用号。

(12)一个对象中的"^r"JSON条目是对已经出现在JSON字符串中的一个对象或数组的引用。它必须与之前的"^i"引用号相匹配。

(13)如果一个数组元素是一个字符串,并且以"^i"开头,那么第一个字符'^'被编码为一个十六进制字符序列。

下面展示一些实例。定义类A如下:

class A

def initialize(name)

@count = 1

@name = name

end

end

使用Oj序列化A的实例:Oj.dump(A.new(“John”)),结果如下:

{"^o":"A","count":1,"name":"John"}

对于这一结果,“^o”键的值为A,表示类A的实例,后续的count及name键代表其实例变量名,值为实例变量的值。使用Oj.load方法反序列化:

Oj.load %Q({"^o":"A","count":1,"name":"John"})

=> #

获得A类的新实例,对应的实例变量已被设置。

2 攻击面分析

2.1 拒绝服务攻击(内存耗尽)

在第1小节中提到auto_define配置,该配置设置为true时,Oj解析到未定义的类时,将自动定义该类。例如,执行下列代码后,对象命名空间中将存在类NotDefined:

Oj.load %Q({"^c":"NotDefined"}),auto_define:true

我们可以模拟攻击者大量请求反序列化不存在的类:

10000000000.times { Oj.load %Q({"^c":"A#{SecureRandom.hex(4)}"}),auto_define:true }

将观察到Ruby进程内存占用显著增长。

2.2 拒绝服务攻击(内存损坏)

尽管Oj默认允许反序列化几乎任意类型,但它与FastJSON等库的显著区别在于反序列化过程中对象的方法不会被调用,例如实例化方法initialize及其他钩子方法,这在一定程度上提供了安全性,至少不太可能在反序列化过程中被利用。

由于Oj代码库比较庞大,为了研究反序列化时的行为,我们可以先从结果入手。考虑下列代码,尝试反序列化一个Proc对象的实例:

Oj.load %Q({"^o":"Proc"})

这将导致抛出TypeError:allocator undefined for Proc。这与调用allocate方法[4]的行为一致。类或模块的allocate方法将分配一个对象实例但不调用initialize方法,这很好地避免了initialize方法在反序列化时被滥用,而限制在于并非所有的类都支持allocate。默认情况下Ruby提供allocate方法实现,对于C扩展可以使用rb_define_alloc_func函数注册自己的allocate方法,或使用rb_undef_alloc_func禁用allocate方法。

尽管调用allocate可以避免调用初始化方法,但这种行为可能导致其他问题。对于完全由运行时托管的对象,仅调用allocate虽可能产生未定义的行为,例如由于某些变量未初始化而导致抛出异常,但这种异常可以被捕获并处理,并不会导致进程崩溃;然而如果某一对象的数据操作交由原生代码处理,例如直接访问未初始化地址,则可能导致内存损坏。一个例子是Ruby3.0.0中Ractor类存在的Bug。Ractor是Ruby3中新增的Actor模型的实现,它几乎完全使用C代码实现。在3.0.0版本中Ractor允许调用allocate方法并返回一个实例,但这将导致一些必要的初始化操作被跳过,并在某一时刻产生对进程地址的非法读写,最终使程序崩溃。

虽然这并不是Oj的责任,但如果Oj反序列化一个Ractor对象,则将导致返回一个仅调用allocate方法的对象,调用它的多数方法都将导致进程崩溃进而拒绝服务,漏洞概念证明如下:

Oj.load(%Q({"^o":"Ractor"})).whatever

2.3 远程代码执行

当反序列化一个对象时,对象的实例变量将设置为攻击者可控制的值。获取到返回的对象后,应用代码可能调用该对象上的任意方法,此时会产生类型混淆,例如应用代码期望获得一个Hash或Array对象,但实际上是一个String对象。一个String对象也许没有什么危险,但攻击者可以构造一条Gadget链,通过操作对象实例变量的值,从应用代码的一次非预期调用开始执行到进行危险操作的方法。一个著名的例子是ActiveSupport中的DeprecatedInstanceVariableProxy[5]类,该类代码大体如下:

class DeprecatedInstanceVariableProxy < DeprecationProxy

def initialize(instance, method, ...)

@instance = instance

@method = method

end

private

def target

@instance.__send__(@method)

end

end

该类继承DeprecationProxy类,DeprecationProxy定义了method_missing钩子方法,该方法在调用对象上未定义的方法时被调用:

def method_missing(...)

...

target.__send__(...)

end

对于这个Gadget,@instance与@method变量可控的情况下,在返回对象上调用任意不存在的方法(很容易满足条件,因为这个类本身就没有实现太多方法),将导致父类DeprecationProxy上的method_missing方法被调用,而该方法实现上又调用target方法,此时@instance对象的@method方法被执行。@instance对象可以使用ERB对象,@method则为result方法,ERB对象设置其src实例变量为需要执行的代码,result方法被调用时将会执行,从而实现远程代码执行。

3 如何防御此类漏洞

经过分析,产生反序列化漏洞的根源在于使用不安全输入创建任意对象。Oj本身在其文档中已指明这一点,并提供了非常简单的解决方案:不使用object模式。如果使用compat模式,且开启了create_additions选项,则还需要审计所有类中的json_create方法是否存在代码执行的可能。

4 结束语

本文介绍了Oj库反序列化攻击面中的不同漏洞类型,并给出防御解决方案。随着近年攻防演练持续升温,反序列化漏洞也越来越受关注,利用简单、危害巨大的特点使其在未来将被更多安全研究者进一步研究、挖掘、利用。

[1]A8:2017-Insecure Deserialization[EB/OL].https://owasp.org/www-project-top-ten/2017/A8_2017-Insecure_Deserialization,2017-12.

[2]Fastjson 反序列化漏洞史[EB/OL].https://paper. seebug. org/1192/,2020-05-08.

[3]Oj Modes[EB/OL].https://github.com/ohler55/oj/blob/ develop/pages/Modes.md,2021-08-03.

[4]method-i-allocate[EB/OL].https://ruby-doc.org/core-2.5.0/Class.html#method-i-allocate,2020-05.

[5]DeprecatedInstanceVariableProxy[EB/OL].https://github.com/rails/rails/blob/18707ab17fa492eb25ad2e8f9818a320dc20b823/activesupport/lib/active_support/deprecation/proxy_wrappers.rb#L88,2021-07-30.

猜你喜欢
序列化字符串调用
Java Web反序列化网络安全漏洞分析
基于文本挖掘的语词典研究
如何建构序列化阅读教学
某物资管理调度系统的数据序列化技术
核电项目物项调用管理的应用研究
系统虚拟化环境下客户机系统调用信息捕获与分析①
SQL server 2008中的常见的字符串处理函数
倍增法之后缀数组解决重复子串的问题
Java 反序列化漏洞研究
最简单的排序算法(续)