Technical Article

Bezbedan pregled PDF-a u Delphi aplikacijama pomoću PDFium komponente

Pregledanje nepouzdanog PDF-a unutar sopstvene aplikacije je izvršna odluka, a deo koji je važan nije izgled čitača već ono što panel odbija da uradi sam. Ne upisujte datoteku na disk. Ne dozvolite da se njene veze otvaraju u sistemu. Ne dajte njenim prilozima putanju. Najveći deo štete od neprijateljskog dokumenta ne dolazi od eksploatacije motora, već od čitača koji radi savršeno obične stvari sa ulazom koji je obezbedio napadač: otvaranje veze file:// ka mrežnom deljenju (UNC) koje propušta NTLM akredititive, ostavljanje kopije u privremenom direktorijumu, kopiranje ugrađenih tereta (payloads) gde god to string imena datoteke kaže. PDFium Component je PDF čitač sa izvornim kodom za Delphi, C++Builder i Lazarus, i postavlja relevantne prekidače tamo gde ih možete dohvatiti: zastavica u vreme učitavanja koja ubija skriptovanje, događaji klika na vezu koje možete sprečiti, pristup prilozima koji ide kroz vaš sopstveni kod i bitovi dozvola koje možete pročitati. Redosled u nastavku prati dokument od trenutka kada stigne do trenutka kada korisnik klikne na nešto u njemu.

Model pretnje panela za pregled

Budite iskreni o tome šta vam „bezbedan pregled“ donosi. Renderer parsira nepouzdane bajtove bez obzira na to šta radite, a sopstveno obezbeđenje motora je pod na kojem stojite. Sve iznad tog poda je politika aplikacije: da li se skripte inicijalizuju, šta radi klik na vezu, da li ugrađene datoteke mogu da stignu do diska, da li su međuspremnik (clipboard) i štampač vrata ili zidovi. Jedna stvar koju treba rano otpisati jeste prekidač motora FPDF_SetSandBoxPolicy. Većina ograničenja motora je ugrađena pri kompajliranju, prekidač malo toga menja u praksi, a oslanjanje na njega samo stvara lažan osećaj da ste nešto uradili. Kada je ulaz zaista neprijateljski, recimo javni portal za otpremanje, jedina stvarna izolacija je iscrtavanje u zasebnom procesu sa niskim privilegijama i slanje bitmapa korisničkom interfejsu. Zastavice unutar procesa su politika. One nisu izolacija.

Dve površine je lako zaboraviti upravo zato što ih nijedan klik nikada ne dodiruje. Prva su privremene datoteke. Ako vaš cevovod skladišti dolazne dokumente na disk pre pregleda, te kopije nadživljavaju sesiju osim ako ih nešto provereno ne obriše, a datoteka koja se može „oporaviti iz privremenog direktorijumu“ tiho je porazila svaku kontrolu koju sam panel sprovodi. Učitavajte iz memorije preko TPdfStreamAdapter umesto toga, tako da neprijateljski bajtovi nikada ne dobiju sopstvenu putanju. Druga je međuspremnik. Pregled koji dozvoljava izbor i kopiranje već je izvezao dokument, ekran po ekran, i nikakvo presretanje veza to neće uhvatiti.

Ubijte JavaScript u vreme učitavanja, a ne u korisničkom interfejsu

JavaScript dokumenta u PDFium komponenti inicijalizuje se samo zajedno sa okruženjem za popunjavanje obrazaca. Učitavanje sa FormFill := False stoga onemogućava skriptovanje u korenu umesto da potiskuje njegove simptome:

procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
  Pdf.FileName := FilePath;
  Pdf.FormFill := False;     // no form environment, hence no JavaScript engine
  Pdf.Active := True;

  FPermissions := Pdf.Permissions;   // raw flag word; all bits set = unrestricted
end;

Kompromis je stvaran i pripada vašoj specifikaciji. Sa onemogućenim popunjavanjem obrazaca, legitimna AcroForm interakcija i skripte za validaciju su takođe nestali; polja se iscrtavaju sa svojim poslednjim sačuvanim izgledom, ali se ne mogu uređivati. Za panel za pregled to je obično ispravan poziv, jer pregled znači gledanje, a ne popunjavanje. Ali ako isti prozor služi i kao površina za popunjavanje obrazaca za pouzdane interne dokumente, odgovor su dve putanje učitavanja sa eksplicitnom odlukom o poverenju između njih, a ne jedna putanja sa kompromisnim podešavanjem koje je previše labavo za neprijateljski slučaj i previše tesno za pouzdani. Strana popunjavanja obrazaca tog razdvajanja ima svoje zamke, pokrivene u navigaciji kroz polja obrazaca i regeneraciji izgleda.

