如何Fuzz Json Web Services

 

一、前言

对JSON服务进行模糊测试(fuzz)往往是一项卓有成效的工作(尤其是采用动态脚本语言实现的JSON服务,如Python、Ruby以及JavaScript)。然而根据我的观察,在测试过程中,很少有人能完美完成这项工作。在这篇文章中,我想跟大家分享如何完成这一任务的个人心得。本文中我使用的是自己常用的一些工具,如果你有更好的选择,也可以选用自己的工具。现在让我们步入正题。

Json Web Services指的是以JSON文档作为输入的简单web服务。云架构(如AWS、Azure以及Google Cloud)经常具备这类接口,可以为我们的手机、手表、电视以及冰箱提供服务。现在基本上所有的东西都在使用JSON数据通信,这是因为JSON与其他文档格式(如XML、YAML、INI等)不同,相对比较简单、紧凑并且不大容易出现奇怪的错误。因为JSON的优良属性以及易用性,现在所有的编程语言和环境都原生支持这种格式。

 

二、测试样例

在许多编程语言中(尤其是JavaScript,还包括Ruby以及Python语言),JSON是首选方案。这意味着JSON虽然是外部对象,但处理起来与原生对象无异,因此可以具备与普通对象类似的访问方式。比如,我们来看一下如下请求:

POST /path/to/service HTTP/1.1
Host: service
Content-Length: xxxx
Content-Type: application/json

{
    "profile": {
        "name": "Bob",
        "age": 40
    }
}

为了访问“profile”属性中的“name”字段,JavaScript开发者很有可能会采用如下方法:

// this is what you will normally see in ES5

updateProfile(body.profile.name, body.profile.age)

// ...or in modern ES6 may even look like this which looks a bit safer

const { profile={} } = body
const { name, age } = profile

updateProfile(name, age)

需要注意的是,这里的“name”以及“age”字段都从“profile”中直接提取。还需要注意的是,以上代码既没有验证这两个字段的类型,也没有对其进行规范化处理。虽说这个例子的确比较简单,但其实大多数公开代码中都存在这种现象。

对这类的代码的利用方法很大程度上取决于具体服务以及“updateProfile”函数的真正处理过程,因此我们无法遵循非常标准的利用步骤。我们不能罗列已有的错误字符,希望能够快速得到结果。相反的是,我们需要分析应用程序的行为,推测可能出现的情况,看看是否存在漏洞利用的机会。这也就是为何我们需要撸起袖子加油fuzz的原因所在。那么问题来了,我们要fuzz什么?怎么fuzz?

显然,我们需要为name以及age字段尝试不同的值。此外,我们还需要尝试其他一些意外的输入,比如数组或者对象。举个例子,updateProfile函数可能想处理一个对象(如{first, last}),而不希望收到一个字符串。在实现检查过程时,开发者可能没有考虑到updateProfile函数如何处理反序列化的JSON数据,因而当name字段为字符串类型时,相应的代码分支可能包含一些bug。我们需要通过fuzz技术来自动尝试这种情况。

在这种场景中,我使用的是AppBandit,但你们也可以选择使用Fuzzer,如果你觉得自己开发的模块更加给力也可以派上用场。之前我已经使用AppBandit以及HTTPView成功完成过这个任务,因此我就不再浪费自己的时间重复造轮子了。

首先,我们可以启动AppBandit,打开新的Fuzzer标签页。我们需要设置一些基本参数,如下图所示。这里的设置比较简单,只是为接下来的操作做准备。

现在我们需要配置JSON文档,以便生成不同的组合方式。为了实现这一目标,我们需要使用JSON Fuzz生成器。转至Body页面,从下拉列表中选择JSON Fuzz。在第一个字段中,我们需要设置自己的文档。在第二个字段中,我们需要设置待使用的载荷(payload)。

如果我们只是在载荷字段中输入一个简单的字符串,那么程序会将其当成字符串来使用。比如,如果载荷为“test”,那么JSON Fuzz生成器会生成如下两个文档:

{
    "profile": {
        "name": "test",
        "age": 40
    }
}

以及

{
    "profile": {
        "name": "Bob",
        "age": "test"
    }
}

然而这对我们查找漏洞来说帮助不大,因此我们需要使用其他生成器,提取更多的值。我们可以在下拉列表中选择字典生成器,从字典的下拉列表中选择FuzzDB,然后选择“attack/json/JSON_Fuzzing.txt”。需要记住的是,AppBandit默认情况下不会内嵌这些字典,如果我们需要可以随时下载。

现在我们已经加载了这些列表,我们也可以编辑并添加自己的测试载荷,只要觉得有用我们就可以往里面添加任何数据,更多的载荷会带来更好的fuzz效果。我们还可以使用箭头按钮来预览载荷的生成过程。请注意,目前我们已经将载荷以正确的方式引入JSON格式中,也就是说载荷会被解释成为字符串。

这并不是我们想要的效果。我们想要的是使用原始的JSON,为了实现这个目标,我们需要勾选“Parse payload”以及“Ignore payload parsing errors”选项。现在载荷已经可以正确生成,如下图所示。

目前数据已生成完毕。退出生成器,使用箭头按钮循环检查一下fuzzer载荷,看一切是否已准备就绪。

在我们点击“Play”按钮之前,我想稍微多配置一下。我们可以设置请求超时参数,也可以将最大连接数增加到60,这样测试起来速度能够快一些。

有些情况下,由于某些原因程序可能没有按我们设想的方式工作。这时候不要气馁,如果出现这种情况,我们可以点击Fork按钮,创建当前配置的完整副本,然后执行另一次测试,这操作起来非常容易。

测试完成后,接下来我们需要分析测试结果,看目标服务有没有出现奇怪的数据。这个过程背后并没有什么深奥的科学,我们只需要简单地浏览输出结果,查找其中的异常行为,如果我们使用AppBandit的Resend工具,就可以确认异常行为,然后再进一步挖掘或者利用异常点。

 

三、总结

我知道这篇文章无法覆盖所有的内容,这是一个开放式问题,大家可以好好讨论一下。与SQL注入、跨站脚本攻击以及本地文件包含问题不同,这并不是非常标准的预定义漏洞。话句话说,我们无法注入一些好玩的字符来实现代码执行。然而,如果使用正确的JSON载荷,我们可以控制代码,最终找到脆弱点。尽管本文中我没有举任何实际的测试场景,然而可以肯定的是,在许多情况下我们可以使用这种方法得到有趣并且能够利用的漏洞结果。

(完)