logo

Database

Insecure or unset HTTP headers - CORS

Need

Restriction of cross-origin access to explicitly trusted origins

Context

• Usage of Kotlin 1.9+ on the JVM for building web applications

• Usage of the Ktor server CORS plugin

Description

1. Non compliant code

import io.ktor.http.HttpHeaders
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.cors.routing.CORS

fun Application.configureCors() {
    install(CORS) {
        // Every website can call the API with the user's credentials...

The `configureCors` function below installs the Ktor `CORS` plugin with `anyHost()`, allows credentials and accepts the `Authorization` header. `anyHost()` makes the server answer cross-origin requests from every website. Combined with `allowCredentials = true`, the plugin reflects the origin of each request, so a script on any page visited by a logged-in user can call the API with that user's cookies and read the responses: profile data, tokens or business records. The attacker only needs the user to open a malicious page. Setting `Access-Control-Allow-Origin` to `*`, or to the value of the request `Origin` header without validation, has the same effect in servlet-based applications.

2. Steps

• Replace `anyHost()` in the Ktor `CORS` plugin with one `allowHost` call per trusted origin, restricted to `https`.

• Never set `Access-Control-Allow-Origin` to `*` or to an unvalidated `Origin` header in servlets or filters.

• Enable credentials only together with explicit origins.

• Allow only the methods and request headers that the clients need.

• Avoid prefix, suffix or substring checks on origins; compare against an exact allowlist.

3. Secure code example

import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.cors.routing.CORS

fun Application.configureCors() {
    install(CORS) {...

The corrected configuration replaces `anyHost()` with `allowHost`, listing the only front end origin allowed to call the API, and restricts it to the `https` scheme. Requests from any other origin receive no CORS headers, so the browser prevents the calling script from reading the response. Credentials remain allowed only because the origin is explicit, and the plugin only accepts the methods and headers the front end uses. When several origins are needed, each one should be listed with its own `allowHost` call. Pattern checks such as "ends with example.com" must be avoided, because `evil-example.com` also satisfies them.