Veze: podrazumevani rukovalac se otvara u sistemu

Ako se ostave sami, klikovi na veze idu direktno u operativni sistem. Podrazumevane opcije LinkOptions čitača uključuju loAutoOpenURI, što je curenje sa file:// na mrežni udeo koje samo čeka da se dogodi. Dva događaja čine usko grlo: OnWebLinkClick za URL-ove otkrivene u tekstu stranice, i OnAnnotationLinkClick za anotacije veza koje nose akcije otvaranja URI-ja ili pokretanja. Postavite Handled := True u oba slučaja, bezuslovno, pre nego što bilo šta odlučite, a zatim ponovo dozvolite samo ono što politika dopušta. Kao drugi sloj, izbacite loAutoOpenURI iz LinkOptions za neprijateljski ulaz i uverite se da se loAutoLaunch, isključen podrazumevano, nikada ne vrati kroz kopiranu konfiguraciju:

procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
  const Url: WString; var Handled: Boolean);
begin
  Handled := True;   // never fall through to the default shell behavior

  if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
    and HostIsAllowed(Url) then
    OpenInBrowser(Url)
  else
    FAudit.LogBlockedLink(FDocumentId, Url);
end;

Dva detalja određuju da li ovo zaista drži vodu. Prvo, provera šeme mora biti provera prefiksa na sirovom stringu pre bilo kakvog parsiranja, jer su file://, UNC putanje i egzotične šeme upravo vrednosti koje ruše jednostavan URL parser ili prolaze kroz onaj koji se previše revnosno normalizuje. Drugo, zabeležite svaku blokadu sa priloženim identitetom dokumenta. Nekoliko blokiranih file:// veza je pozadinski šum; nalet takvih blokada na mnogim dolaznim dokumentima u kratkom prozoru jeste incident o kojem bi vaš bezbednosni tim radije čuo od vas nego sa nekog drugog mesta.

Prilozi: politika ekstenzija i ime datoteke koje niste izabrali

PDF je kontejner, a AttachmentCount sa svojstvom AttachmentName[] govori vam šta nosi pre nego što bilo šta dodirne disk. Dve odvojene kontrole su ovde važne, a samo jedna od njih je očigledna. Očigledna je politika tipa: dozvoljena lista (allowlist) ekstenzija koje se ikada mogu izvesti. Suptilna je to da je ime priloga podatak koji kontroliše napadač, u potpunosti. Ugrađeno ime poput ..\..\Startup\update.exe pretvara neoprezno čuvanje u prelazak putanje koji ispušta izvršnu datoteku u fasciklu koju Windows pokreće pri prijavi. Komponenta vam predaje teret kao bajtove kroz Attachment[] i prepušta vašem kodu da izabere putanju, pa izgradite tu putanju od očišćenog osnovnog imena (basename) i nikada od sirovog ugrađenog stringa:

procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
  RawName, SafeName, Ext: string;
  Data: TBytes;
