开发安全之:JSON Injection

Overview

在 XXX.php 的第 X 行中,responsemsg() 方法将未经验证的输入写入 JSON。攻击者可以利用此调用将任意元素或属性注入 JSON 实体。

Details

JSON injection 会在以下情况中出现:

1. 数据从一个不可信赖的数据源进入程序。

2. 将数据写入到 JSON 流。 在这种情况下,由 XXX.php 的第 X 行的 json_decode() 编写 JSON。 应用程序通常使用 JSON 来存储数据或发送消息。用于存储数据时,JSON 通常会像缓存数据那样处理,而且可能会包含敏感信息。用于发送消息时,JSON 通常与 RESTful 服务一起使用,并且可以用于传输敏感信息,例如身份验证凭据。 如果应用程序利用未经验证的输入构造 JSON,则可以更改 JSON 文档和消息的语义。

在相对理想的情况下,攻击者可能会插入无关的元素,导致应用程序在解析 JSON 文档或请求时抛出异常。在更为严重的情况下,例如涉及 JSON Injection,攻击者可能会插入无关的元素,从而允许对 JSON 文档或请求中对业务非常关键的值执行可预见操作。还有一些情况,JSON Injection 可以导致 Cross-Site Scripting 或 Dynamic Code Evaluation。

例 1:以下 PHP 代码将非特权用户(这些用户具有“默认”角色,与之相反,特权用户具有“管理员”角色)的用户帐户身份验证信息从用户控制的 URL 参数 username 和 password 序列化为位于 ~/user_info.json 的 JSON 文件:

$username = $_GET['username']; $password = $_GET['password']; $user_info_json_string = '{"role":"default","username":"' . $username . '","password":"' . $password . '"}';

$user_info_json_file = fopen('~/user_info.json', 'w');

fwrite($user_info_json_file, $user_info_json_string);

fclose($user_info_json_file);

但是,由于 JSON 序列化使用字符串串联来执行,将不会对 username 和 password 中的不可信赖数据进行验证以转义与 JSON 相关的特殊字符。这样,用户就可以任意插入 JSON 密钥,可能会更改已序列化的 JSON 的结构。在本例中,如果非特权用户 mallory(密码为 Evil123!)将 %22,%22role%22:%22 附加到其用户名中,并将该值传递到 username URL 参数,则最终保存到 ~/user_info.json 的 JSON 将为: { "role":"default", "username":"mallory", "role":"admin", "password":"Evil123!" } 如果之后使用 PHP 的本地 json_decode() 函数对已序列化的 JSON 文件进行反序列化,如下所示:

$user_info_json_string = file_get_contents('user_info.json', 'r');

$user_info_json_data = json_decode($user_info_json_string);

$user_info_json_data 中 username、password 和 role 的最终值将分别为 mallory、Evil123! 和 admin。在没有进一步验证反序列化 JSON 中的值是否有效的情况下,应用程序会错误地为用户分配 mallory“管理员”特权。

Recommendations

在将用户提供的数据写入 JSON 时,请遵循以下准则:

1.不要使用从用户输入派生的名称创建 JSON 属性。

2. 确保使用安全的序列化函数(能够以单引号或双引号分隔不可信赖的数据,并且避免任何特殊字符)执行对 JSON 的所有序列化操作。

示例 2:以下 PHP 代码实现的功能与Example 1 相同,但会使用 json_encode() 而不是字符串连接来对数据进行序列化,从而确保正确地分隔和转义任何不可信数据:

$username = $_GET['username'];

$password = $_GET['password'];

$user_info_array = array('role' => 'default', 'username' => $username, 'password' => $password);

$user_info_json_string = json_encode($user_info_array);

$user_info_json_file = fopen('~/user_info.json', 'w');

fwrite($user_info_json_file, $user_info_json_string);

fclose($user_info_json_file);