Technical Article

Saugus PDF peržiūros skydelis Delphi programose su PDFium komponentu

Nepatikimo PDF failo peržiūra jūsų programoje yra svarbus sprendimas, ir čia svarbiausia ne peržiūros programos išvaizda, o tai, ko šis skydelis atsisako daryti. Neįrašykite failo į diską. Neleiskite jo nuorodoms vykdyti išorinių programų. Nurodykite priedams saugų kelią. Didžioji dalis žalos iš priešiško dokumento kyla ne dėl variklio pažeidžiamumo, o dėl to, kad peržiūros programa atlieka visiškai įprastus veiksmus su užpuoliko pateiktais duomenimis: atidaro file:// nuorodą į UNC bendrinamą aplanką, kuris nutekina NTLM kredencialus, palieka laikiną kopiją temp kataloge arba nukopijuoja įterptą turinį ten, kur nurodo failo vardo eilutė. „PDFium Component“ yra Delphi, C++Builder ir Lazarus skirta PDF peržiūros programa su šaltinio kodu, ir ji pateikia reikiamus jungiklius ten, kur galite juos pasiekti: įkėlimo meto vėliavėlę, kuri išjungia scenarijus, nuorodų paspaudimo įvykius, kuriuos galite blokuoti, priedų prieigą, veikiančią per jūsų kodą, ir leidimų bitus, kuriuos galite nuskaityti. Toliau pateikiama seka seka dokumentą nuo jo atsiradimo iki to momento, kai vartotojas jame ką nors spustelėja.

Peržiūros skydelio grėsmių modelis

Būkite atviri dėl to, ką jums suteikia „saugi peržiūra“. Atvaizdavimo variklis bet kokiu atveju analizuoja nepatikimus baitus, ir paties variklio saugumas yra jūsų pagrindas. Viskas virš to yra programos politika: ar inicijuojami scenarijai, ką daro nuorodos paspaudimas, ar įterptieji failai gali pasiekti diską, ar iškarpinė (clipboard) ir spausdintuvas yra atviri, ar uždaryti. Vienas iš dalykų, kurį reikėtų atmesti iškart – variklio nustatymas FPDF_SetSandBoxPolicy. Dauguma variklio ribojimų yra integruoti jį kompiliuojant, šis nustatymas praktikoje keičia nedaug, o pasikliovimas juo sukuria tik klaidingą saugumo jausmą. Kai įvestis yra tikrai priešiška (pavyzdžiui, viešas įkėlimo portalas), vienintelė tikra izoliacija yra atvaizdavimas atskirame mažų teisių procese ir taškinių paveikslėlių perdavimas vartotojo sąsajai. Proceso viduje esančios vėliavėlės yra tik politika, o ne izoliacija.

Dvi sritis lengva pamiršti būtent dou todėl, kad joks paspaudimas jų neliečia. Pirmoji – laikinieji failai. Jei jūsų sistema išsaugo gautus dokumentus diske prieš peržiūrą, šios kopijos išlieka po sesijos pabaigos, nebent kas nors jas patikimai ištrina. Failas, kurį galima „atkurti iš laikinojo katalogo“, tyliai apeina bet kokią skydelio kontrolę. Vietoj to įkelkite dokumentą tiesiai iš atminties naudodami TPdfStreamAdapter, kad nepatikimi baitai niekada negautų savo asmeninio kelio diske. Antroji sritis – iškarpinė. Peržiūra, kuri leidžia pažymėti ir kopijuoti tekstą, jau eksportuoja dokumento (po vieną ekrano vaizdą), ir joks nuorodų perėmimas to nesustaudys.

JavaScript išjungimas įkėlimo metu, o ne vartotojo sąsajoje

Dokumento JavaScript bibliotekoje „PDFium Component“ inicijuojamas tik kartu su formų pildymo aplinka. Todėl įkėlimas su FormFill := False išjungia scenarijų vykdymą pačioje pradžioje, užuot slopinus jo simptomus:

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;

Šis kompromisas yra realus ir turi būti įtrauktas į specifikaciją. Išjungus formų pildymą, prarandama ir teisinga sąveika su „AcroForm“ bei tikrinimo scenarijai; laukai atvaizduojami su paskutine išsaugota išvaizda, tačiau negali būti redaguojami. Peržiūros skydeliui tai dažniausiai yra teisingas pasirinkimas, nes peržiūra reiškia žiūrėjimą, o ne pildymą. Bet jei tas pats langas naudojamas ir formų pildymui patikimiems vidiniams dokumentams, sprendimas yra du įkėlimo keliai su aiškiu pasitikėjimo sprendimu, o ne vienas kompromisinis kelias, kuris yra per laisvas priešiškam atvejui ir per griežtas patikimam. Formų pildymo pusė turi savo spąstus, kurie nagrinėjami straipsnyje apie formos laukų navigaciją ir išvaizdos atkūrimą.