begin
  RawName := string(Pdf.AttachmentName[Index]);
  SafeName := ExtractFileName(RawName);    // strips any path components
  Ext := LowerCase(ExtractFileExt(SafeName));

  if not FAllowedExt.Contains(Ext) then    // allowlist, not blocklist
    raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);

  Data := Pdf.Attachment[Index];           // embedded payload as raw bytes
  TFile.WriteAllBytes(
    IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;

Preferirajte smer dozvoljene liste. Lista blokiranih „opasnih“ ekstenzija je trka koju gubite onog dana kada neko upotrebi ekstenziju za koju nikada niste čuli; dozvoljena lista sa .pdf, .png i .csv se bezbedno zatvara pri neuspehu.

Šta dozvole enkripcije zapravo obećavaju

Standardni rukovalac bezbednošću u ISO 32000-1 kodira zastavice dozvola za štampanje, kopiranje sadržaja i izmenu, a svojstva Permissions i UserPermissions ih prikazuju kao sirove bit-maske čim se dokument otvori. Tabela 22 u ISO 32000-1 definiše bitove, a nešifrovana datoteka prijavljuje svaki bit postavljen. Pročitajte ih i poštujte ih u svom komandnom sloju, ali budite jasni šta oni predstavljaju. Za dokument šifrovan lozinkom vlasnika i praznom korisničkom lozinkom, sadržaj se potpuno dešifruje pri otvaranju, a zastavice su zahtev usmeren čitačima, a ne mehanizam izvršenja. To ima dve posledice i one vuku u suprotnim smerovima. Nikada ne predstavljajte zastavice dozvola korisnicima kao bezbednosno svojstvo dokumenata koje primaju, jer to nisu. Istovremeno, poštujte bit za ekstrakciju radi pristupačnosti (bit 10) čak i kada je opšte kopiranje (bit 5) odbijeno; pristup čitaču ekrana je posebno izdvojen u modelu dozvola sa razlogom, a njegovo uklanjanje jer je „kopiranje isključeno“ kvari asistivnu tehnologiju bez ikakvog bezbednosnog dobitka.

Sprovedite odbijene akcije na nivou komande, a ne skrivanjem dugmadi na liniji alatki. Ctrl+C, kontekstni meniji i selekcija prevlačenjem zaobilaze liniju alatki; jedna provera dozvole unutar komande za kopiranje ne zaobilazi ništa.

Za dokumente koji zahtevaju korisničku lozinku, dodelite Password pre nego što Active := True i tretirajte vrednost kao tajnu koja i jeste: preuzmite je iz svog skladišta akreditiva po sesiji, držite je van dnevnika i izveštaja o rušenju, i nikada je ne čuvajte pored dokumenta. Panel za pregled koji kešira lozinke „radi pogodnosti“ tiho je postao baza podataka lozinki bez ikakve zaštite koju takva baza zahteva.

Štampanje zaslužuje sopstvenu odluku, umesto da nasledi šta god da je pravilo kopiranja donelo. Fizički otisak je po definiciji nerevidiran, ali blokiranje štampanja u potpunosti tera korisnike ka snimcima ekrana (screenshots), koji su gori na svakom nivou. Uobičajeni srednji put jeste dozvoliti štampanje, ali utisnuti identitet korisnika i vremenski žig na svaku stranicu, što se sprovodi unutar komande za štampanje. Samo imajte ispravno očekivanje od toga: vodeni žig je odvraćanje i atribucija. On nije sprečavanje.

Šta je prihvat već trebalo da vam kaže

Panel za pregled donosi bolje odluke kada dokument stigne sa već priloženim dosijeom: šifrovan ili ne, JavaScript prisutan ili odsutan, popis priloga, tip obrasca. Taj prolaz inspekcije pripada uzvodno od čitača, a šablon u izgradnji radnog stola za pregled prihvata PDF-a proizvodi upravo one zastavice koje bezbednosna politika pregleda želi da konzumira. Datoteke koje je prihvat označio kao rizične otvaraju se kroz zaštićenu putanju automatski; rutinski dokumenti zadržavaju svoje pogodnosti. Povežite ove dve faze sa jednim zajedničkim objektom politike, a ne sa dva ekrana za konfiguraciju, koji će se razići do drugog izdanja bez obzira na to koliko ih pažljivo napisali prvi put.

Gde linija pada između procesa i van njega zavisi od toga ko vam šalje datoteke. Za običan poslovni prihvat, ljudi koji šalju dokumente su poznati i samo neoprezni, i pregled unutar procesa sa isključenim skriptovanjem i presretnutim vezama jeste odbranjiva granica. Za anonimna javna otpremanja to nije slučaj, i nikakvo podešavanje zastavica unutar procesa to ne čini takvim; iscrtajte ih u zasebnom radniku sa niskim privilegijama i pošaljite bitmape korisničkom interfejsu, tako da vas greška motora košta radnika, a ne host aplikacije. Odlučite to razdvajanje namerno i zapišite u koju kantu spada svaka putanja unosa, jer je cena pogrešne procene asimetrična.

Licenciranje, API površina povezana sa bezbednošću i demo zaštićenog čitača nalaze se na stranici proizvoda: PDFium Component.