Use json parser wrapper function to prevent pollution attacks (#13191)

This commit is contained in:
Rijk van Zanten
2022-05-09 14:57:38 -04:00
committed by GitHub
parent ae2024147f
commit a36c7eabeb
20 changed files with 152 additions and 97 deletions

View File

@@ -1,8 +1,9 @@
import { SchemaOverview } from '@directus/schema/dist/types/overview';
import { Column } from 'knex-schema-inspector/dist/types/column';
import getLocalType from './get-local-type';
import logger from '../logger';
import env from '../env';
import logger from '../logger';
import getLocalType from './get-local-type';
import { parseJSON } from './parse-json';
export default function getDefaultValue(
column: SchemaOverview[string]['columns'][string] | Column
@@ -59,7 +60,7 @@ function castToObject(value: any): any | any[] {
if (typeof value === 'string') {
try {
return JSON.parse(value);
return parseJSON(value);
} catch (err: any) {
if (env.NODE_ENV === 'development') {
logger.error(err);

View File

@@ -1,15 +1,16 @@
import { Permission, Accountability, SchemaOverview } from '@directus/shared/types';
import { Accountability, Permission, SchemaOverview } from '@directus/shared/types';
import { deepMap, parseFilter, parsePreset } from '@directus/shared/utils';
import { cloneDeep } from 'lodash';
import hash from 'object-hash';
import { getCache, setSystemCache } from '../cache';
import getDatabase from '../database';
import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions';
import env from '../env';
import { RolesService } from '../services/roles';
import { UsersService } from '../services/users';
import { mergePermissions } from '../utils/merge-permissions';
import { mergePermissionsForShare } from './merge-permissions-for-share';
import { UsersService } from '../services/users';
import { RolesService } from '../services/roles';
import { getCache, setSystemCache } from '../cache';
import hash from 'object-hash';
import env from '../env';
import { parseJSON } from './parse-json';
export async function getPermissions(accountability: Accountability, schema: SchemaOverview) {
const database = getDatabase();
@@ -117,19 +118,19 @@ function parsePermissions(permissions: any[]) {
const permission = cloneDeep(permissionRaw);
if (permission.permissions && typeof permission.permissions === 'string') {
permission.permissions = JSON.parse(permission.permissions);
permission.permissions = parseJSON(permission.permissions);
} else if (permission.permissions === null) {
permission.permissions = {};
}
if (permission.validation && typeof permission.validation === 'string') {
permission.validation = JSON.parse(permission.validation);
permission.validation = parseJSON(permission.validation);
} else if (permission.validation === null) {
permission.validation = {};
}
if (permission.presets && typeof permission.presets === 'string') {
permission.presets = JSON.parse(permission.presets);
permission.presets = parseJSON(permission.presets);
} else if (permission.presets === null) {
permission.presets = {};
}

View File

@@ -13,6 +13,7 @@ import logger from '../logger';
import { RelationsService } from '../services';
import getDefaultValue from './get-default-value';
import getLocalType from './get-local-type';
import { parseJSON } from './parse-json';
export async function getSchema(options?: {
accountability?: Accountability;
@@ -142,7 +143,7 @@ async function getDatabaseSchema(
const type = (existing && getLocalType(column, { special })) || 'alias';
let validation = field.validation ?? null;
if (validation && typeof validation === 'string') validation = JSON.parse(validation);
if (validation && typeof validation === 'string') validation = parseJSON(validation);
result.collections[field.collection].fields[field.field] = {
field: field.field,

View File

@@ -0,0 +1,16 @@
/**
* Run JSON.parse, but ignore `__proto__` properties. This prevents prototype pollution attacks
*/
export function parseJSON(input: string): any {
if (String(input).includes('__proto__')) {
return JSON.parse(input, noproto);
}
return JSON.parse(input);
}
export function noproto<T>(key: string, value: T): T | void {
if (key !== '__proto__') {
return value;
}
}

View File

@@ -1,9 +1,9 @@
import { Accountability, Aggregate, Filter, Query } from '@directus/shared/types';
import { parseFilter } from '@directus/shared/utils';
import { flatten, get, isPlainObject, merge, set } from 'lodash';
import logger from '../logger';
import { Meta } from '../types';
import { Query, Aggregate, Filter } from '@directus/shared/types';
import { Accountability } from '@directus/shared/types';
import { parseFilter } from '@directus/shared/utils';
import { parseJSON } from './parse-json';
export function sanitizeQuery(rawQuery: Record<string, any>, accountability?: Accountability | null): Query {
const query: Query = {};
@@ -99,7 +99,7 @@ function sanitizeAggregate(rawAggregate: any): Aggregate {
if (typeof rawAggregate === 'string') {
try {
aggregate = JSON.parse(rawAggregate);
aggregate = parseJSON(rawAggregate);
} catch {
logger.warn('Invalid value passed for filter query parameter.');
}
@@ -118,7 +118,7 @@ function sanitizeFilter(rawFilter: any, accountability: Accountability | null) {
if (typeof rawFilter === 'string') {
try {
filters = JSON.parse(rawFilter);
filters = parseJSON(rawFilter);
} catch {
logger.warn('Invalid value passed for filter query parameter.');
}
@@ -161,7 +161,7 @@ function sanitizeDeep(deep: Record<string, any>, accountability?: Accountability
if (typeof deep === 'string') {
try {
deep = JSON.parse(deep);
deep = parseJSON(deep);
} catch {
logger.warn('Invalid value passed for deep query parameter.');
}
@@ -200,7 +200,7 @@ function sanitizeAlias(rawAlias: any) {
if (typeof rawAlias === 'string') {
try {
alias = JSON.parse(rawAlias);
alias = parseJSON(rawAlias);
} catch (err) {
logger.warn('Invalid value passed for alias query parameter.');
}