● CWE-942 · OWASP A05:2021

Salah konfigurasi CORS (CWE-942): bagaimana pemantulan Origin membocorkan data dan cara memperbaikinya

API yang menjawab Origin apa pun dengan Access-Control-Allow-Origin bernilai origin itu dan Access-Control-Allow-Credentials: true membiarkan setiap situs membaca responsnya dengan cookie pengunjung. Bagaimana pemeriksaan akhiran, origin null, dan origin yang dipantulkan merusak kebijakan same-origin, serta allowlist dengan kecocokan persis yang memperbaikinya, dengan contoh Express dan cors.

Penjelasan Sederhana (ELI5)

Seorang teller bank akan membacakan saldo Anda kepada siapa pun yang Anda utus, asalkan utusan itu menunjukkan surat yang menyebut untuk siapa ia bekerja dan daftar milik teller menyetujui nama itu. Aturan seorang teller adalah 'setujui nama apa pun yang berakhiran kata Santoso'. Orang asing mengirim utusan dengan surat dari 'Budi Santoso', teller menyetujuinya, dan saldo Anda dibacakan kepada orang yang tidak pernah Anda utus. Pemantulan Origin adalah versi malasnya, ketika teller menyetujui semua nama. Perbaikannya adalah daftar nama yang pendek dan persis, dicocokkan huruf demi huruf.

Konsep Kunci & Istilah

Kebijakan same-origin
Browser hanya membiarkan skrip membaca respons dari origin-nya sendiri (skema, host, dan port). Header respons CORS adalah cara server sengaja melonggarkan aturan itu untuk origin tertentu.
Access-Control-Allow-Origin dengan kredensial
Jika respons menyebut origin pemanggil di Access-Control-Allow-Origin dan mengirim Access-Control-Allow-Credentials: true, skrip di origin itu bisa membaca respons dari request yang membawa cookie pengguna. Browser menolak * bersama kredensial, karena itulah server yang salah konfigurasi memantulkan origin.
Pemantulan Origin dan pencocokan akhiran
Menyalin header Origin ke respons berarti memercayai semua situs. Pemeriksaan seperti endsWith('example.com'), pencocokan substring, atau regex tanpa jangkar dengan titik yang tidak di-escape juga menerima https://attacker-example.com.
Origin null
Iframe bersandbox, URL data:, dan sebagian redirect mengirim Origin: null. Penyerang mana pun bisa menghasilkannya, jadi mengizinkan null sama dengan mengizinkan semua orang.
Vary: Origin
Ketika Access-Control-Allow-Origin bergantung pada request, respons harus membawa Vary: Origin agar cache bersama tidak menyajikan header CORS milik satu origin ke origin lain. Paket cors menambahkannya untuk origin dinamis.

Alur Serangan Langkah demi Langkah

Langkah 1

Korban login di situs API

Browser-nya menyimpan cookie sesi untuk api.example.com yang ikut dikirim pada request lintas situs.

Langkah 2

Korban membuka halaman di domain penyerang

Sebuah skrip memanggil fetch('https://api.example.com/api/account', { credentials: 'include' }). Halaman di https://attacker-example.com yang hanya mencetak panjang respons ke console menggambarkan kasus ini.

Langkah 3

API menyetujui origin itu

Pemeriksaan akhiran menerima attacker-example.com, sehingga respons membawa origin itu di Access-Control-Allow-Origin bersama Access-Control-Allow-Credentials: true.

Langkah 4

Skrip membaca data pribadi

Browser menyerahkan JSON akun ke skrip penyerang, yang bisa mengirimnya ke mana saja. Pada 2016 James Kettle menemukan pola ini di beberapa bursa Bitcoin, tempat pola itu mengekspos API key dan data dompet.

Kode Sumber: Rentan vs Aman

IMPLEMENTASI RENTAN
// server.js: origin apa pun yang berakhiran example.com, ditambah null, boleh membaca dengan cookie
import express from 'express';
import cors from 'cors';
import { accountFor, requireSession } from './account.js';

const app = express();

// true membuat cors memantulkan Origin pemanggil bersama kredensial
app.use(cors({
  origin: (origin, callback) => callback(null, !origin || origin === 'null' || origin.endsWith('example.com')),
  credentials: true,
}));

// data akun pribadi yang bisa dibaca origin yang disetujui
app.get('/api/account', requireSession, (req, res) => {
  res.json(accountFor(req.session.userId));
});
PERBAIKAN AMAN & KUAT
// server.js: dua origin persis, tidak ada yang lain
import express from 'express';
import cors from 'cors';
import { accountFor, requireSession } from './account.js';

const app = express();

// origin lengkap dengan skema dan host, dibandingkan persis
const ALLOWED_ORIGINS = new Set(['https://app.example.com', 'https://admin.example.com']);

// Origin yang tidak dikenal atau kosong tidak mendapat header CORS; cors menambahkan Vary: Origin
app.use(cors({
  origin: (origin, callback) => callback(null, origin !== undefined && ALLOWED_ORIGINS.has(origin)),
  credentials: true,
  methods: ['GET', 'POST'],
  maxAge: 600,
}));

// data pribadi tidak pernah disimpan cache bersama
app.get('/api/account', requireSession, (req, res) => {
  res.set('Cache-Control', 'no-store');
  res.json(accountFor(req.session.userId));
});

Daftar Periksa Penguatan Sistem Rekayasa

Sumber

← Lihat seluruh direktori keamanan Semua panduan kerentanan →