Schema in a response allows additional properties
Description
The schema you have defined allows additional properties, either intentionally or unintentionally.
Unlike in the OpenAPI Specification (OAS) v2, in OAS v3 it is not enough to just state the type of the properties in the schema. By default, the property additionalProperties
is true
. Unless you specifically set additionalProperties
to false
, the schema continues to accept properties of any type.
However, sometimes you intentionally want to allow additional properties, especially when using the combining operations combining operations allOf
, anyOf
, or oneOf
.
- 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.
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. In this case, the schema does not use any combining operations (so there is no need to allow additional properties), but additionalProperties
has not been set, so it defaults to true
:
{
"post": {
"description": "Creates a new pet in the store",
// ...
"responses": {
"200": {
"description": "success",
"schema": {
"type": "object",
"$ref": "#/components/schemas/NewPet"
}
}
}
},
// ...
"NewPet": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Pet name"
},
"tag": {
"type": "string",
"description": "Pet tag"
}
}
}
}
Possible exploit scenario
Attackers strive to make your APIs behave in an unexpected way to learn more about your system or to cause a data breach. Good data definition quality in the schemas used in API responses allows reliably validating that the contents of outgoing API responses are as expected.
While filtering API responses does not block a specific kind of attack, it is there as a damage control mechanism in the unfortunate event that a successful attack has been conducted: it allows blocking the response and prevent attackers from retrieving data they should not access.
In the vast majority of cases (with the notable exception of Denial of Service (DoS and DDoS) attacks) attacks are conducted because attackers want to access data or resources they should not have access to. Often, this means that the structure or the size of the API response changes as a result of a successful attack, compared to a normal API response.
Validating that API responses are as expected can be achieved through proper schema validation of the API responses. The accuracy of this depends on the quality of the response schemas: the better defined your schemas are, the easier it is to detect when something is not right.
Remediation
The best remediation option depends on what combining operations (if any) the schema uses and on how many levels as well as the type of the subchemas of the combining operations.
In general, the safest option is not to allow additional properties. If the schema does not use allOf
, anyOf
, or oneOf
at all, make sure you define all properties of the accepted JSON payload in the schema itself, and set additionalProperties
to false
. This will enforce the limitations to what the schema accepts:
{
"post": {
"description": "Creates a new pet in the store",
// ...
"responses": {
"200": {
"description": "success",
"schema": {
"type": "object",
"$ref": "#/components/schemas/NewPet"
}
}
}
},
// ...
"NewPet": {
"type": "object",
"required": [
"name"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Pet name"
},
"tag": {
"Type": "string",
"description": "Pet tag"
}
}
}
}
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
.
The combining operation allOf
is a handy way to allow composition to re-use objects by reference, but as a drawback you must always allow additional properties, which is a security risk. Consider if you could compile the object into one schema, especially for sensitive APIs and API operations.