Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding options.allowDots (works for node 10.x, 12.x and 14.x) #41

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 52 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,49 @@ Express 4.x middleware which sanitizes user-supplied data to prevent MongoDB Ope
[![Dependency Status](https://david-dm.org/fiznool/express-mongo-sanitize.svg)](https://david-dm.org/fiznool/express-mongo-sanitize)
[![devDependency Status](https://david-dm.org/fiznool/express-mongo-sanitize/dev-status.svg)](https://david-dm.org/fiznool/express-mongo-sanitize#info=devDependencies)


## Installation

``` bash
```bash
npm install express-mongo-sanitize
```

## Usage

Add as a piece of express middleware, before defining your routes.

``` js
```js
const express = require('express');
const bodyParser = require('body-parser');
const mongoSanitize = require('express-mongo-sanitize');

const app = express();

app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// To remove data, use:
app.use(mongoSanitize());

// Or, to replace prohibited characters with _, use:
app.use(mongoSanitize({
replaceWith: '_'
}))

app.use(
mongoSanitize({
replaceWith: '_',
})
);

// Or, to sanitize data that only contains $, without .(dot)
// Can be useful for letting data pass that is meant for querying nested documents. NOTE: This may cause some problems on older versions of MongoDb
// READ MORE: https://github.com/fiznool/express-mongo-sanitize/issues/36
app.use(
mongoSanitize({
allowDots: true,
})
);
```

You can also bypass the middleware and use the module directly:

``` js
```js
const mongoSanitize = require('express-mongo-sanitize');

const payload = {...};
Expand All @@ -54,10 +63,43 @@ mongoSanitize.sanitize(payload, {
replaceWith: '_'
});

// Exclude sanitization of . (dot), only sanitize data that contains $. This may cause some problems on older versions of mongoDb
mongoSanitize.sanitize(payload, {
allowDots: true
});

// Check if the payload has keys with prohibited characters
const hasProhibited = mongoSanitize.has(payload);
```

You can also combine allowDots with replaceWith options:

```js
const mongoSanitize = require('express-mongo-sanitize');

const payload = {...};

// This will only replace the $ with _(symbol) and allow dots
// Example. {'some.data': 'da', $where: 'bad'} -> {'some.data': 'da', _where: 'bad'}

// middleware
app.use(
mongoSanitize({
allowDots: true,
replaceWith: '_'
})
);

// module
mongoSanitize.sanitize(payload, {
allowDots: true,
replaceWith: '_'
});

// Check if the payload has keys with prohibited characters. Pass true as the second argument so it allows dots.
const hasProhibited = mongoSanitize.has(payload, true);
```

## What?

This module searches for any keys in objects that begin with a `$` sign or contain a `.`, from `req.body`, `req.query` or `req.params`. It can then either:
Expand All @@ -71,7 +113,7 @@ See the spec file for more examples.

## Why?

Object keys starting with a `$` or containing a `.` are _reserved_ for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a `$` operator, or including a `.`, which could change the context of a database operation. Most notorious is the `$where` operator, which can execute arbitrary JavaScript on the database.
Object keys starting with a `$` or containing a `.` are _reserved_ for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a `$` operator, or including a `.`, which could change the context of a database operation. Most notorious is the `$where` operator, which can execute arbitrary JavaScript on the database.

The best way to prevent this is to sanitize the received data, and remove any offending keys, or replace the characters with a 'safe' one.

Expand Down
72 changes: 50 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
'use strict';

const TEST_REGEX = /^\$|\./;
const REPLACE_REGEX = /^\$|\./g;
let TEST_REGEX = /^\$|\./;
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved
const TEST_REGEX_WITHOUT_DOT = /^\$/;
let REPLACE_REGEX = /^\$|\./g;
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved

function isPlainObject(obj) {
return typeof obj === 'object' && obj !== null;
}

function withEach(target, cb) {
(function act(obj) {
if(Array.isArray(obj)) {
if (Array.isArray(obj)) {
obj.forEach(act);

} else if(isPlainObject(obj)) {
Object.keys(obj).forEach(function(key) {
} else if (isPlainObject(obj)) {
Object.keys(obj).forEach(function (key) {
const val = obj[key];
const resp = cb(obj, val, key);
if(resp.shouldRecurse) {
if (resp.shouldRecurse) {
act(obj[resp.key || key]);
}
});
}
})(target);

}

function has(target) {
function has(target, allowDots) {
let regex = TEST_REGEX;

if (allowDots) {
regex = TEST_REGEX_WITHOUT_DOT;
}
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved

let hasProhibited = false;
withEach(target, function(obj, val, key) {
if(TEST_REGEX.test(key)) {
withEach(target, function (obj, val, key) {
if (TEST_REGEX.test(key)) {
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved
hasProhibited = true;
return { shouldRecurse: false };
} else {
Expand All @@ -39,25 +44,40 @@ function has(target) {
return hasProhibited;
}

function sanitize(target, options) {
function sanitize(target, options, regex) {
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved
options = options || {};

// Regex is not passed from the middleware
if (!regex) {
regex = TEST_REGEX;

if (options) {
if (options.allowDots) {
TEST_REGEX = TEST_REGEX_WITHOUT_DOT;
}
}
}

let replaceWith = null;
if(!(TEST_REGEX.test(options.replaceWith))) {
if (!regex.test(options.replaceWith) && options.replaceWith !== '.') {
replaceWith = options.replaceWith;
}

withEach(target, function(obj, val, key) {
withEach(target, function (obj, val, key) {
let shouldRecurse = true;

if(TEST_REGEX.test(key)) {
if (regex.test(key)) {
delete obj[key];
if(replaceWith) {
if (replaceWith) {
key = key.replace(REPLACE_REGEX, replaceWith);
// Avoid to set __proto__ and constructor.prototype
// https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications
// https://snyk.io/vuln/SNYK-JS-LODASH-73638
if (key !== "__proto__" && key !== "constructor" && key !== "prototype") {
if (
key !== '__proto__' &&
key !== 'constructor' &&
key !== 'prototype'
) {
obj[key] = val;
}
} else {
Expand All @@ -67,18 +87,26 @@ function sanitize(target, options) {

return {
shouldRecurse: shouldRecurse,
key: key
key: key,
};
});

return target;
}

function middleware(options) {
return function(req, res, next) {
['body', 'params', 'headers', 'query'].forEach(function(k) {
if(req[k]) {
req[k] = sanitize(req[k], options);
return function (req, res, next) {
['body', 'params', 'headers', 'query'].forEach(function (k) {
if (req[k]) {
req[k] = sanitize(
req[k],
options,
options
? options.allowDots
? TEST_REGEX_WITHOUT_DOT
: TEST_REGEX
Blagoj5 marked this conversation as resolved.
Show resolved Hide resolved
: null
);
}
});
next();
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading