Introduction

CORS Security

Cross-Origin Resource Sharing (CORS) is a browser mechanism that controls which origins can access resources on a different origin. While CORS enables legitimate cross-origin requests, misconfigurations are among the most common security vulnerabilities discovered in modern web applications.

How CORS Works

CORS works through HTTP headers that the server sends to tell the browser which origins are permitted. The browser enforces these restrictions on the client side.

Simple Requests

A simple request uses standard methods (GET, HEAD, POST) and headers. The browser adds an Origin header, and the server responds with Access-Control-Allow-Origin.

Request:

GET /api/data HTTP/1.1

Origin: https://trusted-app.com

Response:

HTTP/1.1 200 OK

Access-Control-Allow-Origin: https://trusted-app.com

Preflight Requests

For non-simple requests (custom headers, PUT, DELETE, content types other than form data), the browser sends an OPTIONS preflight request first.

Preflight:

OPTIONS /api/data HTTP/1.1

Origin: https://trusted-app.com

Access-Control-Request-Method: DELETE

Access-Control-Request-Headers: X-Custom-Header

Response:

HTTP/1.1 204 No Content

Access-Control-Allow-Origin: https://trusted-app.com

Access-Control-Allow-Methods: GET, POST, PUT, DELETE

Access-Control-Allow-Headers: X-Custom-Header

Access-Control-Max-Age: 3600

Proper Origin Validation

Never reflect the Origin header back unconditionally. This is the most common dangerous CORS misconfiguration.

UNSAFE: Reflective origin (vulnerable to attack)

def cors_unsafe(request):

origin = request.headers.get('Origin')

response.headers['Access-Control-Allow-Origin'] = origin # NEVER do this

response.headers['Access-Control-Allow-Credentials'] = 'true'

SAFE: Whitelist-based origin validation

ALLOWED_ORIGINS = {

'https://app.example.com',

'https://admin.example.com',

'https://trusted-partner.com',

}

def cors_safe(request):

origin = request.headers.get('Origin')

if origin in ALLOWED_ORIGINS:

response.headers['Access-Control-Allow-Origin'] = origin

response.headers['Vary'] = 'Origin'

if origin and is_origin_allowed(origin):

response.headers['Access-Control-Allow-Origin'] = origin

response.headers['Vary'] = 'Origin'

def is_origin_allowed(origin):

from urllib.parse import urlparse

parsed = urlparse(origin)

Must be HTTPS

if parsed.scheme != 'https':

return False

Exact match only, no wildcards for credentialed requests

return parsed.geturl() in ALLOWED_ORIGINS

Common Misconfigurations

1\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Wildcard with Credentials

VULNERABLE: Wildcard origin with credentials

response.headers['Access-Control-Allow-Origin'] = '*'

response.headers['Access-Control-Allow-Credentials'] = 'true'

Browsers will reject this combination, but attackers can still exploit

applications that don't rely on browser enforcement (e.g., server-side CORS)

2\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Overly Permissive Origin Match

VULNERABLE: Subdomain matching that is too permissive

def unsafe_origin_check(origin):

Accepts evil.example.com as matching example.com

return origin.endswith('.example.com') or origin == 'example.com'

Also vulnerable: origin contains 'example.com' would match evil-example.com

3\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Null Origin

VULNERABLE: Allow null origin

if origin == 'null':

response.headers['Access-Control-Allow-Origin'] = 'null'

null origin is used by file://, sandboxed iframes, and some

automated scanners. Attackers can exploit this.

Exploit Scenarios

Internal Network API Attack

An attacker hosts a malicious site that performs cross-origin requests to internal APIs. If the internal API reflects origins, the attacker can exfiltrate sensitive data.

CORS Configuration Best Practices

from flask import Flask, request, jsonify

from functools import wraps

app = Flask(name)

def cors_allowed(origin):

ALLOWED = [

'https://app.example.com',

'https://dashboard.example.com',

]

return origin in ALLOWED

def cors_middleware(f):

@wraps(f)

def decorated(args, *kwargs):

origin = request.headers.get('Origin')

if request.method == 'OPTIONS':

response = app.make_default_options_response()

else:

response = f(args, *kwargs)

if origin and cors_allowed(origin):

response.headers['Access-Control-Allow-Origin'] = origin

response.headers['Vary'] = 'Origin'

response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'

response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'

response.headers['Access-Control-Max-Age'] = '3600'

if request.cookies:

response.headers['Access-Control-Allow-Credentials'] = 'true'

return response

return decorated

Conclusion

CORS misconfigurations remain a top web vulnerability. Never reflect origins dynamically, never use wildcard with credentials, validate origins against an allowlist, and always set the Vary: Origin header when using dynamic CORS. Remember that CORS is a browser-enforced policy — protect sensitive endpoints with proper authentication regardless.