攻击者是否可以通过发送 JSON 在远程服务器上执行任意命令?是的,如果正在运行的代码包含不安全的反序列化漏洞。但这怎么可能呢?
在这篇博文中,我们将描述不安全反序列化漏洞的工作原理以及如何在 Ruby 项目中检测它们。这篇博文中的所有示例都是使用 Ruby 的 Oj JSON 序列化库制作的,但这并不意味着它们仅限于此库。在这篇博文的最后,我们将链接到一个存储库,其中包含适用于 Oj (JSON)、Ox (XML)、Psych (YAML) 和 Marshal (自定义二进制格式) 的有效示例漏洞,并向您展示 CodeQL 如何检测此类漏洞。了解不安全反序列化的工作原理可以帮助您完全避免此类错误,而不是专注于避免某些方法。
内容
一步步:为 Oj 构建检测工具链
许多人都知道如何利用反序列化漏洞。但它究竟是如何运作的呢?(这既是魔法,也是汗水和泪水。)在本节中,我们将展示如何为 Oj(一个基于 Ruby 的 JSON 反序列化库)构建一个调用外部 URL 的不安全反序列化检测小工具。此检测小工具基于 William Bowling(又名vakzz)为 Marshal 和 Ruby 3.0.3 开发的通用反序列化小工具,适用于 Oj 和 Ruby 3.3。
1. 从课程开始
大多数情况下,不安全的反序列化漏洞都与反序列化库支持多态性的能力有关,这意味着能够实例化序列化数据中指定的任意类或类状结构。然后,攻击者将这些类链接在一起,在被利用的系统上执行代码。所有使用的类通常都必须由被利用的项目访问。在这种情况下,用于特定目的(例如执行命令或代码)的类称为小工具。而通过将这些类组合起来成为更大漏洞的一部分(例如,通过嵌套它们),我们得到了所谓的小工具链。序列化和反序列化任意构造的能力长期以来被视为一项强大的功能,它最初并非用于代码执行。2015 年,随着 FoxGlove Security 发布了一篇关于广泛存在的 Java 反序列化漏洞的博客文章,公众对此功能的看法发生了变化。2017 年,BlackHat 上展示了针对基于 Java 和 .NET 的 JSON 库的不安全反序列化攻击,标题为“ 13 号星期五:JSON 攻击”。
当使用名为 Oj 的(非默认)Ruby 库反序列化 JSON 时,项目很容易受到攻击,只需构造如下内容即可:
data = Oj.load(untrusted_json)
Oj 库默认支持 JSON 中指定的类的实例化。可以通过指定附加参数或使用来禁用此行为Oj.safe_load
。
正如介绍中所提到的,不安全的反序列化漏洞不仅限于 JSON;它们可能发生在从用户控制的数据反序列化的任意类或类似类的结构的任何地方。
要实例化一个名称为 且具有内容为MyClass
的字段的类,必须将以下 JSON 传递给易受攻击的 Oj 接收器。member
value
{
"^o": "MyClass",
"member": "value"
}
2. 接下来是映射(哈希)、列表、getter、setter、构造函数等
虽然类的实例化是反序列化不安全漏洞最常见的特征,但接下来的构造块因语言而异。虽然在 Java 和类似语言中,反序列化不安全漏洞有时会使用构造函数、setter 和 getter 来初始触发代码执行,但对于 Ruby 反序列化漏洞,我们不能依赖它们。Vakzz的博客文章是关于 Ruby 二进制 Marshal 序列化的利用,它依赖于一种所谓的魔术方法(在序列化对象重建中调用的方法)_load
(类似于 Java 的readObject
)来触发代码执行。但是,Oj 不会调用这个魔术方法,因此为了触发我们的小工具链的执行,我们不能依赖这个方法,而必须找到其他方法。
首先回答这个问题:我们可以使用什么来触发 Oj 中的代码执行?
方法hash(code)
!
Oj 并不是我们依赖该hash
方法作为小工具链启动的唯一反序列化库。hash
当反序列化库将键值对添加到哈希图(在 Ruby 中简称为哈希本身)时,通常会在键对象上调用该方法。
下表展示了 Ruby 中流行的序列化库的启动方法:
图书馆 | 输入数据 | 课堂内开球方法 |
元帅(红宝石) | 二进制 | _load |
橙汁 | JSON | hash (类需要作为键放入哈希(映射)中) |
牛 | XML | hash (类需要作为键放入哈希(映射)中) |
心理学(红宝石) | YAML | hash (类需要作为键放入哈希(映射)中)init_with |
JSON (Ruby) | JSON | json_create ([参见最后关于 json_create 的注释](#table-vulnerable-sinks)) |
让我们创建一个小的概念证明来演示如何使用该hash
方法启动我们的小工具链。
我们假设我们有一个类,例如下面的类,在目标 Ruby 项目中可用(提示:在实际项目中不会有这样的小工具):
class SimpleClass
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
调用“hash”将使用“system”执行“@cmd”成员变量中的命令。请注意,在 Oj 反序列化过程中,不会执行构造函数。在这里,我们使用它自己创建一个快速示例有效负载并转储生成的 JSON:
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload
注意:虽然直接序列化单个小工具可能有意义,但序列化甚至只是调试整个小工具链通常很危险,因为它可能会在序列化过程中触发链的执行(这不会给你预期的结果,但你会“利用”你自己的系统)。 |
有效载荷 JSON 如下所示:
{
"^o": "SimpleClass",
"cmd": "open -a calculator"
}
如果我们现在加载这个 JSON,什么Oj.load
也不会发生。为什么?因为实际上没有人调用 hash 方法。</