Mempratinjaukan sebuah PDF yang tidak dipercaya di dalam aplikasi Anda sendiri adalah sebuah keputusan eksekusi, dan bagian yang penting bukanlah tampilan chrome viewer-nya, melainkan apa yang menolak dilakukan oleh panel itu dengan sendirinya. Jangan menulis file itu ke disk. Jangan biarkan link-nya menjalankan shell. Jangan serahkan sebuah path kepada lampirannya. Sebagian besar kerusakan dari sebuah dokumen jahat datang bukan dari sebuah eksploit engine, melainkan dari sebuah viewer yang melakukan hal-hal yang sepenuhnya biasa terhadap input yang dipasok penyerang: membuka sebuah link file:// ke sebuah UNC share yang membocorkan kredensial NTLM, meninggalkan sebuah salinan yang di-stage di direktori temp, menyalin payload yang disematkan ke mana pun yang diperintahkan oleh sebuah string nama file. PDFium Component adalah sebuah viewer PDF dengan source code untuk Delphi, C++Builder, dan Lazarus, dan ia menempatkan saklar-saklar yang relevan di tempat yang bisa Anda jangkau: sebuah flag saat-muat yang mematikan scripting, event klik-link yang dapat Anda veto, akses lampiran yang berjalan melalui kode Anda sendiri, dan bit izin yang dapat Anda baca. Urutan di bawah ini mengikuti sebuah dokumen sejak saat ia tiba hingga saat seorang pengguna mengklik sesuatu di dalamnya
Model ancaman dari sebuah panel pratinjau
Bersikaplah jujur soal apa yang sebenarnya diberikan oleh "pratinjau aman" kepada Anda. Renderer itu mengurai byte yang tidak dipercaya apa pun yang Anda lakukan, dan hardening milik engine itu sendiri adalah lantai tempat Anda berpijak. Segala sesuatu di atas lantai itu adalah kebijakan aplikasi: apakah script diinisialisasi, apa yang dilakukan sebuah klik link, apakah file yang disematkan dapat mencapai disk, apakah clipboard dan printer adalah pintu atau tembok. Satu hal yang sebaiknya dicoret sejak awal adalah saklar FPDF_SetSandBoxPolicy milik engine. Sebagian besar pembatasan engine sudah dikompilasi ke dalamnya, saklar itu mengubah sedikit sekali dalam praktiknya, dan mengalokasikan sebagian dari cerita isolasi Anda padanya hanya menghasilkan rasa aman yang palsu seolah-olah sudah melakukan sesuatu. Ketika input-nya benar-benar jahat, katakanlah sebuah portal unggah publik, satu-satunya isolasi yang sesungguhnya adalah me-render dalam sebuah proses berprivilese-rendah yang terpisah dan mengirimkan bitmap ke UI. Flag in-process adalah kebijakan. Bukan containment
Dua permukaan mudah terlupakan justru karena tidak ada klik yang pernah menyentuhnya. Yang pertama adalah file sementara. Jika pipeline Anda men-stage dokumen masuk ke disk sebelum pratinjau, salinan yang di-stage itu bertahan lebih lama dari sesi kecuali sesuatu menghapusnya secara terverifikasi, dan sebuah file yang "dapat dipulihkan dari direktori temp" telah diam-diam mengalahkan setiap kontrol yang diberlakukan oleh panel itu sendiri. Muat dari memori melalui TPdfStreamAdapter sebagai gantinya, sehingga byte yang jahat tidak pernah mendapatkan path miliknya sendiri. Yang kedua adalah clipboard. Sebuah pratinjau yang mengizinkan select-and-copy telah mengekspor dokumen itu, satu layar penuh pada satu waktu, dan tidak ada intersepsi link yang akan menangkap itu
Matikan JavaScript saat load, bukan di UI
JavaScript dokumen pada PDFium Component hanya diinisialisasi bersama-sama dengan lingkungan form-fill. Memuat dengan FormFill := False karena itu menonaktifkan scripting pada akarnya, bukan sekadar menekan gejalanya:
procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
Pdf.FileName := FilePath;
Pdf.FormFill := False; // tanpa lingkungan form, sehingga tanpa engine JavaScript
Pdf.Active := True;
FPermissions := Pdf.Permissions; // kata flag mentah; semua bit diset = tanpa batasan
end;
Trade-off ini nyata dan layak dituliskan dalam spek Anda. Dengan form fill dinonaktifkan, interaksi AcroForm dan script validasi yang sah pun ikut hilang; field dirender dengan tampilan terakhir yang tersimpan tetapi tidak dapat diedit. Untuk sebuah panel pratinjau, itu biasanya keputusan yang tepat, karena preview berarti melihat, bukan mengisi. Tetapi jika jendela yang sama juga berfungsi ganda sebagai permukaan pengisian form untuk dokumen internal yang dipercaya, jawabannya adalah dua jalur pemuatan dengan sebuah keputusan kepercayaan yang eksplisit di antara keduanya, bukan satu jalur dengan sebuah pengaturan kompromi yang terlalu longgar untuk kasus jahat dan terlalu ketat untuk kasus yang dipercaya. Sisi pengisian-form dari pemisahan itu memiliki jebakannya sendiri, dibahas dalam navigasi field form dan pembuatan ulang appearance
Link: handler default-nya menjalankan shell
Jika dibiarkan, klik link langsung menuju sistem operasi. LinkOptions default milik viewer mencakup loAutoOpenURI, yang merupakan kebocoran file://-ke-UNC-share yang menunggu untuk terjadi. Dua event membentuk titik penyempitan (choke point): OnWebLinkClick untuk URL yang terdeteksi dalam teks halaman, dan OnAnnotationLinkClick untuk anotasi link yang membawa aksi URI atau launch. Set Handled := True pada keduanya, tanpa syarat, sebelum memutuskan apa pun, lalu izinkan kembali hanya apa yang diizinkan oleh kebijakan. Sebagai lapisan kedua, hapus loAutoOpenURI dari LinkOptions untuk input yang jahat dan pastikan loAutoLaunch, yang mati secara default, tidak pernah menyusup kembali melalui sebuah config yang disalin:
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
const Url: WString; var Handled: Boolean);
begin
Handled := True; // jangan pernah jatuh ke perilaku shell default
if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
and HostIsAllowed(Url) then
OpenInBrowser(Url)
else
FAudit.LogBlockedLink(FDocumentId, Url);
end;
Dua detail menentukan apakah ini benar-benar bertahan. Pertama, pemeriksaan skema harus berupa sebuah pemeriksaan prefix pada string mentah sebelum parsing apa pun, karena file://, UNC path, dan skema eksotis adalah persis nilai-nilai yang membuat sebuah URL parser naif crash atau lolos melewati sebuah parser yang menormalisasi terlalu bersemangat. Kedua, catat setiap pemblokiran dengan identitas dokumen yang dilampirkan. Segelintir link file:// yang diblokir adalah noise latar belakang; sebuah ledakan link semacam itu di banyak dokumen masuk dalam jendela waktu yang singkat adalah sebuah insiden yang lebih baik didengar oleh tim keamanan Anda dari Anda daripada dari tempat lain
Lampiran: kebijakan ekstensi dan nama file yang bukan Anda yang memilih
Sebuah PDF adalah kontainer, dan AttachmentCount bersama properti AttachmentName[] memberi tahu Anda apa yang dibawanya sebelum apa pun menyentuh disk. Dua kontrol terpisah penting di sini, dan hanya satu di antaranya yang jelas. Yang jelas adalah kebijakan tipe: sebuah allowlist ekstensi yang boleh diekspor kapan pun. Yang halus adalah bahwa nama lampiran itu adalah data yang dikendalikan penyerang, titik. Sebuah nama yang disematkan seperti ..\..\Startup\update.exe mengubah sebuah save yang ceroboh menjadi sebuah path traversal yang menjatuhkan sebuah executable ke dalam sebuah folder yang dijalankan Windows saat login. Komponen ini menyerahkan payload kepada Anda sebagai byte melalui Attachment[] dan membiarkan kode Anda memilih path-nya, jadi bangun path itu dari sebuah basename yang sudah disanitasi dan jangan pernah dari string mentah yang disematkan:
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
RawName, SafeName, Ext: string;
Data: TBytes;
begin
RawName := string(Pdf.AttachmentName[Index]);
SafeName := ExtractFileName(RawName); // menghapus komponen path apa pun
Ext := LowerCase(ExtractFileExt(SafeName));
if not FAllowedExt.Contains(Ext) then // allowlist, bukan blocklist
raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);
Data := Pdf.Attachment[Index]; // payload yang disematkan sebagai byte mentah
TFile.WriteAllBytes(
IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;
Lebih pilih arah allowlist. Sebuah blocklist ekstensi "berbahaya" adalah sebuah perlombaan yang Anda kalah pada hari seseorang menjadikan senjata sebuah ekstensi yang belum pernah Anda dengar; sebuah allowlist berisi .pdf, .png, dan .csv gagal secara tertutup (fails closed)
Apa yang sesungguhnya dijanjikan oleh izin enkripsi
Standard security handler milik ISO 32000-1 mengkodekan flag izin untuk pencetakan, penyalinan konten, dan modifikasi, dan properti Permissions serta UserPermissions memunculkannya sebagai bitmask mentah begitu dokumen terbuka. ISO 32000-1 Tabel 22 mendefinisikan bit-bit itu, dan sebuah file yang tidak terenkripsi melaporkan semua bit diset. Baca itu dan hormati dalam lapisan command Anda, tetapi bersikaplah jelas soal apa sebenarnya itu. Untuk sebuah dokumen yang dienkripsi dengan sebuah owner password dan sebuah user password kosong, konten itu terdekripsi sepenuhnya saat dibuka, dan flag-nya adalah sebuah permintaan kepada viewer yang sesuai standar, bukan sebuah mekanisme penegakan. Itu memiliki dua konsekuensi, dan keduanya menarik ke arah yang berlawanan. Jangan pernah menyajikan flag izin kepada pengguna sebagai sebuah properti keamanan dari dokumen yang mereka terima, karena itu bukan properti keamanan. Pada saat yang sama, hormati bit ekstraksi-aksesibilitas (bit 10) bahkan ketika penyalinan umum (bit 5) ditolak; akses screen reader sengaja dipisahkan secara terpisah dalam model izin ini, dan mencabutnya karena "penyalinan dimatikan" merusak teknologi bantu tanpa keuntungan keamanan apa pun
Tegakkan aksi yang ditolak pada level command, bukan dengan menyembunyikan tombol toolbar. Ctrl+C, menu konteks, dan drag-select semuanya melewati toolbar; satu pemeriksaan izin di dalam command penyalinan tidak dapat dilewati apa pun
Untuk dokumen yang memang membutuhkan sebuah user password, tetapkan Password sebelum Active := True dan perlakukan nilai itu seperti rahasia yang sebenarnya: ambil dari credential store Anda per sesi, jauhkan dari log dan laporan crash, dan jangan pernah menyimpannya secara persisten di samping dokumennya. Sebuah panel pratinjau yang meng-cache password "demi kenyamanan" telah diam-diam menjadi sebuah database password tanpa satu pun perlindungan yang dimiliki oleh sebuah database password
Pencetakan layak mendapat keputusannya sendiri, bukan mewarisi apa pun yang menjadi keputusan aturan penyalinan. Sebuah hasil cetak fisik secara definisi tidak teraudit, namun memblokir pencetakan secara langsung cenderung mendorong pengguna ke arah screenshot, yang lebih buruk di setiap sisi. Sebuah jalan tengah yang umum adalah mengizinkan pencetakan tetapi mencap setiap halaman dengan identitas pengguna dan sebuah timestamp, ditegakkan di dalam command cetak. Pertahankan saja ekspektasi yang tepat untuknya: sebuah watermark adalah pencegahan (deterrence) dan atribusi. Itu bukan pencegahan mutlak (prevention)
Apa yang seharusnya sudah diberitahukan oleh intake kepada Anda
Sebuah panel pratinjau membuat keputusan yang lebih baik ketika file itu muncul dengan sebuah dosir yang sudah terlampir: terenkripsi atau tidak, JavaScript ada atau tidak, sensus lampiran, tipe form-nya. Pass inspeksi itu seharusnya berada di hulu viewer, dan pola dalam membangun sebuah PDF intake review workbench menghasilkan persis flag yang ingin dikonsumsi oleh sebuah kebijakan pratinjau. File yang ditandai berisiko oleh intake terbuka secara otomatis melalui jalur yang diperkeras; dokumen rutin mempertahankan kenyamanannya. Ikat kedua tahap itu pada satu objek kebijakan bersama, bukan dua layar konfigurasi, yang akan bergeser saling menjauh pada rilis kedua betapapun hati-hatinya Anda menulisnya pertama kali
Di mana garis batas jatuh antara in-process dan out-of-process bergantung pada siapa yang mengirimkan file kepada Anda. Untuk intake bisnis biasa, orang-orang yang mengirim dokumen dikenal dan hanya sekadar ceroboh, dan pratinjau in-process dengan scripting mati dan link yang dicegat adalah sebuah standar yang dapat dipertahankan. Untuk unggahan publik anonim, itu tidak cukup, dan tidak ada jumlah pengaturan flag in-process yang dapat membuatnya cukup; render itu dalam sebuah worker berprivilese-rendah yang terpisah dan kirimkan bitmap ke UI, sehingga sebuah cacat engine hanya merugikan Anda satu worker, bukan aplikasi host-nya. Putuskan pemisahan itu dengan sengaja dan catat kelompok mana yang menjadi tempat setiap jalur ingesti, karena biaya menebak salah itu bersifat asimetris
Lisensi, permukaan API terkait keamanan, dan sebuah demo viewer yang diperkeras ada pada halaman produk: PDFium Component