Skip to content

Commit

Permalink
fix: Fixed security issue #738: prototype pollution possible when app…
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Jan 20, 2021
1 parent d75de70 commit da2bd4f
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 3 deletions.
109 changes: 109 additions & 0 deletions __tests__/patch.js
Expand Up @@ -12,6 +12,8 @@ enableAllPlugins()

jest.setTimeout(1000)

const isProd = process.env.NODE_ENV === "production"

function runPatchTest(base, producer, patches, inversePathes) {
let resultProxies, resultEs5

Expand Down Expand Up @@ -1147,3 +1149,110 @@ test("#676 patching Date objects", () => {
)
expect(rebuilt.date).toEqual(new Date("2020-11-10T08:08:08.003Z"))
})

test("do not allow __proto__ polution - 738", () => {
const obj = {}

// @ts-ignore
expect(obj.polluted).toBe(undefined)
expect(() => {
applyPatches({}, [
{op: "add", path: ["__proto__", "polluted"], value: "yes"}
])
}).toThrow(
isProd
? "24"
: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
)
// @ts-ignore
expect(obj.polluted).toBe(undefined)
})

test("do not allow __proto__ polution using arrays - 738", () => {
const obj = {}
const ar = []

// @ts-ignore
expect(obj.polluted).toBe(undefined)
// @ts-ignore
expect(ar.polluted).toBe(undefined)
expect(() => {
applyPatches(
[],
[{op: "add", path: ["__proto__", "polluted"], value: "yes"}]
)
}).toThrow(
isProd
? "24"
: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
)
// @ts-ignore
expect(obj.polluted).toBe(undefined)
// @ts-ignore
expect(ar.polluted).toBe(undefined)
})

test("do not allow prototype polution - 738", () => {
const obj = {}

// @ts-ignore
expect(obj.polluted).toBe(undefined)
expect(() => {
applyPatches(Object, [
{op: "add", path: ["prototype", "polluted"], value: "yes"}
])
}).toThrow(
isProd
? "24"
: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
)
// @ts-ignore
expect(obj.polluted).toBe(undefined)
})

test("do not allow constructor polution - 738", () => {
const obj = {}

// @ts-ignore
expect(obj.polluted).toBe(undefined)
const t = {}
applyPatches(t, [{op: "replace", path: ["constructor"], value: "yes"}])
expect(typeof t.constructor).toBe("function")
// @ts-ignore
expect(Object.polluted).toBe(undefined)
})

test("do not allow constructor.prototype polution - 738", () => {
const obj = {}

// @ts-ignore
expect(obj.polluted).toBe(undefined)
expect(() => {
applyPatches({}, [
{op: "add", path: ["constructor", "prototype", "polluted"], value: "yes"}
])
}).toThrow(
isProd
? "24"
: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
)
// @ts-ignore
expect(Object.polluted).toBe(undefined)
})

test("maps can store __proto__, prototype and constructor props", () => {
const obj = {}
const map = new Map()
map.set("__proto__", {})
map.set("constructor", {})
map.set("prototype", {})
const newMap = applyPatches(map, [
{op: "add", path: ["__proto__", "polluted"], value: "yes"},
{op: "add", path: ["constructor", "polluted"], value: "yes"},
{op: "add", path: ["prototype", "polluted"], value: "yes"}
])
expect(newMap.get("__proto__").polluted).toBe("yes")
expect(newMap.get("constructor").polluted).toBe("yes")
expect(newMap.get("prototype").polluted).toBe("yes")
expect(obj.polluted).toBe(undefined)
})
14 changes: 12 additions & 2 deletions src/plugins/patches.ts
Expand Up @@ -26,7 +26,8 @@ import {
ArchtypeArray,
die,
isDraft,
isDraftable
isDraftable,
ArchtypeObject
} from "../internal"

export function enablePatches() {
Expand Down Expand Up @@ -211,7 +212,16 @@ export function enablePatches() {

let base: any = draft
for (let i = 0; i < path.length - 1; i++) {
base = get(base, path[i])
const parentType = getArchtype(base)
const p = path[i]
// See #738, avoid prototype pollution
if (
(parentType === ArchtypeObject || parentType === ArchtypeArray) &&
(p === "__proto__" || p === "constructor")
)
die(24)
if (typeof base === "function" && p === "prototype") die(24)
base = get(base, p)
if (typeof base !== "object") die(15, path.join("/"))
}

Expand Down
3 changes: 2 additions & 1 deletion src/utils/errors.ts
Expand Up @@ -38,7 +38,8 @@ const errors = {
},
23(thing: string) {
return `'original' expects a draft, got: ${thing}`
}
},
24: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
} as const

export function die(error: keyof typeof errors, ...args: any[]): never {
Expand Down

5 comments on commit da2bd4f

@SteveRuben
Copy link

@SteveRuben SteveRuben commented on da2bd4f Jan 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

up date to correct, right ? or already corrected ?

@Befa222

This comment was marked as off-topic.

@mweststrate
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Befa222 place random comments on your own repos please.

@abbaskiko
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security alert fix

@Farukh1x95
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix Dep

Please sign in to comment.