Skip to content

Commit ca8f673

Browse files
committed
Initial commit
0 parents  commit ca8f673

File tree

8 files changed

+351
-0
lines changed

8 files changed

+351
-0
lines changed

.gitignore

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Created by https://www.gitignore.io/api/node
2+
3+
### Node ###
4+
# Logs
5+
logs
6+
*.log
7+
npm-debug.log*
8+
9+
# Runtime data
10+
pids
11+
*.pid
12+
*.seed
13+
14+
# Directory for instrumented libs generated by jscoverage/JSCover
15+
lib-cov
16+
17+
# Coverage directory used by tools like istanbul
18+
coverage
19+
20+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21+
.grunt
22+
23+
# node-waf configuration
24+
.lock-wscript
25+
26+
# Compiled binary addons (http://nodejs.org/api/addons.html)
27+
build/Release
28+
29+
# Dependency directory
30+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
31+
node_modules
32+

.jshintrc

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"node": true,
3+
"expr": true,
4+
"esnext": true,
5+
"bitwise": true,
6+
"curly": true,
7+
"eqeqeq": true,
8+
"immed": true,
9+
"indent": 2,
10+
"latedef": true,
11+
"newcap": false,
12+
"noarg": true,
13+
"quotmark": "single",
14+
"regexp": true,
15+
"undef": true,
16+
"unused": true,
17+
"strict": true,
18+
"trailing": true,
19+
"smarttabs": true,
20+
"globals": {
21+
"afterEach": false,
22+
"beforeEach": false,
23+
"describe": false,
24+
"expect": false,
25+
"it": false
26+
}
27+
}

.travis.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
language: node_js
2+
node_js:
3+
- '0.10'
4+
- '0.12'
5+
- '4'
6+
- '5'

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Tom Spencer.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Express Mongoose Sanitize
2+
3+
Express 4.x middleware which sanitizes user-supplied data to prevent MongoDB Operator Injection.
4+
5+
## Installation
6+
7+
``` bash
8+
npm install express-mongo-sanitize
9+
```
10+
11+
## Usage
12+
13+
Add as a piece of express middleware, before defining your routes.
14+
15+
``` js
16+
var express = require('express'),
17+
bodyParser = require('body-parser'),
18+
mongoSanitize = require('express-mongo-sanitize');
19+
20+
var app = express();
21+
22+
app.use(bodyParser.urlencoded({extended: true}));
23+
app.use(bodyParser.json());
24+
app.use(mongoSanitize());
25+
26+
```
27+
28+
## What?
29+
30+
This module removes any keys in objects that begin with a `$` sign from `req.body`, `req.query` or `req.params`.
31+
32+
## Why?
33+
34+
Object keys starting with a `$` are _reserved_ for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a `$` operator, which could change the context of a database operation. Most notorious is the `$where` operator, which can execute arbitrary JavaScript on the database.
35+
36+
The best way to prevent this is to sanitize the received data, and remove any offending keys.
37+
38+
## Credits
39+
40+
Inspired by [mongo-sanitize](https://github.com/vkarpov15/mongo-sanitize).
41+
42+
## License
43+
44+
MIT

index.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
var sanitize = function(val) {
4+
if(Array.isArray(val)) {
5+
val.forEach(sanitize);
6+
7+
} else if(val instanceof Object) {
8+
Object.keys(val).forEach(function(key) {
9+
if (/^\$/.test(key)) {
10+
delete val[key];
11+
} else {
12+
sanitize(val[key]);
13+
}
14+
});
15+
}
16+
17+
return val;
18+
};
19+
20+
module.exports = function() {
21+
return function(req, res, next) {
22+
['body', 'params', 'query'].forEach(function(k) {
23+
if(req[k]) {
24+
req[k] = sanitize(req[k]);
25+
}
26+
});
27+
next();
28+
};
29+
};

package.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "express-mongo-sanitize",
3+
"version": "1.0.0",
4+
"description": "Sanitize your express payload to prevent MongoDB operator injection.",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "./node_modules/mocha/bin/mocha test.js"
8+
},
9+
"engines": {
10+
"node": ">=0.10.0"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/fiznool/express-mongo-sanitize.git"
15+
},
16+
"keywords": [
17+
"mongodb",
18+
"express",
19+
"middleware",
20+
"operator",
21+
"injection",
22+
"security"
23+
],
24+
"author": "Tom Spencer <[email protected]>",
25+
"license": "MIT",
26+
"bugs": {
27+
"url": "https://github.com/fiznool/express-mongo-sanitize/issues"
28+
},
29+
"homepage": "https://github.com/fiznool/express-mongo-sanitize#readme",
30+
"devDependencies": {
31+
"body-parser": "^1.14.1",
32+
"express": "^4.13.3",
33+
"mocha": "^2.3.3",
34+
"supertest": "^1.1.0"
35+
}
36+
}

