Validasi upload PDF aman tidak cukup dilakukan dengan memeriksa ekstensi file atau header Content-Type dari browser. Jika backend menerima PDF dari pengguna, ancaman yang realistis mencakup polyglot file, file terkompresi yang meledak saat diproses, eksploit terhadap parser, metadata berbahaya, SSRF saat parser mencoba mengambil aset eksternal, dan abuse upload dalam jumlah besar.
Pendekatan yang lebih aman adalah membuat pipeline parsing yang eksplisit, kecil, dapat divalidasi, dan terisolasi. Ide ini sejalan dengan semangat desain dokumen yang ketat: backend tidak memperlakukan PDF sebagai blob yang “mungkin valid”, tetapi sebagai input tak tepercaya yang harus lolos serangkaian pemeriksaan sempit sebelum diterima, disimpan, atau diproses lebih lanjut.
Mengapa validasi upload PDF biasa sering gagal
Banyak implementasi hanya melakukan tiga hal: memeriksa ekstensi .pdf, membaca Content-Type: application/pdf, lalu menyimpan file. Ini lemah karena seluruh informasi itu bisa dipalsukan oleh klien.
- Ekstensi file hanya nama. File
invoice.pdfbisa berisi data lain. - MIME dari klien tidak bisa dipercaya. Browser atau script dapat mengirim nilai apa pun.
- Parser langsung di proses utama membuat bug parsing berubah menjadi crash, kebocoran resource, atau bahkan pintu masuk eksploitasi.
Masalah utamanya bukan hanya “apakah file ini PDF”, tetapi juga apa yang akan dilakukan sistem terhadap file itu. Begitu file dipratinjau, diindeks, diekstrak teksnya, atau dibaca jumlah halamannya, Anda sudah memasuki wilayah parser dan semua risiko yang menyertainya.
Model ancaman untuk upload PDF
1. Polyglot file
Polyglot adalah file yang valid di lebih dari satu format, atau sengaja dibuat agar lolos pemeriksaan sederhana tetapi mengeksekusi perilaku lain di sistem berbeda. Misalnya, file bisa dimulai dengan signature PDF namun menyimpan konten lain di bagian tertentu.
Mitigasi utamanya adalah signature check + parser validation + normalisasi hasil, bukan hanya satu pemeriksaan.
2. Zip bomb dan decompression abuse
PDF dapat berisi stream terkompresi. Jika parser mengekspansi stream besar tanpa batas, file kecil di jaringan dapat menjadi penggunaan CPU, RAM, atau disk yang tidak proporsional.
Mitigasi: batasi ukuran file mentah, ukuran hasil ekstraksi, jumlah objek, jumlah halaman, dan waktu parsing.
3. Parser exploit
PDF adalah format kompleks. Banyak parser mendukung fitur lama, objek terkompresi, font terbenam, lampiran, JavaScript, form, anotasi, dan struktur yang tidak sederhana. Kompleksitas ini memperluas permukaan serangan.
Mitigasi paling penting adalah memproses file di proses terisolasi atau container terpisah, dengan hak akses minimum dan batas resource ketat.
4. Metadata berbahaya
Nama file, judul dokumen, author, subject, embedded file name, dan metadata lain sering berakhir di log, database, UI, atau header HTTP. Jika tidak dinormalisasi, metadata dapat memicu masalah seperti XSS tersimpan, log injection, atau karakter tak terduga pada filesystem.
5. SSRF lewat fetch aset
Beberapa pipeline dokumen atau layanan konversi dapat mengambil resource eksternal, misalnya font, URL lampiran, atau referensi lain. Jika proses parser memiliki akses jaringan, file berbahaya bisa memaksa sistem melakukan request ke internal network.
Mitigasi: proses parsing tanpa akses egress kecuali benar-benar diperlukan.
6. Abuse upload massal
Serangan tidak selalu canggih. Upload massal file valid namun besar dapat menguras penyimpanan, antrean kerja, CPU parser, dan biaya object storage.
Mitigasi: rate limit, kuota pengguna, pemrosesan asinkron, dan retensi file gagal yang pendek.
Prinsip desain pipeline yang ketat
Alih-alih membiarkan satu library “menerima semua lalu mencoba memahami sisanya”, lebih aman membangun pipeline dengan tahapan yang kecil dan jelas. Setiap tahap punya input, output, batasan, dan alasan kegagalan yang bisa diaudit.
1. Allowlist MIME dan signature
Gunakan dua lapis verifikasi awal:
- Allowlist MIME: hanya terima
application/pdfsebagai sinyal awal, bukan bukti final. - Magic number / signature: verifikasi header PDF seperti awalan
%PDF-.
Namun jangan berhenti di sini. Signature valid belum menjamin struktur aman.
2. Batas ukuran dan kompleksitas
Tentukan kebijakan eksplisit, misalnya:
- ukuran file maksimum,
- jumlah halaman maksimum,
- jumlah objek maksimum,
- batas waktu parsing,
- batas memori proses parser,
- batas ukuran hasil ekstraksi teks atau metadata.
Nilai pastinya tergantung kebutuhan bisnis. Sistem invoice internal bisa jauh lebih ketat daripada sistem arsip legal.
3. Parsing di proses terisolasi
Jangan jalankan parser di proses web utama. Gunakan worker terpisah, subprocess, atau container khusus dengan:
- user non-root,
- filesystem sementara yang terbatas,
- tanpa akses secret aplikasi utama,
- pembatas CPU dan memori,
- timeout keras,
- opsional tanpa jaringan keluar.
Tujuannya bukan membuat parser “tak bisa rusak”, tetapi memastikan kerusakan tidak meluas ke aplikasi inti.
4. Normalisasi nama file dan metadata
Simpan nama asli hanya sebagai data tampilan setelah dibersihkan. Untuk penyimpanan fisik, gunakan nama acak atau ID internal. Hindari menggunakan nama file pengguna sebagai path langsung.
Praktik yang aman:
- hapus separator path seperti
/dan\, - batasi panjang nama,
- normalisasi Unicode jika perlu,
- hapus karakter kontrol,
- escape saat ditampilkan di HTML atau log.
5. Simpan ke object storage, bukan filesystem aplikasi publik
Setelah file lolos, simpan ke object storage atau bucket privat. Hindari meletakkan upload mentah langsung di direktori yang dilayani web server. Ini mengurangi risiko eksekusi tidak sengaja, traversal, dan kebocoran file sementara.
6. Audit log yang bisa ditindaklanjuti
Setiap kegagalan validasi harus punya alasan terstruktur: signature mismatch, timeout parser, halaman melebihi batas, metadata invalid, dan sebagainya. Log seperti ini penting untuk debugging, forensik, dan tuning kebijakan.
Alur verifikasi step-by-step
Berikut alur yang praktis untuk backend produksi. Anda bisa menjalankannya sinkron untuk file kecil, tetapi umumnya lebih aman dibuat asinkron melalui queue.
- Terima upload ke area sementara dengan batas ukuran pada reverse proxy dan aplikasi.
- Hitung hash file untuk deduplikasi, audit, atau korelasi insiden.
- Periksa ukuran mentah. Tolak lebih awal jika melewati batas.
- Periksa MIME yang terdeteksi server-side, bukan hanya dari klien.
- Periksa signature PDF pada beberapa byte awal.
- Kirim file ke worker terisolasi untuk parsing minimal.
- Parser membaca struktur dasar: jumlah halaman, objek penting, stream, metadata inti.
- Terapkan batas kompleksitas: halaman, objek, waktu, memori, ukuran ekstraksi.
- Matikan fitur berisiko jika parser mendukungnya, seperti akses jaringan atau eksekusi komponen tambahan.
- Opsional scan antivirus sebagai lapisan tambahan, bukan pengganti parsing ketat.
- Normalisasi metadata dan nama file.
- Pindahkan file yang lolos ke object storage privat.
- Simpan hasil validasi dan audit log.
- Baru setelah itu file tersedia untuk workflow lanjutan seperti preview, OCR, atau indexing.
Catatan: jika Anda perlu membuat thumbnail, ekstrak teks, atau OCR, lakukan sebagai tahap lanjutan yang terpisah dari validasi awal. Jangan mencampur semua proses ke satu worker tanpa batasan berbeda.
Contoh arsitektur backend yang aman
Komponen minimum
- API upload: menerima file dan metadata dasar.
- Temporary storage: lokasi singkat sebelum lolos validasi.
- Validation queue: antrean untuk memproses file secara asinkron.
- Isolated parser worker: container atau subprocess dengan limit ketat.
- Object storage privat: tujuan akhir file yang lolos.
- Audit/event log: mencatat semua keputusan validasi.
Alur data
API tidak perlu membaca seluruh isi PDF di memori. Simpan stream ke storage sementara, catat record database dengan status pending_validation, lalu kirim job ke worker. Worker mengambil file, menjalankan validasi, dan mengembalikan hasil terstruktur. Status berubah menjadi accepted atau rejected.
Pola ini membatasi dampak file bermasalah pada latensi request pengguna dan memudahkan penerapan timeout keras.
Pseudocode pipeline validasi
function handleUpload(request, userContext) {
enforceRateLimit(userContext)
tempFile = streamToTempStorage(request.file)
originalName = request.file.name
safeDisplayName = sanitizeDisplayName(originalName)
if (tempFile.size > MAX_RAW_SIZE) {
rejectAndLog("raw_size_exceeded", tempFile)
return error("File terlalu besar")
}
detectedMime = detectMime(tempFile)
if (detectedMime != "application/pdf") {
rejectAndLog("mime_mismatch", tempFile)
return error("Format file tidak didukung")
}
if (!hasPdfSignature(tempFile)) {
rejectAndLog("signature_invalid", tempFile)
return error("Signature PDF tidak valid")
}
fileId = createUploadRecord({
status: "pending_validation",
original_name: safeDisplayName,
sha256: sha256(tempFile),
size: tempFile.size
})
enqueueValidationJob(fileId, tempFile.path)
return accepted({ file_id: fileId, status: "pending_validation" })
}
function validatePdfJob(fileId, tempPath) {
result = runInIsolatedProcess({
file: tempPath,
timeoutMs: PARSER_TIMEOUT_MS,
memoryLimitMb: PARSER_MEMORY_LIMIT_MB,
networkDisabled: true
}, function () {
doc = strictPdfParse(tempPath)
return {
pageCount: doc.pageCount,
objectCount: doc.objectCount,
hasEmbeddedFiles: doc.hasEmbeddedFiles,
metadata: extractSafeMetadata(doc)
}
})
if (result.timedOut) {
reject(fileId, "parser_timeout")
return
}
if (!result.ok) {
reject(fileId, "parser_failed")
return
}
if (result.pageCount > MAX_PAGE_COUNT) {
reject(fileId, "page_limit_exceeded")
return
}
if (result.objectCount > MAX_OBJECT_COUNT) {
reject(fileId, "object_limit_exceeded")
return
}
if (result.hasEmbeddedFiles && !ALLOW_EMBEDDED_FILES) {
reject(fileId, "embedded_files_not_allowed")
return
}
finalKey = moveToPrivateObjectStorage(tempPath, fileId)
markAccepted(fileId, {
storage_key: finalKey,
metadata: normalizeMetadata(result.metadata)
})
}Pseudocode di atas sengaja menempatkan parsing berat di fungsi terisolasi. Dengan begitu, API utama hanya menangani kontrol alur, bukan detail pemrosesan PDF.
Implementasi hardening yang sering terlewat
Blokir akses jaringan dari worker parser
Jika parser, converter, atau tool lain memiliki perilaku mengambil resource eksternal, nonaktifkan akses egress di level container, namespace jaringan, firewall, atau runtime sandbox. Ini adalah pertahanan efektif terhadap SSRF dan kebocoran metadata ke luar.
Gunakan direktori sementara terpisah dan bersihkan otomatis
File upload yang gagal validasi tidak boleh menumpuk. Terapkan retention policy pendek untuk file di area sementara. Pastikan pembersihan aman terhadap race condition dan tidak menggunakan path dari input pengguna.
Bedakan validasi awal dan pemrosesan lanjutan
Preview, OCR, ekstraksi teks penuh, klasifikasi dokumen, dan indexing search sering lebih mahal serta lebih berisiko daripada validasi dasar. Jangan jalankan semuanya sebelum file dinyatakan lolos.
Antivirus bersifat tambahan
Antivirus dapat membantu mendeteksi sampel yang dikenal, tetapi tidak menggantikan parser ketat dan isolasi proses. File bisa lolos scan namun tetap menyebabkan parser kehabisan resource atau memicu bug.
Jangan percaya metadata untuk keputusan keamanan
Misalnya, jangan izinkan file karena title berisi “Invoice”, atau gunakan author untuk menentukan tenant. Metadata PDF mudah dimanipulasi.
Checklist implementasi
- Batasi ukuran upload di reverse proxy dan aplikasi.
- Gunakan allowlist MIME dan verifikasi signature PDF.
- Hash file untuk audit dan deduplikasi opsional.
- Proses parsing di worker, subprocess, atau container terisolasi.
- Terapkan timeout, limit memori, dan limit CPU.
- Nonaktifkan akses jaringan pada worker parser jika memungkinkan.
- Batasi jumlah halaman, jumlah objek, dan ukuran ekstraksi.
- Normalisasi nama file dan metadata sebelum disimpan atau ditampilkan.
- Simpan file lolos ke object storage privat.
- Jangan sajikan upload mentah langsung dari web root.
- Terapkan rate limit, kuota pengguna, dan kontrol concurrency job.
- Pisahkan validasi awal dari OCR/preview/indexing.
- Tambahkan audit log terstruktur untuk setiap alasan gagal.
- Opsional: scan antivirus sebagai lapisan tambahan.
- Bersihkan file sementara secara otomatis dan aman.
Trade-off keamanan vs biaya
Isolasi proses menambah overhead
Menjalankan parser di container atau subprocess menambah latensi, kompleksitas deployment, dan biaya komputasi. Namun trade-off ini biasanya layak jika upload berasal dari publik atau tenant eksternal.
Jika sistem hanya dipakai internal dengan volume rendah, subprocess lokal dengan limit resource mungkin cukup. Untuk sistem multi-tenant atau internet-facing, container terpisah tanpa egress lebih masuk akal.
Batas ketat bisa menolak file sah
Dokumen legal, laporan CAD, atau scan arsip bisa sangat besar atau memiliki banyak halaman. Jika batas terlalu rendah, pengguna sah akan terganggu. Solusinya bukan melonggarkan semua aturan, tetapi membuat kelas kebijakan berdasarkan use case.
Antivirus menambah waktu proses
Jika setiap file harus discan sebelum diterima, throughput turun. Banyak sistem memilih pola: validasi struktural dulu, simpan di bucket karantina, scan asinkron, lalu promosi ke bucket final setelah lolos.
Kesalahan umum dan cara debugging
1. Hanya gagal dengan pesan “invalid file”
Masalah ini menyulitkan operasi. Simpan kode alasan internal seperti signature_invalid, parser_timeout, atau page_limit_exceeded. Untuk pengguna akhir, tampilkan pesan ringkas; untuk operator, simpan detailnya.
2. Mengukur batas hanya dari ukuran file mentah
File 2 MB bisa jauh lebih mahal diproses daripada file 20 MB. Pantau juga waktu parsing, jumlah halaman, objek, dan memori worker.
3. Menaruh file gagal di storage permanen
Ini menambah biaya dan memperluas permukaan paparan. Gunakan bucket atau direktori karantina dengan retensi pendek.
4. Mengizinkan parser yang sama untuk semua tahap
Tool yang baik untuk ekstraksi teks belum tentu ideal untuk validasi minimal. Pertimbangkan pipeline bertahap: parser minimal untuk verifikasi awal, lalu tool lain untuk fitur lanjutan jika file sudah lolos.
5. Tidak mengamati pola abuse
Tambahkan metrik seperti jumlah upload per user, rasio gagal validasi, timeout parser, dan ukuran file rata-rata. Ini membantu membedakan bug parser dari serangan massal atau penyalahgunaan API.
Penutup
Validasi upload PDF aman dengan parser ketat dan isolasi proses pada dasarnya adalah soal memperkecil kepercayaan dan memperjelas tahapan. Jangan percaya ekstensi, jangan parsing di proses utama, jangan izinkan akses jaringan yang tidak perlu, dan jangan gabungkan validasi dengan pemrosesan lanjutan tanpa batas resource.
Jika Anda harus memilih prioritas implementasi, urutannya sederhana: allowlist MIME + signature, batas ukuran/halaman, parser di proses terisolasi, timeout, rate limit, normalisasi nama file, penyimpanan privat, dan audit log. Antivirus bisa menjadi lapisan tambahan, tetapi fondasi keamanannya tetap ada pada pipeline yang kecil, eksplisit, dan dapat divalidasi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!