Pada aplikasi pembaca dokumen web—misalnya layanan yang menerima PDF atau EPUB lalu menampilkan preview, sinkronisasi progres baca, dan akses lintas perangkat—dua area yang paling sering diremehkan adalah upload file dan session management. Jika dua area ini lemah, penyerang tidak perlu mencari celah rumit: cukup unggah file berbahaya, memanfaatkan validasi MIME yang dangkal, menebak endpoint preview/sync, atau menahan session ID korban lewat session fixation.
Hardening upload & session untuk aplikasi pembaca dokumen web berarti membangun alur yang aman dari awal: autentikasi yang benar, rotasi session setelah login, cookie dengan atribut tepat, validasi file berlapis, penyimpanan terisolasi, pemindaian asinkron, signed URL untuk akses file, rate limiting per user/IP, dan audit log yang cukup untuk investigasi. Fokus artikel ini adalah implementasi backend yang praktis dan framework-agnostik.
Konteks use case dapat dibayangkan seperti ekosistem pembaca dokumen modern bergaya perangkat E-Ink: pengguna mengunggah dokumen, membuka preview di web, lalu menyinkronkan metadata atau posisi baca. Pembahasan ini bukan tentang perangkat keras, melainkan tentang sisi aplikasi web yang melayani alur tersebut.
Ancaman nyata pada aplikasi pembaca dokumen
1. Upload file berbahaya
Masalah paling umum adalah backend mempercayai nama file, ekstensi, atau header Content-Type dari klien. Ini tidak cukup. Penyerang dapat mengunggah file dengan ekstensi ganda seperti laporan.pdf.php, file yang berpura-pura menjadi PDF, arsip berisi payload berbahaya, atau dokumen yang memicu parser rentan pada proses preview/konversi.
Risikonya bukan hanya remote code execution. Dalam banyak sistem, dampak yang lebih realistis adalah:
- file non-dokumen tersimpan di lokasi yang dapat diakses publik,
- server kehabisan memori atau disk karena file besar atau decompression bomb,
- engine preview/cron worker macet saat memproses file rusak,
- metadata sensitif bocor lewat endpoint download/preview.
2. Validasi MIME/ekstensi yang lemah
Validasi upload sering hanya memeriksa:
- ekstensi file ada di daftar putih,
- header
Content-Typedari browser, - ukuran file tanpa inspeksi isi.
Ini lemah karena semua informasi tersebut dapat dimanipulasi dari sisi klien. Pemeriksaan harus dilakukan di server dengan kombinasi beberapa sinyal: ekstensi yang diizinkan, signature file atau magic bytes, parsing minimal, batas ukuran, dan jika perlu pemindaian antimalware.
3. Session fixation dan cookie tidak aman
Jika aplikasi tidak mengganti session ID setelah login, penyerang dapat memaksa korban menggunakan session yang sudah diketahui sebelumnya. Setelah korban berhasil login, penyerang tinggal memakai session ID yang sama. Ini adalah session fixation.
Masalah lain yang sering muncul:
- cookie session tidak diberi
HttpOnly, sehingga bisa diakses JavaScript saat terjadi XSS, - cookie tidak diberi
Secure, sehingga bisa ikut terkirim lewat koneksi non-TLS, SameSitetidak diatur, sehingga risiko CSRF meningkat,- session terlalu lama aktif tanpa rotasi atau invalidasi.
4. CSRF pada aksi upload, delete, sync, dan rename
Aplikasi pembaca dokumen sering punya aksi yang terlihat “bukan transaksi penting”, seperti menambah dokumen ke library, mengganti judul, menghapus file, atau menyinkronkan catatan. Jika endpoint ini memakai cookie session dan tidak dilindungi CSRF, browser korban bisa mengirim request berbahaya dari situs lain.
5. Secret di environment yang bocor atau salah kelola
Secret tetap sebaiknya disimpan di environment atau secret manager, tetapi itu bukan jaminan aman jika:
- file environment ikut ter-commit ke repository,
- nilai secret dicetak ke log saat startup/error,
- container/image menyimpan secret sebagai layer statis,
- secret dipakai terlalu luas tanpa rotasi.
Pada aplikasi dokumen, secret biasanya melindungi session signing, CSRF token, signed URL, koneksi storage, dan kredensial antrean kerja. Jika salah satu bocor, dampaknya bisa meluas.
6. Abuse lewat endpoint preview dan sync
Endpoint preview/sync tampak tidak berbahaya, tetapi sering menjadi target abuse:
- IDOR: pengguna menebak ID dokumen milik orang lain,
- resource abuse: penyerang memukul endpoint preview berkali-kali untuk memaksa rendering mahal,
- enumeration: endpoint sync mengungkap keberadaan dokumen, user, atau timestamp,
- cache poisoning atau penyalahgunaan signed URL yang terlalu lama berlaku.
Arsitektur alur aman yang direkomendasikan
Alur aman tidak bergantung pada satu kontrol. Ia menggabungkan pemeriksaan berlapis dari autentikasi sampai akses file hasil proses.
Alur tingkat tinggi
- Pengguna login.
- Server memverifikasi kredensial lalu merotasi session ID.
- Cookie session dikirim dengan
HttpOnly,Secure, danSameSiteyang sesuai. - Pengguna meminta upload.
- Server memeriksa autentikasi, otorisasi, CSRF, rate limit, dan batas ukuran request.
- File masuk ke lokasi karantina yang tidak dapat diakses publik.
- Server memvalidasi ekstensi, MIME terdeteksi server-side, signature file, dan metadata dasar.
- Worker async memindai file, mengekstrak metadata, dan jika perlu membuat preview.
- Jika lolos, file dipindah ke storage final terisolasi dan direferensikan lewat ID internal, bukan nama file asli.
- Akses preview/download diberikan melalui signed URL atau proxy endpoint yang memeriksa izin per request.
- Semua aksi penting dicatat ke audit log.
Mengapa perlu karantina dan scan async?
Jika upload langsung tersedia untuk preview atau download, Anda memberi jendela waktu bagi file berbahaya untuk dipakai sebelum pemeriksaan selesai. Karantina memaksa file berada dalam status pending sampai lolos validasi dan pemindaian. Pendekatan async juga membuat request upload tetap responsif tanpa memaksa pengguna menunggu proses berat.
Validasi ownership di setiap endpoint
Jangan hanya memeriksa bahwa user sudah login. Endpoint seperti /documents/{id}/preview, /documents/{id}/download, atau /sync harus memverifikasi bahwa dokumen benar-benar milik user tersebut atau memang dibagikan kepadanya. Ini penting untuk mencegah IDOR.
Hardening session dan autentikasi
Rotasi session setelah login
Setelah autentikasi berhasil, buang session lama dan buat session ID baru. Ini memutus skenario session fixation. Praktik yang sama juga layak dipertimbangkan setelah perubahan hak akses penting, misalnya user menjadi admin, mengganti password, atau mengaktifkan MFA.
// Pseudo-code framework-agnostik
function login(request) {
user = auth.verifyPassword(request.email, request.password)
if (!user) return response(401)
session.invalidateCurrent()
sessionId = session.create({ userId: user.id, issuedAt: now() })
response.setCookie("sid", sessionId, {
httpOnly: true,
secure: true,
sameSite: "Lax",
path: "/",
maxAge: 60 * 60 * 8
})
return response(200, { ok: true })
}Catatan: SameSite=Lax sering cocok untuk aplikasi web biasa karena masih membantu melindungi dari sebagian CSRF pada navigasi lintas situs. Jika aplikasi benar-benar membutuhkan alur lintas situs tertentu, evaluasi SameSite=None dengan syarat Secure wajib aktif.
Cookie session yang aman
Untuk cookie session:
HttpOnly: mencegah akses dari JavaScript.Secure: hanya terkirim lewat HTTPS.SameSite: mengurangi risiko CSRF.Pathsempit jika memungkinkan.- Jangan set
Domainterlalu luas kecuali memang diperlukan lintas subdomain.
Kesalahan umum adalah mengaktifkan Secure hanya di produksi tetapi tidak menguji perilaku cookie pada environment staging yang juga memakai HTTPS. Hasilnya, bug baru muncul setelah rilis.
Session timeout dan invalidasi
Gunakan dua batas waktu:
- idle timeout: sesi mati jika tidak aktif dalam jangka tertentu,
- absolute timeout: sesi tetap harus login ulang setelah batas total tertentu.
Selain itu, invalidasikan semua sesi saat password diubah atau saat terdeteksi insiden seperti reuse token, perubahan lokasi ekstrem, atau login simultan yang mencurigakan.
CSRF untuk endpoint berbasis cookie
Jika autentikasi utama memakai cookie session, endpoint mutasi seperti upload, delete, rename, share, dan sync perlu proteksi CSRF. Token CSRF harus diverifikasi di server dan tidak boleh diganti dengan pemeriksaan Origin saja. Header Origin dan Referer tetap berguna sebagai lapisan tambahan, bukan pengganti.
// Pseudo-code verifikasi CSRF
function requireCsrf(request) {
tokenCookie = request.cookies["csrf"]
tokenHeader = request.headers["x-csrf-token"]
if (!tokenCookie || !tokenHeader) deny(403)
if (!constantTimeEqual(tokenCookie, tokenHeader)) deny(403)
}Hardening upload file: validasi berlapis, bukan satu cek
1. Batasi tipe file yang benar-benar dibutuhkan
Semakin sedikit format yang didukung, semakin kecil permukaan serangan. Jika aplikasi pembaca dokumen Anda hanya perlu PDF dan EPUB, jangan buka pintu untuk semua format arsip, office document, atau image mentah tanpa alasan jelas.
2. Terapkan batas ukuran di beberapa lapisan
Batas ukuran file harus konsisten di:
- reverse proxy atau load balancer,
- server aplikasi,
- kode bisnis upload,
- worker parser/preview.
Tujuannya bukan hanya mencegah disk penuh, tetapi juga menghindari parser memproses file yang tidak realistis untuk use case Anda. Banyak bug operasional muncul karena batas ukuran hanya ada di frontend, sehingga penyerang tinggal memanggil API langsung.
3. Verifikasi ekstensi, MIME, dan signature file
Lakukan pemeriksaan berlapis berikut:
- ekstensi file ada di daftar yang diizinkan,
- MIME type ditentukan dari isi file di server, bukan dari header klien,
- signature file cocok dengan format yang diharapkan,
- opsional: parsing ringan untuk memastikan struktur dasar file valid.
Misalnya, PDF yang sah biasanya memiliki penanda awal format tertentu. EPUB pada dasarnya kontainer ZIP dengan struktur khusus. Namun, memeriksa magic bytes saja belum cukup; file masih bisa valid secara superficial tetapi berbahaya bagi parser tertentu. Di sinilah scan dan sandboxing membantu.
// Pseudo-code validasi upload
function validateUpload(file) {
allowedExt = ["pdf", "epub"]
ext = getLowercaseExtension(file.originalName)
if (!allowedExt.includes(ext)) deny(415, "extension not allowed")
if (file.sizeBytes > MAX_UPLOAD_BYTES) deny(413, "file too large")
mime = detectMimeFromContent(file.tempPath)
if (!isAllowedMimeForExtension(ext, mime)) deny(415, "mime mismatch")
if (!matchesExpectedSignature(file.tempPath, ext)) {
deny(415, "invalid file signature")
}
return true
}4. Ganti nama file, jangan percaya nama asli
Nama file asli boleh disimpan sebagai metadata untuk tampilan UI, tetapi file fisik di storage sebaiknya memakai ID acak atau hash yang tidak bisa ditebak. Ini mencegah masalah path traversal, bentrok nama, dan enumerasi nama file.
Jangan pernah langsung memakai nama asli untuk path storage seperti:
/uploads/{userInputFileName}Gunakan pola seperti:
/quarantine/{tenantId}/{randomId}5. Simpan di lokasi yang tidak dieksekusi dan tidak publik
File upload sebaiknya tidak berada di direktori yang dilayani langsung oleh web server sebagai aset publik. Jika file tersimpan di object storage atau volume terpisah, akses harus melewati signed URL jangka pendek atau endpoint backend yang memeriksa otorisasi.
Tujuannya:
- mencegah file diakses sebelum lolos scan,
- mencegah file diperlakukan sebagai kode yang dapat dieksekusi,
- memudahkan penerapan kebijakan retensi, karantina, dan audit.
6. Scan async dan status file
Tambahkan status file seperti:
pending_scan,ready,rejected,quarantined.
UI dan API hanya boleh menampilkan preview/download untuk status ready. Worker async dapat melakukan:
- pemindaian antimalware,
- ekstraksi metadata aman,
- pembuatan thumbnail/preview,
- normalisasi dokumen jika memang dibutuhkan.
// Pseudo-code alur upload
function uploadDocument(request) {
requireAuth(request)
requireCsrf(request)
rateLimit("upload", request.user.id, request.ip)
file = request.file("document")
validateUpload(file)
docId = db.insert("documents", {
ownerId: request.user.id,
originalName: sanitizeDisplayName(file.originalName),
status: "pending_scan",
sizeBytes: file.sizeBytes
})
quarantinePath = storage.putPrivate("quarantine/" + docId, file.stream)
queue.publish("scan-document", {
documentId: docId,
path: quarantinePath
})
audit.log("document.uploaded", {
actorUserId: request.user.id,
documentId: docId,
ip: request.ip
})
return response(202, { documentId: docId, status: "pending_scan" })
}7. Waspadai parser dan generator preview
Sering kali titik terlemah bukan endpoint upload, melainkan worker yang membuat preview. Jika Anda menjalankan parser pihak ketiga, anggap ia sebagai komponen berisiko:
- jalankan di proses/worker terisolasi,
- batasi CPU, memori, dan waktu eksekusi,
- hindari akses jaringan jika tidak diperlukan,
- jangan berikan akses tulis ke storage final secara luas.
Pendekatan ini mengurangi dampak jika parser crash atau rentan terhadap file yang dirancang khusus.
Preview, download, dan sync: jangan jadi jalur bocor data
Signed URL untuk akses terbatas
Jika file atau hasil preview disajikan dari storage, gunakan signed URL dengan masa berlaku singkat. Signed URL cocok saat Anda ingin menghindari proxy semua byte file melalui aplikasi, tetapi tetap ingin membatasi siapa yang dapat mengaksesnya.
Prinsip pentingnya:
- masa berlaku pendek,
- terikat ke resource tertentu,
- jika perlu, terikat ke metode HTTP tertentu,
- dibuat setelah pemeriksaan otorisasi dilakukan.
// Pseudo-code signed URL
function getPreviewUrl(request, documentId) {
requireAuth(request)
doc = db.findDocument(documentId)
requireOwnerOrSharedAccess(request.user, doc)
if (doc.status !== "ready") deny(409)
url = storage.signUrl(doc.previewPath, {
expiresInSeconds: 300
})
return response(200, { url: url })
}Rate limit per user dan per IP
Rate limit penting bukan hanya untuk login, tetapi juga untuk:
- upload,
- preview generation,
- sync progress/catatan,
- download signed URL,
- search library.
Gunakan kombinasi kunci per user dan per IP. Rate limit per user mencegah abuse dari akun valid, sedangkan per IP membantu meredam serangan anonim atau distribusi yang belum login.
Trade-off: jika rate limit terlalu agresif pada endpoint sync, perangkat pembaca atau aplikasi mobile bisa terlihat gagal menyimpan progres baca. Karena itu, pisahkan kebijakan berdasarkan jenis endpoint. Sync kecil berfrekuensi tinggi perlu batas berbeda dari upload file besar.
Desain endpoint sync yang aman
Endpoint sync sering memproses data kecil tapi sering dipanggil. Hardening yang perlu:
- verifikasi ownership setiap item yang disinkronkan,
- batasi ukuran payload dan jumlah item per request,
- gunakan idempotency atau versi/etag bila ada konflik penulisan,
- audit perubahan penting seperti delete, rename, share, annotation export.
Jangan biarkan klien mengirim path storage, status internal scan, atau field sensitif lain yang seharusnya hanya ditentukan server.
Secret management dan konfigurasi operasional
Secret di environment: benar, tapi belum cukup
Menyimpan secret di environment lebih baik daripada hard-code di source code, tetapi perlu disiplin operasional:
- jangan commit file secret ke repository,
- jangan log seluruh environment saat debug,
- batasi akses secret per service,
- rotasi secret penting secara berkala,
- pisahkan secret dev, staging, dan production.
Untuk aplikasi pembaca dokumen, prioritas secret biasanya meliputi:
- kunci penandatanganan session/cookie,
- kunci CSRF atau app secret umum,
- kredensial database, queue, dan object storage,
- kunci penandatanganan signed URL atau token internal.
Contoh konfigurasi keamanan minimum
# Pseudo-config
APP_ENV=production
APP_DEBUG=false
SESSION_COOKIE_NAME=sid
SESSION_COOKIE_HTTPONLY=true
SESSION_COOKIE_SECURE=true
SESSION_COOKIE_SAMESITE=Lax
SESSION_IDLE_TIMEOUT_SECONDS=1800
SESSION_ABSOLUTE_TIMEOUT_SECONDS=28800
UPLOAD_MAX_BYTES=26214400
ALLOWED_UPLOAD_TYPES=pdf,epub
SIGNED_URL_TTL_SECONDS=300
CSRF_PROTECTION_ENABLED=trueNama kunci konfigurasi di atas sengaja generik. Implementasi nyata akan berbeda per framework, tetapi prinsipnya tetap sama.
Audit log, monitoring, dan debugging insiden
Apa yang perlu dicatat
Audit log sebaiknya mencatat aksi yang relevan secara keamanan dan investigasi, misalnya:
- login sukses/gagal, logout, password change,
- rotasi dan invalidasi session,
- upload dokumen, hasil scan, perubahan status file,
- permintaan preview/download, terutama yang ditolak,
- share/unshare dokumen, delete, rename, restore,
- rate limit trigger, token/signature invalid, CSRF failure.
Catat ID user, ID dokumen, timestamp, IP, user agent ringkas, dan hasil aksi. Hindari menyimpan data rahasia atau isi dokumen mentah ke log.
Tips debugging saat hardening mulai diterapkan
- Banyak 403 setelah deploy: cek mismatch CSRF token, atribut
SameSite, atau reverse proxy yang mengubah header. - Cookie session tidak tersimpan: pastikan HTTPS aktif dan atribut
Securesesuai lingkungan. - Upload sah ikut ditolak: bandingkan hasil deteksi MIME server-side dengan format yang benar-benar Anda dukung; jangan hanya melihat ekstensi.
- Preview timeout: cek worker parser, batas memori, dan apakah file besar lolos terlalu awal.
- Signed URL bocor: kurangi TTL, audit titik pembangkitan URL, dan jangan cache URL terlalu lama di frontend.
Checklist implementasi backend
- Rotasi session ID setelah login dan aksi sensitif.
- Set cookie session dengan
HttpOnly,Secure, danSameSiteyang tepat. - Terapkan CSRF protection pada semua endpoint mutasi berbasis cookie.
- Batasi format file ke daftar minimum yang dibutuhkan.
- Validasi upload dengan ekstensi, MIME server-side, signature file, dan batas ukuran.
- Simpan upload di storage privat/non-publik dengan nama internal acak.
- Gunakan status file: pending, ready, rejected, quarantined.
- Scan file dan buat preview secara async di worker terisolasi.
- Jangan izinkan preview/download sebelum status file
ready. - Gunakan signed URL jangka pendek atau proxy endpoint dengan cek otorisasi.
- Verifikasi ownership pada setiap endpoint dokumen, preview, download, dan sync.
- Terapkan rate limit per user dan per IP untuk login, upload, preview, dan sync.
- Audit log untuk login, upload, scan result, akses file, delete, share, dan rate limit.
- Kelola secret di environment atau secret manager tanpa mengeksposnya ke log atau repository.
- Nonaktifkan mode debug di production dan sanitasi pesan error.
Kesalahan umum yang perlu dihindari
- Menganggap pemeriksaan ekstensi sudah cukup.
- Menyimpan file upload di folder publik agar mudah diakses frontend.
- Tidak merotasi session setelah login.
- Mengandalkan CORS sebagai pengganti CSRF protection.
- Menggunakan signed URL dengan masa berlaku terlalu panjang.
- Melupakan otorisasi pada endpoint preview karena dianggap hanya membaca data.
- Tidak memberi batas ukuran pada endpoint sync dan parser preview.
- Mencetak secret, token, atau session ID ke log saat debug.
Penutup
Hardening upload & session untuk aplikasi pembaca dokumen web bukan satu fitur, melainkan rangkaian kontrol yang saling melengkapi. Session yang aman tanpa upload yang aman tetap berbahaya; validasi upload yang ketat tanpa otorisasi preview juga tetap bisa bocor data.
Jika Anda ingin memulai dari prioritas tertinggi, urutannya sederhana: rotasi session setelah login, amankan cookie, aktifkan CSRF, batasi tipe dan ukuran file, simpan upload di storage privat, lakukan scan async, lalu tutup akses file dengan signed URL dan pemeriksaan ownership. Setelah itu, tambahkan rate limit dan audit log yang memadai. Dengan pendekatan ini, aplikasi pembaca dokumen akan jauh lebih tahan terhadap serangan yang paling sering terjadi di dunia nyata.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!