⚠️ EDUCATIONAL DEMO: Simulates a CORS misconfiguration that allows attacker-controlled sites to read API data from the school portal. All data shown is fictional.

CORS Misconfiguration Demo

When Access-Control-Allow-Origin: * is set on a school API, any website can silently read its data using a logged-in user's session.

THE SECURITY RISK IN PLAIN ENGLISH

Browsers normally prevent websites from reading responses from other sites. This is called the Same-Origin Policy — a security rule built into every browser.

CORS headers are the server's way of granting exceptions: "I allow requests from this other site." When a school API sets Access-Control-Allow-Origin: *, it says "I allow requests from every site on the internet" — including attacker sites.

The attacker's page loads in the victim's browser. JavaScript on that page calls the school's API with the victim's cookies already attached. The API responds with real data — and the attacker's script reads it and sends it home.

The fix: Only allow origins that actually need access. Access-Control-Allow-Origin: https://school.edu.ph instead of *.

❌ Vulnerable: ACAO: *
Attacker's page at win-prize.com runs this script when the staff member visits:
// On attacker's page: fetch('https://school-portal.edu.ph/api/students', { credentials: 'include' // sends victim's cookies }).then(r => r.json()) .then(data => { // API said Access-Control-Allow-Origin: * // Browser allowed the read. Data stolen: sendToAttacker(data); });
📤 API RESPONSE — READ BY ATTACKER'S PAGE
Access-Control-Allow-Origin: *
Content-Type: application/json

[
  { "id": 1001, "name": "Maria Santos", "grade": "7", "lrn": "103140202345", "guardian_phone": "09171234567" },
  { "id": 1002, "name": "Jose Reyes", "grade": "8", "lrn": "103140208812", "guardian_phone": "09189876543" },
  { "id": 1003, "name": "Ana Cruz", "grade": "7", "lrn": "103140211099", "guardian_phone": "09201122334" }
]

⚠️ Student records including LRNs and guardian phone numbers sent to attacker. RA 10173 breach.
✅ Protected: ACAO: origin-only
Same script runs on the attacker's page. But the API now only allows its own origin:
// API response headers (protected): Access-Control-Allow-Origin: https://school-portal.edu.ph // Not * — only the school's own origin // Browser checks: does win-prize.com // match school-portal.edu.ph? // No → browser blocks the read.
🛡️ BROWSER CORS ERROR — ATTACKER READ BLOCKED
Access to fetch 'https://school-portal.edu.ph/api/students' from origin 'https://win-prize.com' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://school-portal.edu.ph' that is not equal to the supplied origin. → fetch() threw a TypeError. → Attacker's script received nothing. → Student data never left the school's server.

❌ Current State (Vulnerable)

  • Access-Control-Allow-Origin: * allows every site to read API responses
  • Any logged-in parent, student, or staff becomes a data source for attackers
  • Student records, grades, medical info, addresses can be scraped silently
  • RA 10173 data breach — personal data of minors exposed to unauthorized parties
  • Often introduced by developers to "fix a CORS error" without understanding the risk

✅ Fix (Restrict the Origin)

  • Replace * with explicit allowed origins: https://school.edu.ph
  • Laravel: set allowed_origins in config/cors.php
  • Nginx: add_header 'Access-Control-Allow-Origin' 'https://school.edu.ph'
  • Never use * with credentials: include — browsers block it anyway, but misconfigured servers reflect the Origin header instead
  • If API is internal only: remove CORS headers entirely — no cross-origin access needed