Nuorodos: numatytasis doroklis paleidžia išorines programas

Palikus ramybėje, nuorodų paspaudimai keliauja tiesiai į operacinę sistemą. Numatytosios peržiūros programos LinkOptions apima loAutoOpenURI – tai yra file:// ir UNC bendrinimo nuorodų nutekėjimas, pasiruošęs įvykti. Du įvykiai sudaro patikros tašką: OnWebLinkClick puslapio tekste aptiktoms nuorodoms ir OnAnnotationLinkClick nuorodų anotacijoms su URI arba paleidimo veiksmais. Nustatykite Handled := True abiejuose įvykiuose besąlygiškai, prieš priimdami bet kokį sprendimą, ir leiskite tik tai, ką leidžia saugumo politika. Kaip antrą apsaugos sluoksnį, pašalinkite loAutoOpenURILinkOptions nepatikimiems failams ir įsitikinkite, kad loAutoLaunch (kuris pagal nutylėjimą yra išjungtas) niekada neatsiranda nukopijuotoje konfigūracijoje:

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;

Du dalykai lemia, ar ši apsauga tikrai veikia. Pirma, schemos patikrinimas tai turi būti atliekamas kaip prefikso patikra neapdorotoje eilutėje prieš bet kokį analizavimą, nes file://, UNC keliai ir egzotinės schemos yra būtent tos reikšmės, kurios sugadina paprastą URL analizatorių arba praslysta pro jį, jei jis normalizuoja tekstą per daug agresyviai. Antra, fiksuokite kiekvieną blokavimą nurodydami dokumento tapatybę. Keli užblokuoti file:// nuorodų paspaudimai yra tik foninis triukšmas, tačiau jų pliūpsnis per trumpą laiką keliuose gautuose dokumentuose yra incidentas, apie kurį jūsų saugumo komanda tikrai norėtų sužinoti iš jūsų, o ne iš kitų šaltinių.

Priedai: plėtinių politika ir failo pavadinimas, kurio nepasirinkote

PDF yra konteineris, ir savybė AttachmentCount kartu su AttachmentName[] praneša, ką failas turi, prieš kam nors paliečiant diską. Čia svarbūs du atskiri kontrolės mechanizmai, ir tik vienas iš jų yra akivaizdus. Akivaizdusis yra plėtinių politika – leidžiamų eksportuoti plėtinių sąrašas (allowlist). Subtili detalė yra ta, kad priedo pavadinimas yra užpuoliko kontroliuojami duomenys. Įterptas pavadinimas, toks kaip ..\..\Startup\update.exe, paverčia neatsargų išsaugojimą kelio kirtimo (path traversal) ataka, kuri įrašo vykdomąjį failą į aplanką, kurį Windows paleidžia prisijungimo metu. Komponentas perduoda priedo turinį baitais per savybę Attachment[] ir leidžia jūsų kodui pasirinkti kelią diske, todėl kurkite šį kelią iš išvalyto pagrindinio failo pavadinimo, o ne iš neapdorotos įterptos eilutės:

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;

Teikite pirmenybę leidžiamam sąrašui (allowlist). Blokuojamų „pavojingų“ plėtinių sąrašas yra kova, kurią pralaimėsite tą dieną, kai užpuolikas panaudos plėtinį, apie kurį niekada negirdėjote; leidžiamų failų sąrašas, apimantis .pdf, .png ir .csv, apsaugo geriau.

Ką iš tikrųjų garantuoja šifravimo leidimai

