Describe the bug
The CDK constructs for API Gateway allow supplying multiple trusted origins to be used in the auto-generated OPTIONS method handlers for CORS preflight responses (CorsOptions interface).
The CORS specification only allows for a single origin in the response headers, so what CDK does in this case is generate a response template that modifies the response based on the origin that the client is requesting. It checks each of the supplied origins (beyond the first one) to see if it matches the Origin request header, and if it does, it sets the Access-Control-Allow-Origin response header to the origin that was supplied in the request.
However, this check treats the configured origin as a regular expression (the code in question). Therefore, if https://www.company.com has been configured as an allowed origin, an attacker could register the domain https://wwwxcompany.com. A crafted request from this origin will match the regular expression and the preflight request succeeds.
It's possible that this is intended behavior, however the documentation doesn't mention anything to that effect. It would also be inconsistent as it does not apply to the first specified origin, only additional ones.
Expected Behavior
Given an API Gateway resource configured with the CORS preflight origins "https://www.firstorigin.com", "https://www.secondorigin.com":
An OPTIONS request to the resource with a request header of Origin: https://wwwXsecondorigin.com should lead to a response header similar to Access-Control-Allow-Origin: https://www.firstorigin.com, i.e. not allowing the invalid origin from the request.
Current Behavior
The OPTIONS request leads to a response header of Access-Control-Allow-Origin: https://wwwXsecondorigin.com, signaling to the requesting browser that the requested origin from is valid.
Reproduction Steps
Create a POC stack such as:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
export class CorsPocStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new apigateway.RestApi(this, 'api', {
defaultCorsPreflightOptions: {
allowOrigins: ['https://www.firstorigin.com', 'https://www.secondorigin.com'],
}
})
.root
.addResource('hello')
.addMethod('GET', new apigateway.MockIntegration({}));
}
}
Deploy the stack.
Call the CORS preflight method, e.g. using httpie:
http OPTIONS https://xxx.execute-api.xxx.amazonaws.com/prod/hello "Origin: https://wwwXsecondorigin.com"
Examine the response and see that it includes the header: Access-Control-Allow-Origin: https://wwwXsecondorigin.com
Possible Solution
Change $origin.matches("${o}") to $origin == "${o}"
Additional Information/Context
No response
CDK CLI Version
2.89.0 (build 2ad6683)
Framework Version
No response
Node.js Version
v20.2.0
OS
macOS
Language
Typescript
Language Version
TypeScript (5.1.6)
Other information
No response
Describe the bug
The CDK constructs for API Gateway allow supplying multiple trusted origins to be used in the auto-generated OPTIONS method handlers for CORS preflight responses (CorsOptions interface).
The CORS specification only allows for a single origin in the response headers, so what CDK does in this case is generate a response template that modifies the response based on the origin that the client is requesting. It checks each of the supplied origins (beyond the first one) to see if it matches the Origin request header, and if it does, it sets the Access-Control-Allow-Origin response header to the origin that was supplied in the request.
However, this check treats the configured origin as a regular expression (the code in question). Therefore, if
https://www.company.comhas been configured as an allowed origin, an attacker could register the domainhttps://wwwxcompany.com. A crafted request from this origin will match the regular expression and the preflight request succeeds.It's possible that this is intended behavior, however the documentation doesn't mention anything to that effect. It would also be inconsistent as it does not apply to the first specified origin, only additional ones.
Expected Behavior
Given an API Gateway resource configured with the CORS preflight origins
"https://www.firstorigin.com", "https://www.secondorigin.com":An OPTIONS request to the resource with a request header of
Origin: https://wwwXsecondorigin.comshould lead to a response header similar toAccess-Control-Allow-Origin: https://www.firstorigin.com, i.e. not allowing the invalid origin from the request.Current Behavior
The OPTIONS request leads to a response header of
Access-Control-Allow-Origin: https://wwwXsecondorigin.com, signaling to the requesting browser that the requested origin from is valid.Reproduction Steps
Create a POC stack such as:
Deploy the stack.
Call the CORS preflight method, e.g. using httpie:
http OPTIONS https://xxx.execute-api.xxx.amazonaws.com/prod/hello "Origin: https://wwwXsecondorigin.com"Examine the response and see that it includes the header:
Access-Control-Allow-Origin: https://wwwXsecondorigin.comPossible Solution
Change
$origin.matches("${o}")to$origin == "${o}"Additional Information/Context
No response
CDK CLI Version
2.89.0 (build 2ad6683)
Framework Version
No response
Node.js Version
v20.2.0
OS
macOS
Language
Typescript
Language Version
TypeScript (5.1.6)
Other information
No response