CSRF — Menumpang Kredensial Korban
Peramban melampirkan cookie secara otomatis pada setiap permintaan ke sebuah domain, termasuk permintaan yang dipicu dari situs lain. Situs penyerang dapat menyusun formulir yang mengirim ke bank Anda, dan cookie sesi ikut terkirim.
<!-- Di situs penyerang -->
<form action="https://bank.id/transfer" method="POST">
<input name="ke" value="penyerang">
<input name="jumlah" value="10000000">
</form>
<script>document.forms[0].submit()</script>
Pertahanan Berlapis untuk CSRF
Set-Cookie: sesi=abc; SameSite=Lax; Secure; HttpOnly
// Token CSRF — pola kirim ganda
const token = randomBytes(32).toString("hex");
res.cookie("csrf", token, { sameSite: "lax", secure: true });
// Klien menyalin nilai itu ke header X-CSRF-Token
// Server membandingkan dengan waktu tetap
if (!timingSafeEqual(Buffer.from(req.cookies.csrf),
Buffer.from(req.get("X-CSRF-Token") ?? ""))) {
return res.status(403).end();
}
Serangan CSRF bergantung pada kredensial yang dilampirkan peramban secara otomatis. Header Authorization harus ditambahkan JavaScript secara eksplisit, dan skrip di situs penyerang tidak dapat membaca token milik domain lain. Karena itu API murni berbasis token tidak memerlukan token CSRF — tetapi API yang menerima autentikasi cookie tetap memerlukannya.
Nilai Lax tetap mengirim cookie pada navigasi tingkat atas dengan metode GET. Bila ada endpoint semacam GET /akun/hapus?id=5, tautan sederhana dari situs mana pun sudah cukup untuk memicunya. Metode GET tidak boleh mengubah keadaan — ini bukan sekadar kerapian REST, melainkan batas keamanan.
CORS Sering Disalahpahami
// Berbahaya — memantulkan asal apa pun sambil mengizinkan kredensial
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header("Access-Control-Allow-Credentials", "true");
// Aman — daftar putih eksplisit
const ASAL_BOLEH = new Set(["https://app.contoh.id", "https://admin.contoh.id"]);
const asal = req.headers.origin;
if (ASAL_BOLEH.has(asal)) {
res.header("Access-Control-Allow-Origin", asal);
res.header("Access-Control-Allow-Credentials", "true");
res.header("Vary", "Origin");
}
Konfigurasi ini sering muncul sebagai "perbaikan cepat" ketika CORS menghalangi pengembangan. Akibatnya, situs mana pun yang dikunjungi pengguna yang sedang login dapat memanggil API Anda dengan cookie mereka dan membaca balasannya. Perhatikan juga Vary: Origin — tanpa itu, cache dapat menyajikan header CORS milik satu asal kepada asal lain.
CORS adalah aturan yang ditegakkan peramban tentang siapa yang boleh membaca balasan. Ia tidak menghalangi permintaan dari curl, skrip server, atau alat apa pun di luar peramban. Melonggarkan CORS tidak "membuka" API yang tadinya tertutup — API selalu dapat dipanggil langsung. Yang berubah adalah situs mana yang boleh membaca data atas nama pengguna Anda.
SSRF — Server Diperalat Mengetuk Pintu Dalam
// Rentan — URL dari pengguna diambil apa adanya
app.post("/ambil-pratinjau", async (req, res) => {
const hasil = await fetch(req.body.url);
res.send(await hasil.text());
});
Server aplikasi biasanya dapat menjangkau alamat yang tertutup dari internet. Permintaan ke http://169.254.169.254/latest/meta-data/iam/security-credentials/ mengembalikan kredensial IAM sementara milik instans — dan dengan itu penyerang memperoleh hak yang sama seperti aplikasi Anda di cloud. Alamat internal lain seperti panel admin, Redis, dan Elasticsearch juga terbuka lewat jalur yang sama.
Menahan SSRF
import dns from "dns/promises";
import net from "net";
const RENTANG_TERLARANG = [
"127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
"192.168.0.0/16", "169.254.0.0/16", "::1/128", "fc00::/7"
];
async function urlAman(masukan) {
const u = new URL(masukan);
if (!["http:", "https:"].includes(u.protocol)) throw new Error("skema ditolak");
const alamat = await dns.resolve4(u.hostname);
for (const ip of alamat) {
if (diRentangTerlarang(ip)) throw new Error("alamat internal ditolak");
}
return { url: u, ip: alamat[0] }; // sambungkan ke IP ini, bukan resolusi ulang
}
Bila kode memvalidasi nama host, lalu memanggil fetch yang menyelesaikan DNS sekali lagi, penyerang dapat mengendalikan server DNS yang menjawab alamat publik pada pemeriksaan pertama dan 169.254.169.254 pada pemeriksaan kedua. Celah ini dikenal sebagai DNS rebinding. Solusinya adalah menyambung langsung ke alamat IP yang sudah divalidasi, bukan menyelesaikan nama host untuk kedua kalinya.
Lapis tambahan yang lebih kuat: - Jalankan pengambilan URL lewat proksi keluar dengan daftar putih - Tempatkan layanan itu pada jaringan tanpa akses ke rentang internal - Matikan pengikutan pengalihan, atau validasi ulang setiap lompatan - Batasi ukuran balasan dan waktu tunggu
Pemeriksaan Cepat untuk Endpoint API
□ Autentikasi diperiksa — bukan hanya keberadaan token, tapi keabsahannya □ Otorisasi memeriksa kepemilikan sumber daya, bukan sekadar peran □ Masukan divalidasi dengan skema ketat yang menolak field asing □ Laju permintaan dibatasi, terutama pada endpoint mahal □ Galat tidak membocorkan jejak tumpukan atau struktur internal □ Balasan tidak menyertakan field sensitif yang tidak diminta □ Aksi penting tercatat di log audit beserta identitas pelakunya