test.js

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
'use strict';
2+
3+
var request = require('supertest'),
4+
express = require('express'),
5+
bodyParser = require('body-parser'),
6+
sanitize = require('./index.js');
7+
8+
describe('Express Mongo Sanitize', function() {
9+
var app = express();
10+
app.use(bodyParser.urlencoded({extended: true}));
11+
app.use(bodyParser.json());
12+
app.use(sanitize());
13+
14+
app.post('/body', function(req, res){
15+
res.status(200).json({
16+
body: req.body
17+
});
18+
});
19+
20+
app.get('/query', function(req, res){
21+
res.status(200).json({
22+
query: req.query
23+
});
24+
});
25+
26+
describe('Top-level object', function() {
27+
it('should sanitize the query string', function(done) {
28+
request(app)
29+
.get('/query?q=search&$where=malicious')
30+
.set('Accept', 'application/json')
31+
.expect(200, {
32+
query: {
33+
q: 'search'
34+
}
35+
}, done);
36+
});
37+
38+
it('should sanitize a JSON body', function(done) {
39+
request(app)
40+
.post('/body')
41+
.send({
42+
q: 'search',
43+
is: true,
44+
and: 1,
45+
even: null,
46+
stop: undefined,
47+
$where: 'malicious'
48+
})
49+
.set('Content-Type', 'application/json')
50+
.set('Accept', 'application/json')
51+
.expect(200, {
52+
body: {
53+
q: 'search',
54+
is: true,
55+
and: 1,
56+
even: null
57+
}
58+
}, done);
59+
});
60+
61+
it('should sanitize a form url-encoded body', function(done) {
62+
request(app)
63+
.post('/body')
64+
.send('q=search&$where=malicious')
65+
.set('Content-Type', 'application/x-www-form-urlencoded')
66+
.set('Accept', 'application/json')
67+
.expect(200, {
68+
body: {
69+
q: 'search'
70+
}
71+
}, done);
72+
});
73+
});
74+
75+
describe('Nested Object', function() {
76+
it('should sanitize a nested object in the query string', function(done) {
77+
request(app)
78+
.get('/query?username[$gt]=')
79+
.set('Accept', 'application/json')
80+
.expect(200, {
81+
query: {
82+
username: {}
83+
}
84+
}, done);
85+
});
86+
87+
it('should sanitize a nested object in a JSON body', function(done) {
88+
request(app)
89+
.post('/body')
90+
.send({
91+
username: { $gt: '' }
92+
})
93+
.set('Content-Type', 'application/json')
94+
.set('Accept', 'application/json')
95+
.expect(200, {
96+
body: {
97+
username: {}
98+
}
99+
}, done);
100+
});
101+
102+
it('should sanitize a nested object in a form url-encoded body', function(done) {
103+
request(app)
104+
.post('/body')
105+
.send('username[$gt]=')
106+
.set('Content-Type', 'application/x-www-form-urlencoded')
107+
.set('Accept', 'application/json')
108+
.expect(200, {
109+
body: {
110+
username: {}
111+
}
112+
}, done);
113+
});
114+
});
115+
116+
describe('Nested Object inside an Array', function() {
117+
it('should sanitize a nested object in the query string', function(done) {
118+
request(app)
119+
.get('/query?username[0][$gt]=')
120+
.set('Accept', 'application/json')
121+
.expect(200, {
122+
query: {
123+
username: [{}]
124+
}
125+
}, done);
126+
});
127+
128+
it('should sanitize a nested object in a JSON body', function(done) {
129+
request(app)
130+
.post('/body')
131+
.send({
132+
username: [{ $gt: '' }]
133+
})
134+
.set('Content-Type', 'application/json')
135+
.set('Accept', 'application/json')
136+
.expect(200, {
137+
body: {
138+
username: [{}]
139+
}
140+
}, done);
141+
});
142+
143+
it('should sanitize a nested object in a form url-encoded body', function(done) {
144+
request(app)
145+
.post('/body')
146+
.send('username[0][$gt]=')
147+
.set('Content-Type', 'application/x-www-form-urlencoded')
148+
.set('Accept', 'application/json')
149+
.expect(200, {
150+
body: {
151+
username: [{}]
152+
}
153+
}, done);
154+
});
155+
});
156+
});

0 commit comments

Comments
 (0)