Skip to content

Commit

Permalink
Add fix from #283
Browse files Browse the repository at this point in the history
  • Loading branch information
colincasey committed Jun 19, 2023
1 parent 2bc6217 commit 8584a01
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 12 deletions.
20 changes: 19 additions & 1 deletion lib/__tests__/cookieJar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/

import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store} from '../cookie'
import { Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store } from "../cookie";

const { objectContaining, assertions } = expect
jest.useFakeTimers()
Expand Down Expand Up @@ -1008,6 +1008,24 @@ it('should fix issue #197 - CookieJar().setCookie throws an error when empty coo
)).rejects.toThrowError('Cookie failed to parse')
})

it('should fix issue #282 - Prototype pollution when setting a cookie with the domain __proto__', async () => {
const jar = new CookieJar(undefined, {
rejectPublicSuffixes: false
});
// try to pollute the prototype
jar.setCookieSync(
"Slonser=polluted; Domain=__proto__; Path=/notauth",
"https://__proto__/admin"
);
jar.setCookieSync(
"Auth=Lol; Domain=google.com; Path=/notauth",
"https://google.com/"
);

const pollutedObject = {};
expect('/notauth' in pollutedObject).toBe(false);
})

// special use domains under a sub-domain
describe.each([
"local",
Expand Down
22 changes: 11 additions & 11 deletions lib/memstore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
import {Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain} from "./cookie";
import {Store} from './store'
import {getCustomInspectSymbol, getUtilInspect} from './utilHelper'
import { Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain } from "./cookie";
import { Store } from "./store";
import { getCustomInspectSymbol, getUtilInspect } from "./utilHelper";

export class MemoryCookieStore extends Store {
override synchronous: boolean;
Expand All @@ -46,7 +46,7 @@ export class MemoryCookieStore extends Store {
constructor() {
super();
this.synchronous = true;
this.idx = {};
this.idx = Object.create(null);
const customInspectSymbol = getCustomInspectSymbol();
if (customInspectSymbol) {
// @ts-ignore
Expand Down Expand Up @@ -154,10 +154,10 @@ export class MemoryCookieStore extends Store {
return promiseCallback.promise
}

const domainEntry: { [key: string]: any } = this.idx[domain] ?? {}
const domainEntry: { [key: string]: any } = this.idx[domain] ?? Object.create(null)
this.idx[domain] = domainEntry

const pathEntry: { [key: string]: any } = domainEntry[path] ?? {}
const pathEntry: { [key: string]: any } = domainEntry[path] ?? Object.create(null)
domainEntry[path] = pathEntry

pathEntry[key] = cookie
Expand Down Expand Up @@ -225,7 +225,7 @@ export class MemoryCookieStore extends Store {
const promiseCallback = createPromiseCallback<void>(arguments)
const cb = promiseCallback.callback

this.idx = {};
this.idx = Object.create(null);

cb(null);
return promiseCallback.promise
Expand Down Expand Up @@ -270,9 +270,9 @@ export class MemoryCookieStore extends Store {
export function inspectFallback(val: { [x: string]: any; }) {
const domains = Object.keys(val);
if (domains.length === 0) {
return "{}";
return "[Object: null prototype] {}";
}
let result = "{\n";
let result = "[Object: null prototype] {\n";
Object.keys(val).forEach((domain, i) => {
result += formatDomain(domain, val[domain]);
if (i < domains.length - 1) {
Expand All @@ -286,7 +286,7 @@ export function inspectFallback(val: { [x: string]: any; }) {

function formatDomain(domainName: string, domainValue: { [x: string]: any; }) {
const indent = " ";
let result = `${indent}'${domainName}': {\n`;
let result = `${indent}'${domainName}': [Object: null prototype] {\n`;
Object.keys(domainValue).forEach((path, i, paths) => {
result += formatPath(path, domainValue[path]);
if (i < paths.length - 1) {
Expand All @@ -300,7 +300,7 @@ function formatDomain(domainName: string, domainValue: { [x: string]: any; }) {

function formatPath(pathName: string, pathValue: { [x: string]: any; }) {
const indent = " ";
let result = `${indent}'${pathName}': {\n`;
let result = `${indent}'${pathName}': [Object: null prototype] {\n`;
Object.keys(pathValue).forEach((cookieName, i, cookieNames) => {
const cookie = pathValue[cookieName];
result += ` ${cookieName}: ${cookie.inspect()}`;
Expand Down
25 changes: 25 additions & 0 deletions test/cookie_jar_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -810,4 +810,29 @@ vows
}
}
})
.addBatch({
"Issue #282 - Prototype pollution": {
"when setting a cookie with the domain __proto__": {
topic: function() {
const jar = new tough.CookieJar(undefined, {
rejectPublicSuffixes: false
});
// try to pollute the prototype
jar.setCookieSync(
"Slonser=polluted; Domain=__proto__; Path=/notauth",
"https://__proto__/admin"
);
jar.setCookieSync(
"Auth=Lol; Domain=google.com; Path=/notauth",
"https://google.com/"
);
this.callback();
},
"results in a cookie that is not affected by the attempted prototype pollution": function() {
const pollutedObject = {};
assert(pollutedObject["/notauth"] === undefined);
}
}
}
})
.export(module);

0 comments on commit 8584a01

Please sign in to comment.