Tutorial Mencegah Kerentanan BOLA pada REST API Node.js dan Express
Poin Kunci Artikel Ini:
- Autentikasi hanya mengonfirmasi siapa pengguna tersebut.Otorisasi (AuthZ): Memverifikasi hak akses identitas terverifikasi terhadap entitas data spesifik.
- Kode Endpoint Rentan (Vulnerable Pattern)Pada contoh di bawah, middleware hanya mengekstrak payload pengguna dan menempelkannya ke .
- Handler utama mengambil dokumen langsung berdasarkan tanpa melakukan verifikasi kepemilikan.2.
Anatomi Kerentanan BOLA pada Endpoint REST API
Broken Object Level Authorization (BOLA)—sebelumnya dikenal sebagai Insecure Direct Object Reference (IDOR)—konsisten menduduki peringkat pertama dalam OWASP API Security Top 10. Kerentanan ini berakar dari kegagalan desain arsitektur otorisasi, di mana aplikasi mengekspos identifier internal suatu objek dalam endpoint API tanpa memvalidasi secara eksplisit apakah identitas yang meminta memiliki hak akses sah terhadap resource tersebut.
Dalam rekayasa perangkat lunak backend, pemisahan secara tegas antara Autentikasi (AuthN) dan Otorisasi (AuthZ) sangat krusial:
- Autentikasi (AuthN): Memverifikasi klaim identitas pemanggil request via token JWT, cookie sesi, atau API Key. Autentikasi hanya mengonfirmasi siapa pengguna tersebut.
- Otorisasi (AuthZ): Memverifikasi hak akses identitas terverifikasi terhadap entitas data spesifik. Otorisasi menentukan apakah pengguna tersebut diizinkan membaca, mengubah, atau menghapus entitas target.
BOLA terjadi saat pengembang berasumsi bahwa pengguna yang berhasil terautentikasi otomatis berhak mengakses seluruh ID resource yang dikirimkan via parameter HTTP request. Dampak eksploitasi BOLA berkisar dari kebocoran data sensitif (Horizontal Privilege Escalation) hingga pengambilalihan akun dan modifikasi data massal secara ilegal (Vertical Privilege Escalation).
Mekanisme dan Vektor Eksploitasi BOLA
Alur eksploitasi BOLA memanfaatkan pola URL deterministik dan identifier sekuensial. Berikut alur serangan BOLA pada endpoint transaksi:
- Autentikasi Sah: Penyerang mendaftar akun sah, melakukan login, dan menerima token JWT dengan klaim
userId: "usr_101". - Request Legitimat: Penyerang mengakses data profil transaksi milik sendiri melalui
GET /api/v1/orders/5501. Server memvalidasi token dan mengembalikan payload JSON status 200 OK. - Manipulasi Target (Parameter Tampering): Penyerang mengganti parameter ID pada URL menjadi
GET /api/v1/orders/5502tanpa mengubah headerAuthorization. - Kegagalan Validasi Akses: Middleware autentikasi memverifikasi JWT penyerang valid. Namun, handler route langsung mengeksekusi kueri
SELECT * FROM orders WHERE id = 5502tanpa mencocokkan kolomuser_iddari rekaman tersebut denganusr_101. - Kebocoran Data: Server mengembalikan payload data milik korban (
usr_102) dengan respon HTTP 200 OK. Penyerang kemudian menjalankan skrip enumerasi otomatis untuk mengunduh seluruh isi tabelorders.
Simulasi Kode Rentan dan Pattern Perbaikan pada Express.js
Berikut adalah implementasi REST API Node.js menggunakan Express dan Mongoose ODM yang mengandung celah BOLA, diikuti dengan dua pola remedi strategis.
1. Kode Endpoint Rentan (Vulnerable Pattern)
Pada contoh di bawah, middleware authenticateJWT hanya mengekstrak payload pengguna dan menempelkannya ke req.user. Handler utama mengambil dokumen langsung berdasarkan req.params.id tanpa melakukan verifikasi kepemilikan.
const express = require('express');
const app = express();
const { authenticateJWT } = require('./middleware/auth');
const Document = require('./models/Document');
// RENTAN BOLA: Token dikonfirmasi valid, tetapi hak akses terhadap ID objek diabaikan
app.get('/api/v1/documents/:id', authenticateJWT, async (req, res) => {
try {
const document = await Document.findById(req.params.id);
if (!document) {
return res.status(404).json({ success: false, message: 'Dokumen tidak ditemukan' });
}
// Mengembalikan dokumen tanpa memeriksa apakah document.ownerId === req.user.id
return res.status(200).json({ success: true, data: document });
} catch (err) {
return res.status(500).json({ success: false, message: 'Kesalahan internal server' });
}
});2. Eksploitasi Menggunakan cURL
# Penyerang (User ID: usr_101) meminta dokumen rahasia milik User ID: usr_102
curl -i -X GET https://api.perusahaan.com/api/v1/documents/doc_99823 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."3. Remedi 1: Query Scope Context-Bound (Inline Binding)
Pendekatan defensif paling mendasar adalah mengunci kueri basis data dengan menyertakan identifier pemanggil terautentikasi (req.user.id) ke dalam klausul pencarian.
// AMAN: Kueri dibatasi khusus untuk resource yang dimiliki oleh req.user.id
app.get('/api/v1/documents/:id', authenticateJWT, async (req, res) => {
try {
const document = await Document.findOne({
_id: req.params.id,
ownerId: req.user.id // Penguncian otorisasi pada layer database query
});
if (!document) {
// Mengembalikan 404 Not Found untuk menyembunyikan keberadaan resource (Anti-Enumeration)
return res.status(404).json({ success: false, message: 'Dokumen tidak ditemukan' });
}
return res.status(200).json({ success: true, data: document });
} catch (err) {
return res.status(500).json({ success: false, message: 'Kesalahan internal server' });
}
});4. Remedi 2: Middleware Otorisasi Resource Terpusat (Reusable Authorization Layer)
Penulisan penguncian kueri secara manual di setiap controller rawan kelalaian manusia. Pendekatan arsitektural yang lebih tangguh adalah membuat middleware otorisasi terpusat yang dapat digunakan kembali secara konsisten pada berbagai route Express.
// middleware/authorizeResource.js
const authorizeResource = (Model, options = {}) => {
const { idParam = 'id', ownerField = 'ownerId', allowAdmin = true } = options;
return async (req, res, next) => {
try {
const resourceId = req.params[idParam];
const currentUserId = req.user && req.user.id;
const currentUserRole = req.user && req.user.role;
if (!currentUserId) {
return res.status(401).json({ success: false, message: 'Konteks pengguna tidak ditemukan' });
}
const resource = await Model.findById(resourceId);
if (!resource) {
return res.status(404).json({ success: false, message: 'Resource tidak ditemukan' });
}
const isOwner = resource[ownerField].toString() === currentUserId.toString();
const isAdmin = allowAdmin && currentUserRole === 'admin';
if (!isOwner && !isAdmin) {
// Mengembalikan 404 ketimbang 403 untuk mencegah kebocoran keberadaan ID
return res.status(404).json({ success: false, message: 'Resource tidak ditemukan' });
}
// Menempelkan resource ke request context untuk diakses oleh controller selanjutnya
req.resource = resource;
return next();
} catch (err) {
return res.status(500).json({ success: false, message: 'Gagal memverifikasi otorisasi resource' });
}
};
};
module.exports = authorizeResource;Penerapan middleware pada endpoint Express:
const authorizeResource = require('./middleware/authorizeResource');
const Document = require('./models/Document');
app.get(
'/api/v1/documents/:id',
authenticateJWT,
authorizeResource(Document, { ownerField: 'ownerId', allowAdmin: true }),
(req, res) => {
// Controller hanya menerima request yang sudah lolos uji kepemilikan resource
return res.status(200).json({ success: true, data: req.resource });
}
);Mitigasi Tingkat Lanjut: Non-Sequential Identifier, ABAC, dan Continuous Testing
Pencegahan BOLA secara menyeluruh membutuhkan kombinasi antara desain pengenal objek, otorisasi berbasis atribut (Attribute-Based Access Control), dan pengujian otomatis dalam siklus pengembangan perangkat lunak (SDLC).
1. Penggunaan High-Entropy Identifiers (UUID v4 / NanoID)
Gunakan identifier acak dengan tingkat entropi tinggi seperti Universally Unique Identifier (UUID v4) atau NanoID menggantikan Auto-Increment Integer ID. Hal ini mencegah penyerang melakukan tebakan sekuensial (enumeration attack).
const { v4: uuidv4 } = require('uuid');
const mongoose = require('mongoose');
const documentSchema = new mongoose.Schema({
_id: { type: String, default: () => uuidv4() },
title: { type: String, required: true },
ownerId: { type: String, required: true, index: true },
content: String
});Peringatan Keamanan: UUID adalah strategi defense-in-depth untuk menyulitkan enumerasi, bukan pengganti otorisasi. Jika sebuah UUID terekspos melalui HTTP log, referrer header, atau kebocoran sekunder, endpoint tanpa pengecekan otorisasi tetap rentan terhadap serangan BOLA.
2. Fine-Grained Authorization dengan Attribute-Based Access Control (CASL.js)
Ketika aplikasi membutuhkan aturan otorisasi kompleks—seperti kepemilikan bersyarat, peran tim, atau status dokumen—penerapan library ABAC seperti CASL.js sangat direkomendasikan.
const { AbilityBuilder, createMongoAbility } = require('@casl/ability');
function defineAbilitiesFor(user) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
if (user.role === 'admin') {
can('manage', 'all');
} else {
can('read', 'Document', { ownerId: user.id });
can('update', 'Document', { ownerId: user.id, isLocked: false });
cannot('delete', 'Document');
}
return build();
}
// Integrasi pada Express Controller
app.patch('/api/v1/documents/:id', authenticateJWT, async (req, res) => {
const document = await Document.findById(req.params.id);
if (!document) return res.status(404).json({ message: 'Dokumen tidak ditemukan' });
const ability = defineAbilitiesFor(req.user);
// Evaluasi aturan akses CASL
if (ability.cannot('update', document)) {
return res.status(403).json({ success: false, message: 'Anda tidak memiliki hak untuk mengubah dokumen ini' });
}
Object.assign(document, req.body);
await document.save();
return res.json({ success: true, data: document });
});3. Automated Testing dan Static Security Analysis dalam CI/CD
Mencegah regresi kode BOLA membutuhkan integrasi pengujian statis (SAST) dan pengujian otomatis (Integration Testing) pada pipeline CI/CD.
Contoh unit & integration test menggunakan Supertest dan Jest untuk memverifikasi penolakan akses cross-user:
const request = require('supertest');
const app = require('../app');
describe('BOLA Authorization Tests', () => {
it('harus menolak akses ketika User A meminta dokumen milik User B', async () => {
const tokenUserA = 'Bearer TOKEN_USER_A_VALID';
const documentIdUserB = 'doc_user_b_7788';
const response = await request(app)
.get(`/api/v1/documents/${documentIdUserB}`)
.set('Authorization', tokenUserA);
expect([403, 404]).toContain(response.statusCode);
expect(response.body.data).toBeUndefined();
});
});Gunakan pula alat Static Application Security Testing (SAST) seperti Semgrep dengan aturan kustom untuk mendeteksi kueri yang mengabaikan konteks pengguna:
rules:
- id: express-detect-bola-query
patterns:
- pattern: $MODEL.findById(req.params.$ID)
- pattern-not-inside: |
$MODEL.findOne({ _id: req.params.$ID, ownerId: req.user.id })
message: "Potensi BOLA Detected: Kueri model tidak memvalidasi req.user.id"
languages: [javascript, typescript]
severity: WARNINGChecklist Strategi Proteksi API Production
Gunakan daftar periksa teknis berikut saat melakukan proses code review dan audit keamanan backend:
- Mandatory Object-Level Check: Pastikan setiap endpoint berparameter ID memverifikasi kepemilikan objek sebelum memproses data.
- Context-Bound Queries: Selalu sertakan
userIdpengguna terautentikasi ke dalam parameter pencarian database (findOne,updateMany,deleteOne). - Non-Sequential Primary Keys: Gunakan UUID v4 atau NanoID untuk seluruh resource ID yang terekspos ke publik.
- Uniform Error Response (404 Masking): Kembalikan HTTP 404 Not Found ketimbang 403 Forbidden pada akses tak berwenang untuk mencegah pembocoran keberadaan data (resource enumeration).
- Centralized Authorization Layer: Terapkan middleware otorisasi terpusat atau library ABAC/RBAC teruji untuk menghindari logika manual pada controller.
- Negative Integration Test Suite: Tulis pengujian integrasi otomatis yang mensimulasikan permintaan resource lintas pengguna (User A ke Resource B).
- SAST & DAST Integration: Jalankan pemindaian statis Semgrep dan pemindaian dinamis OWASP ZAP secara terotomatisasi pada pipeline CI/CD.
Kesimpulan
Kerentanan BOLA terjadi akibat kegagalan asumsi bahwa autentikasi saja cukup untuk mengamankan data. Dalam merancang REST API yang aman pada Node.js dan Express, otorisasi berbasis objek harus diterapkan secara eksplisit pada setiap titik akses resource. Dengan mengombinasikan middleware otorisasi terpusat, pengenal berentropi tinggi, pengujian integrasi negatif, serta analisis keamanan otomatis dalam CI/CD, risiko kebocoran data akibat BOLA dapat diminimalkan secara efektif di lingkungan produksi.


