Schema in a request defines combining operations when 'additionalProperties' is set to 'false'
Description
The schema defines combining operations allOf
, anyOf
, or oneOf
while additionalProperties
is set to false
. This may result in all messages being blocked.
- If you are using combining operations for objects with properties,
additionalProperties
must betrue
or the combining operations will not work. - If you are using combining operations for primitives with no properties,
additionalProperties
can befalse
and the combining operations still work. - If you have nested combining operations inside each other (for example,
allOf
withanyOf
nested in the properties), the correctadditionalProperties
value depends on the combinations of the combining operations.
Note that schemas listed in an allOf
, anyOf
, or oneOf
array know nothing of the other schemas on the list. You cannot use combining operations to add more details from one schema to another to “extend” it in the sense of object-oriented inheritance.
For more details, see the OpenAPI Specification.
Example
The following is an example of how this type of risk could look in your API definition. A reusable schema in the #/component/schemas
section has been extended with an enum
from allOf
, but the properties have also been restricted in additionalProperties
. The schema will reject everything, because properties
refers to the entire reusable schema:
{
"components": {
"schemas": {
"Pet": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"petType": {
"type": "string"
}
},
"required": [
"name",
"petType"
]
},
"Cat": {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"type": "object",
"properties": {
"furType": {
"type": "enum",
"enum": [
"short-haired",
"long-haired",
"curly",
"naked"
],
"default": "short-haired"
}
}
}
],
"additionalProperties": false
}
}
}
}
Possible exploit scenario
This is less of an security issue, and more a functional issue with your data definition quality. If the schema now rejects everything, even valid requests are blocked.
Remediation
Do not use combining operations like inheritance in an object-oriented language. When using combining operations in your schemas, pay attention when and how you restrict additional properties. The best remediation option depends on what combining operations the schema uses, on how many levels, and the type of the subchemas of the combining operations.
If the combining operation is anyOf
or oneOf
AND its schema is a primitive with no properties, set additionalProperties
to false
:
{
"properties": {
"name": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"additionalProperties": false
}
},
"additionalProperties": false
}
If the combining operation is anyOf
or oneOf
AND its schema is an object with properties, set additionalProperties
to true
:
{
"type": "object",
"anyOf": [
{
"type": "object",
"required": ["age"],
"properties": {
"age": {
"minimum": 5,
"type": "integer"
}
},
"additionalProperties": true
},
{
"type": "object",
"required": ["name"],
"properties": {
"name": {
"minLength": 3,
"type": "string"
}
},
"additionalProperties": true
}
],
"additionalProperties": true
}
If the combining operation is allOf
, additionalProperties
must be true
:
{
"type": "object",
"allOf": [
{
"type": "object",
"required": ["age"],
"properties": {
"age": {
"minimum": 5,
"type": "integer"
}
},
"additionalProperties": true
},
{
"type": "object",
"required": ["name"],
"properties": {
"name": {
"minLength": 3,
"type": "string"
}
},
"additionalProperties": true
}
],
"additionalProperties": true
}
For nested combining operations, the basic principles above apply:
allOf
must always haveadditionalProperties
set totrue
, in both root schema and subschemas.anyOf
andoneOf
for primitives can haveadditionalProperties
set tofalse
.anyOf
andoneOf
for objects must in general haveadditionalProperties
set totrue
.
However, the following clarifications should be noted:
- If you have only
oneOf
in both root schema and subschemas, you can setadditionalProperties
tofalse
even if the schema was for an object with properties. - If you have only
anyOf
in both root schema and subschemas, you can setadditionalProperties
tofalse
in subschemas to impose stricter security, or totrue
for more relaxed security. - If you have
oneOf
but the root schema definesrequired
properties, you must setadditionalProperties
totrue
.
{
"type": "object",
"required": ["age"],
"properties": {
"age": {
"type": "integer",
"minimum": 0
}
},
"oneOf": [
{
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"minLength": 3
}
},
"additionalProperties": true
}
],
"additionalProperties": true
}
Sometimes compiling reusable schemas from #/components/schemas/
using combining operations may result in conflicting demands for additionalProperties
.
For example, your reusable schemas could be primitives with no properties (like #/components/schemas/User
and #/components/schemas/Usermail
in the code example below), and you are only referencing them under oneOf
(like in the path /users/search
), so you decide to set additionalProperties
to false
to increase security.
However, then you add a path (here /users
) to your API that references these reusable schemas under allOf
. As stated above, allOf
requires additionalProperties
to be true
. This creates a conflit between security and functionality of the value of additionalProperties
:
- If the reusable schemas allow additional properties, this is a security risk.
- If the reusable schemas refenced as subschemas under
allOf
do not allow additional properties, the intersection of theallOf
is null.
{
"paths": {
"/users": {
"requestBody": {
"content": {
"schema": {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/User"
},
{
"$ref": "#/components/schemas/Usermail"
}
]
}
}
}
},
"/users/search": {
"requestBody": {
"content": {
"schema": {
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/User"
},
{
"$ref": "#/components/schemas/Usermail"
}
]
}
}
}
}
},
// ...
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"firstname": {
"type": "string"
},
"lastname": {
"type": "string"
}
},
"additionalProperties": false
},
"Usermail": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
},
"additionalProperties": false
}
}
}
}
We recommend splitting the allOf
schema (the path /users
) into two separate objects to be able to enforce security while avoiding the null intersection in the allOf
.