Unit 6: The browser security model · Lesson 2 of 5 · 20 min

CORS

How a server lets a named origin read its responses, and the setting that lets everyone in.

mission

You are
The analyst reviewing the shop's API.
Situation
Three API routes send CORS headers. One of them carries signed-in customers' orders.
Goal
Check which origins each route trusts, with and without cookies.
Hand in
A finding for any route that trusts every origin with credentials.

CORS as a gatekeeper's list

01

A route with one trusted origin

learner@web-lab
curl -sI -H 'Origin: https://status.example.com' http://127.0.0.1:8000/api/status | grep -i access-control
Access-Control-Allow-Origin: https://status.example.com

you should see Only the status site may read it.

02

The preflight

Before some cross-origin requests, the browser asks first with OPTIONS.

learner@web-lab
curl -si -X OPTIONS http://127.0.0.1:8000/api/status | grep -i access-control
Access-Control-Allow-Origin: https://status.example.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 600

you should see Allowed origin, allowed methods and how long the browser may remember the answer.

03

Try an unknown origin on the orders route

learner@web-lab
curl -si -H 'Origin: https://unknown.example' http://127.0.0.1:8000/api/orders | grep -i access-control
Access-Control-Allow-Origin: https://unknown.example
Access-Control-Allow-Credentials: true

you should see The route copies whatever origin it receives and allows credentials.

defender tip Reflected origin plus Allow-Credentials: true means any website a signed-in customer visits can read their orders. That is a high severity finding.

04

Compare with the products route

learner@web-lab
curl -si -H 'Origin: https://unknown.example' http://127.0.0.1:8000/api/products | grep -i -E 'access-control|vary'
Access-Control-Allow-Origin: https://shop.example.com
Vary: Origin

you should see A fixed origin regardless of what you send, and Vary: Origin for caches.

CORS on /api/orders

Shop today
allow_origin = request.headers["Origin"]
allow_credentials = true

Every origin may read signed-in data.

Fix
if origin in {"https://shop.example.com"}:
    allow_origin = origin
    allow_credentials = true
vary = "Origin"

A fixed allowlist.

What a defender takes away

  1. 1CORS relaxes same origin; it never adds protection.
  2. 2Never reflect the Origin header together with Allow-Credentials.
  3. 3Use a fixed allowlist and send Vary: Origin.

Questions

0 of 3 solved

Answer each one from what you find on your lab machine.