logo

Database

Insecure or unset HTTP headers - CORS

Need

Restriction of cross-origin access to explicitly trusted origins

Context

• Usage of Rust 1.75+ for building application services

• Usage of the actix-web crate for handling HTTP requests

• Usage of the actix-cors crate for cross-origin resource sharing

Description

1. Non compliant code

use actix_cors::Cors;
use actix_web::{get, App, HttpResponse, HttpServer};

fn cors() -> Cors {
    // Any website can make requests with the user's cookies and read the response
    Cors::permissive()
}
...

The `cors` function below configures the application with `Cors::permissive()` from the `actix-cors` crate. `Cors::permissive()` accepts any origin, any method and any header, reflects the origin of each request in `Access-Control-Allow-Origin`, and allows credentials. As a result, a script running on any website can send authenticated requests to the API with the cookies of the visitor, and read the responses. An attacker who lures a logged-in user to a malicious page can read their profile, tokens or business data, or perform actions on their behalf. `Cors::default().allow_any_origin()` and a manual `Access-Control-Allow-Origin: *` header, set with `insert_header` or `DefaultHeaders`, open the API to every origin in the same way.

2. Steps

• Replace `Cors::permissive()` and `allow_any_origin()` with `Cors::default()` plus one `allowed_origin` call per trusted origin.

• Remove manual `Access-Control-Allow-Origin` headers set to `*` with `insert_header` or `DefaultHeaders`.

• List only the methods and request headers the clients need with `allowed_methods` and `allowed_headers`.

• Enable `supports_credentials` only together with explicit origins, never with a wildcard or a reflected origin.

• Validate dynamic origins with `allowed_origin_fn` against an exact allowlist instead of prefix, suffix or substring checks.

3. Secure code example

use actix_cors::Cors;
use actix_web::http::header;
use actix_web::{get, App, HttpResponse, HttpServer};

fn cors() -> Cors {
    // Only the front end origin may call the API with credentials
    Cors::default()
        .allowed_origin("https://app.example.com")...

The corrected `cors` function starts from `Cors::default()`, which denies every cross-origin request, and allows only the origin of the application front end with `allowed_origin`. It also lists the exact methods and request headers the front end uses, and caches the preflight answer for one hour with `max_age`. Requests from any other origin receive no CORS headers, so the browser blocks the calling script from reading the response. `supports_credentials` is enabled only because the origin is explicit. Credentials must never be combined with a wildcard or a reflected origin, and when a list of origins is needed, it should be a fixed allowlist rather than a pattern such as a suffix check that `evil-example.com` could also satisfy.