Previzualizarea unui PDF de încredere necunoscută în interiorul propriei dvs. aplicații este o decizie de execuție, iar partea care contează nu este interfața vizualizatorului, ci ceea ce panoul refuză să facă de la sine. Nu scrieți fișierul pe disc. Nu lăsați linkurile lui să lanseze programe externe. Nu îi dați atașamentelor sale o cale. Cea mai mare parte a daunelor produse de un document ostil nu provine dintr-un exploit al motorului, ci dintr-un vizualizator care face lucruri perfect obișnuite cu intrare furnizată de atacator: deschiderea unui link file:// către o partajare UNC care scurge credențiale NTLM, lăsarea unei copii intermediare în directorul temporar, copierea payload-urilor încorporate oriunde îi indică un șir de nume de fișier. PDFium Component este un vizualizator PDF cu cod sursă pentru Delphi, C++Builder și Lazarus, iar el pune comutatoarele relevante la îndemâna dvs.: un indicator la momentul încărcării care omoară scriptarea, evenimente de clic pe link pe care le puteți respinge, acces la atașamente care trece prin propriul dvs. cod și biți de permisiune pe care îi puteți citi. Ordinea de mai jos urmărește un document din momentul în care ajunge până în momentul în care un utilizator dă clic pe ceva din el
Modelul de amenințare al unui panou de previzualizare
Fiți sinceri în privința a ceea ce vă oferă cu adevărat o „previzualizare securizată”. Randatorul analizează octeți de încredere necunoscută indiferent ce faceți, iar întărirea proprie a motorului este podeaua pe care stați. Tot ce se află deasupra acelei podele este politică de aplicație: dacă scripturile se inițializează, ce face un clic pe link, dacă fișierele încorporate pot ajunge pe disc, dacă clipboard-ul și imprimanta sunt uși sau ziduri. Un lucru de scos din calcul de la bun început este comutatorul motorului FPDF_SetSandBoxPolicy. Majoritatea restricțiilor motorului sunt compilate în el, comutatorul schimbă puțin în practică, iar bugetarea vreunei părți din povestea dvs. de izolare pe seama lui produce doar un fals sentiment că ați făcut ceva. Când intrarea este cu adevărat ostilă, de exemplu un portal public de încărcare, singura izolare reală este randarea într-un proces separat cu privilegii reduse și trimiterea bitmap-urilor către interfață. Indicatorii din interiorul procesului sunt politică. Nu sunt izolare
Există două suprafețe ușor de uitat, exact pentru că niciun clic nu le atinge vreodată. Prima sunt fișierele temporare. Dacă pipeline-ul dvs. pune documentele primite pe disc înainte de previzualizare, acele copii intermediare supraviețuiesc sesiunii, dacă nimic nu le șterge verificabil, iar un fișier care este „recuperabil din directorul temporar” a înfrânt în tăcere fiecare control pe care panoul îl aplică el însuși. Încărcați în schimb din memorie prin TPdfStreamAdapter, astfel încât octeții ostili să nu primească niciodată o cale proprie. A doua este clipboard-ul. O previzualizare care permite selectare și copiere a exportat deja documentul, un ecran plin odată, iar nicio interceptare de link nu prinde asta
Opriți JavaScript la momentul încărcării, nu în interfață
JavaScript-ul documentului din PDFium Component se inițializează doar împreună cu mediul form-fill. Încărcarea cu FormFill := False dezactivează deci scriptarea la rădăcină, în loc să îi suprime simptomele:
procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
Pdf.FileName := FilePath;
Pdf.FormFill := False; // fără mediu de formular, deci fără motor JavaScript
Pdf.Active := True;
FPermissions := Pdf.Permissions; // cuvânt brut de indicatori; toți biții setați = fără restricții
end;
Compromisul este real și aparține specificației dvs. Cu form fill dezactivat, dispar și interacțiunea AcroForm legitimă și scripturile de validare; câmpurile se randează cu ultimul lor aspect salvat, dar nu pot fi editate. Pentru un panou de previzualizare, aceasta este de obicei decizia corectă, deoarece previzualizare înseamnă privire, nu completare. Dar dacă aceeași fereastră servește dublu ca suprafață de completare a formularelor pentru documente interne de încredere, răspunsul este două căi de încărcare cu o decizie explicită de încredere între ele, nu o singură cale cu o setare de compromis prea permisivă pentru cazul ostil și prea strictă pentru cel de încredere. Partea de completare a formularelor din această separare are propriile capcane, tratate în navigarea câmpurilor de formular și regenerarea aspectului
Linkuri: handler-ul implicit lansează programe externe
Lăsate netratate, clicurile pe linkuri merg direct la sistemul de operare. LinkOptions implicite ale vizualizatorului includ loAutoOpenURI, ceea ce este exact scurgerea de la file:// către o partajare UNC așteptând să se întâmple. Două evenimente formează punctul de strangulare: OnWebLinkClick pentru URL-urile detectate în textul paginii, și OnAnnotationLinkClick pentru adnotările de tip link care poartă acțiuni URI sau de lansare. Setați Handled := True în ambele, necondiționat, înainte de a decide orice, apoi re-permiteți doar ce permite politica. Ca al doilea strat, eliminați loAutoOpenURI din LinkOptions pentru intrare ostilă și asigurați-vă că loAutoLaunch, dezactivat implicit, nu se strecoară niciodată înapoi printr-o configurație copiată:
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
const Url: WString; var Handled: Boolean);
begin
Handled := True; // nu lăsați niciodată să treacă prin comportamentul implicit al shell-ului
if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
and HostIsAllowed(Url) then
OpenInBrowser(Url)
else
FAudit.LogBlockedLink(FDocumentId, Url);
end;
Două detalii decid dacă acest lucru chiar rezistă. Primul, verificarea schemei trebuie să fie o verificare de prefix pe șirul brut, înainte de orice analiză, deoarece file://, căile UNC și schemele exotice sunt exact valorile care blochează un parser URL naiv sau se strecoară printr-unul care normalizează prea agresiv. Al doilea, jurnalizați fiecare blocare cu identitatea documentului atașată. O mână de linkuri file:// blocate este zgomot de fundal; o rafală a lor pe parcursul multor documente primite într-o fereastră scurtă de timp este un incident despre care echipa dvs. de securitate ar prefera să audă de la dvs., nu din altă parte
Atașamente: politica de extensii și numele de fișier pe care nu l-ați ales dvs
Un PDF este un container, iar AttachmentCount împreună cu proprietatea AttachmentName[] vă spune ce poartă înainte ca ceva să atingă discul. Aici contează două controale separate, și doar unul dintre ele este evident. Cel evident este politica de tip: o listă albă de extensii care pot fi vreodată exportate. Cel subtil este că numele atașamentului este date controlate de atacator, punct. Un nume încorporat precum ..\..\Startup\update.exe transformă o salvare neatentă într-un path traversal care depune un executabil într-un folder pe care Windows îl rulează la logare. Componenta vă oferă payload-ul ca octeți prin Attachment[] și lasă codul dvs. să aleagă calea, așa că construiți acea cale dintr-un nume de bază igienizat, niciodată din șirul brut încorporat:
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
RawName, SafeName, Ext: string;
Data: TBytes;
begin
RawName := string(Pdf.AttachmentName[Index]);
SafeName := ExtractFileName(RawName); // elimină orice componente de cale
Ext := LowerCase(ExtractFileExt(SafeName));
if not FAllowedExt.Contains(Ext) then // listă albă, nu listă neagră
raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);
Data := Pdf.Attachment[Index]; // payload încorporat ca octeți bruți
TFile.WriteAllBytes(
IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;
Preferați direcția listei albe. O listă neagră de extensii „periculoase” este o cursă pe care o pierdeți în ziua în care cineva transformă în armă o extensie despre care nu ați auzit niciodată; o listă albă de .pdf, .png și .csv eșuează în siguranță (fail closed)
Ce promit de fapt permisiunile de criptare
Handler-ul de securitate standard din ISO 32000-1 codifică indicatori de permisiune pentru tipărire, copierea conținutului și modificare, iar proprietățile Permissions și UserPermissions le expun ca măști de biți brute odată ce documentul se deschide. Tabelul 22 din ISO 32000-1 definește biții, iar un fișier necriptat raportează fiecare bit setat. Citiți-i și respectați-i în stratul dvs. de comenzi, dar fiți clari cu privire la ce sunt ei. Pentru un document criptat cu o parolă de proprietar și o parolă de utilizator goală, conținutul se decriptează complet la deschidere, iar indicatorii sunt o cerere adresată vizualizatoarelor conforme, nu un mecanism de impunere. Asta are două consecințe, care trag în direcții opuse. Nu prezentați niciodată utilizatorilor indicatorii de permisiune ca pe o proprietate de securitate a documentelor pe care le primesc, deoarece nu sunt așa ceva. În același timp, respectați bitul de extragere pentru accesibilitate (bitul 10) chiar și acolo unde copierea generală (bitul 5) este refuzată; accesul pentru cititoarele de ecran este exceptat separat, intenționat, în modelul de permisiuni, iar eliminarea lui pentru că „copierea este dezactivată” strică tehnologia asistivă fără niciun câștig de securitate
Impuneți acțiunile refuzate la nivelul comenzii, nu ascunzând butoane din bara de instrumente. Ctrl+C, meniurile contextuale și selecția prin tragere ocolesc toate o bară de instrumente; o singură verificare de permisiune în interiorul comenzii de copiere nu ocolește nimic
Pentru documentele care chiar necesită o parolă de utilizator, atribuiți Password înainte de Active := True și tratați valoarea ca secretul care este: preluați-o din magazinul dvs. de credențiale per sesiune, țineți-o departe de jurnale și rapoarte de crash și nu o persistați niciodată lângă document. Un panou de previzualizare care stochează parole în cache „pentru comoditate” a devenit în tăcere o bază de date de parole fără niciuna dintre protecțiile uneia
Tipărirea merită propria decizie, în loc să moștenească orice a rezultat din regula de copiere. Un exemplar tipărit fizic este neauditat prin definiție, totuși blocarea totală a tipăririi tinde să împingă utilizatorii spre capturi de ecran, care sunt mai rele din toate punctele de vedere. Un compromis obișnuit este să permiteți tipărirea, dar să ștampilați fiecare pagină cu identitatea utilizatorului și un timestamp, impuse în interiorul comenzii de tipărire. Doar păstrați așteptarea corectă pentru asta: un filigran este descurajare și atribuire. Nu este prevenire
Ce ar fi trebuit să vă spună deja recepția
Un panou de previzualizare ia decizii mai bune atunci când fișierul sosește cu un dosar deja atașat: criptat sau nu, JavaScript prezent sau absent, un recensământ al atașamentelor, tipul formularului. Acea trecere de inspecție aparține în amonte de vizualizator, iar tiparul din construirea unui banc de lucru pentru revizuirea la recepție a PDF-urilor produce exact indicatorii pe care o politică de previzualizare vrea să îi consume. Fișierele pe care recepția le-a marcat drept riscante se deschid automat prin calea întărită; documentele de rutină își păstrează avantajele. Legați cele două etape de un singur obiect de politică partajat, în loc de două ecrane de configurare, care se vor îndepărta unul de celălalt până la a doua versiune, oricât de atent le-ați scrie prima dată
Unde cade linia dintre in-process și out-of-process depinde de cine vă trimite fișierele. Pentru recepția obișnuită de afaceri, persoanele care trimit documente sunt cunoscute și doar neatente, iar previzualizarea in-process cu scriptarea dezactivată și linkurile interceptate este o bară apărabilă. Pentru încărcările publice anonime, nu este, și nicio cantitate de setare de indicatori in-process nu o face una; randați-le într-un worker separat cu privilegii reduse și trimiteți bitmap-uri către interfață, astfel încât un defect al motorului să vă coste un worker, nu aplicația gazdă. Decideți acea separare în mod deliberat și consemnați în ce categorie cade fiecare cale de ingestie, deoarece costul unei presupuneri greșite este asimetric
Licențierea, suprafața de API legată de securitate și o demonstrație de vizualizator întărit se află pe pagina de produs: PDFium Component