Skip to content

Commit

Permalink
trial for default payload validation
Browse files Browse the repository at this point in the history
  • Loading branch information
legrego committed Oct 21, 2019
1 parent 5988818 commit 9275412
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/core/server/http/base_path_proxy_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export class BasePathProxyServer {
return responseToolkit.continue;
},
],
validate: { payload: null as any },
},
path: `${this.httpConfig.basePath}/{kbnPath*}`,
});
Expand Down Expand Up @@ -175,6 +176,7 @@ export class BasePathProxyServer {
return responseToolkit.continue;
},
],
validate: { payload: null as any },
},
path: `/__UNSAFE_bypassBasePath/{kbnPath*}`,
});
Expand Down
12 changes: 11 additions & 1 deletion src/core/server/http/http_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import { readFileSync } from 'fs';
import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from 'hapi';
import Hoek from 'hoek';
import { ServerOptions as TLSOptions } from 'https';
import { ValidationError } from 'joi';
import Joi, { ValidationError } from 'joi';
import { HttpConfig } from './http_config';
import { extendJoiForPrototypePollution } from './prototype_pollution';

const customJoi = extendJoiForPrototypePollution(Joi);

/**
* Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server.
Expand All @@ -45,6 +48,13 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = {
options: {
abortEarly: false,
},
payload: customJoi.alternatives().try(
customJoi
.object({})
.unknown()
.preventPrototypePollution(),
customJoi.array()
),
},
},
state: {
Expand Down
20 changes: 20 additions & 0 deletions src/core/server/http/prototype_pollution/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { extendJoiForPrototypePollution } from './joi_pp_extensions';
111 changes: 111 additions & 0 deletions src/core/server/http/prototype_pollution/joi_pp_extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Joi from 'joi';

interface StackItem {
value: any;
previousKey: string | null;
}

// we have to do Object.prototype.hasOwnProperty because when you create an object using
// Object.create(null), and I assume other methods, you get an object without a prototype,
// so you can't use current.hasOwnProperty
const hasOwnProperty = (obj: any, property: string) =>
Object.prototype.hasOwnProperty.call(obj, property);

const isObject = (obj: any) => typeof obj === 'object' && obj !== null;

// we're using a stack instead of recursion so we aren't limited by the call stack
function validateObject(obj: any) {
if (!isObject(obj)) {
return;
}

const stack: StackItem[] = [
{
value: obj,
previousKey: null,
},
];
const seen = new WeakSet([obj]);

while (stack.length > 0) {
const { value, previousKey } = stack.pop()!;

if (!isObject(value)) {
continue;
}

if (hasOwnProperty(value, '__proto__')) {
return 'object.proto_invalid_key';
}

if (hasOwnProperty(value, 'prototype') && previousKey === 'constructor') {
return `object.constructor-prototype_invalid_key`;
}

// iterating backwards through an array is reportedly more performant
const entries = Object.entries(value);
for (let i = entries.length - 1; i >= 0; --i) {
const [key, childValue] = entries[i];
if (isObject(childValue)) {
if (seen.has(childValue)) {
return `object.circular_reference`;
}

seen.add(childValue);
}

stack.push({
value: childValue,
previousKey: key,
});
}
}
}

export function extendJoiForPrototypePollution(joi: any) {
const custom = joi.extend((joiInstance: any) => {
const preventPrototypePollutionExtension: Joi.Extension = {
name: 'object',
base: joiInstance.object(),
language: {
proto_invalid_key: '__proto__ is an invalid key',
'constructor-prototype_invalid_key': 'constructor.prototype is an invalid key',
circular_reference: 'Circular reference detected',
},
rules: [
{
name: 'preventPrototypePollution',
validate(params: any, value: any, state: Joi.State, options: Joi.ValidationOptions) {
const error = validateObject(value);
if (error) {
return this.createError(error, {}, state, options);
}
return value;
},
},
],
};

return preventPrototypePollutionExtension;
});

return custom;
}
1 change: 1 addition & 0 deletions src/legacy/core_plugins/console/server/proxy_route.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const createProxyRoute = ({
parse: false,
},
validate: {
payload: null,
query: Joi.object()
.keys({
method: Joi.string()
Expand Down

0 comments on commit 9275412

Please sign in to comment.