Standartinis ISO 32000-1 saugumo doroklis koduoja spausdinimo, turinio kopijavimo ir modifikavimo leidimų vėliavėles, o savybės Permissions bei UserPermissions pateikia jas kaip neapdorotas bitų kaukes, kai dokumentas yra atidaromas. ISO 32000-1 22 lentelė apibrėžia šiuos bitus, o neužšifruotas failas grąžina visus nustatytus bitus. Nuskaitykite juos ir laikykitės jų savo komandų lygmenyje, tačiau aiškiai supraskite, kas jie yra. Dokumentui, užšifruotam savininko slaptažodžiu su tuščiu vartotojo slaptažodžiu, turinys atidarymo metu yra visiškai iššifruojamas, o šios vėliavėlės yra tik prašymas peržiūros programoms, o ne privalomas vykdymo mechanizmas. Tai turi dvi pasekmes, kurios veda priešingomis kryptimis. Niekada nepristatykite leidimų vėliavėlių vartotojams kaip dokumentų saugumo savybės, nes jie tokie nėra. Kartu laikykitės prieinamumo išgavimo bito (10 bitas), net jei bendras kopijavimas (5 bitas) yra uždraustas; ekrano skaitytuvų prieiga leidimų modelyje tikslingai išskirta atskirai, o jos pašalinimas, nes „kopijavimas išjungtas“, sugadina pagalbinę technologiją be jokios naudos saugumui.

Užtikrinkite uždraustų veiksmų vykdymą komandų lygmenyje, o ne paslėpdami įrankių juostos mygtukus. Klavišų kombinacija Ctrl+C, kontekstiniai meniu ir pažymėjimas vilkimu apeina įrankių juostą; vienintelė leidimų patikra kopijavimo komandoje neleidžia nieko apeiti.

Dokumentams, kuriems reikalingas vartotojo slaptažodis, priskirkite Password prieš nustatydami Active := True ir elkitės su šia reikšme kaip su paslaptimi: gaukite ją iš savo kredencialų saugyklos kiekvienai sesijai, neregistruokite žurnaluose ar gedimų ataskaitose ir niekada neišsaugokite šalia dokumento. Peržiūros skydelis, kuris saugo slaptažodžius talpykloje „patogumui“, tyliai tampa slaptažodžių duomenų baze, neturėdamas tam reikalingų apsaugos priemonių.

Spausdinimas reikalauja atskiro sprendimo, užuot tiesiog perėmus kopijavimo taisyklę. Fizinis spaudinys pagal apibrėžimą yra neaudituojamas, tačiau visiškas spausdinimo blokavimas dažnai pastūmėja vartotojus daryti ekrano nuotraukas, o tai yra blogiau visais atžvilgiais. Dažnas tarpinis variantas – leisti spausdinti, tačiau ant kiekvieno puslapio uždėti vartotojo tapatybės ir laiko spaudą, tai užtikrinant spausdinimo komandoje. Tik turėkite teisingus lūkesčius: vandens ženklas yra atgrasymo ir autorystės priskyrimo priemonė. Tai nėra prevencija.

Ką priėmimo sistema jau turėjo pranešti

Peržiūros skydelis priima geresnius sprendimus, kai failas pateikiamas su jau paruošta informacija: užšifruotas ar ne, yra JavaScript ar ne, priedų sąrašas, formos tipas. Šis tikrinimo žingsnis turi būti atliekamas prieš peržiūros programą, o metodas, aprašytas straipsnyje apie PDF priėmimo peržiūros darbo aplinkos kūrimą, sukuria būtent tas vėliavėles, kurias nori naudoti peržiūros politika. Failai, kuriuos priėmimo sistema pažymėjo kaip rizikingus, automatiškai atidaromi saugiuoju būdu; įprasti dokumentai išlaiko savo patogumus. Susiekite šiuos du etapus su vienu bendru politikos objektu, o ne su dviem konfigūracijos ekranais, kurie išsiskirs jau su antrąja versja, kad ir kaip kruopščiai juos parašytumėte pirmą kartą.

Where ribą nubrėžti tarp apdorojimo procese (in-process) ir už jo ribų (out-of-process), priklauso nuo to, kas siunčia failus. Įprastam verslo dokumentų priėmimui siuntėjai yra žinomi ir tiesiog neatsargūs, todėl peržiūra procese su išjungtais scenarijais ir perimtomis nuorodomis yra pakankamas barjeras. Anoniminiams viešiems įkėlimams tai netinka, ir joks vėliavėlių nustatymas proceso viduje to nepakeis; atvaizduokite juos atskirame mažų teisių procese ir perduokite taškinius paveikslėlius vartotojo sąsajai, kad variklio klaida kainuotų tik procesą, o ne pačią pagrindinę programą. Priimkite šį sprendimą apgalvotai ir užfiksuokite, į kurią kategoriją patenka kiekvienas priėmimo kelias, nes klaidos kaina yra asimetriška.

Licencijavimas, su saugumu susijusi API sąsaja ir apsaugotos peržiūros programos demonstracinė versija yra pateikiamos produkto puslapyje: „PDFium Component“.