Teknisk artikel

Sikker PDF-forhåndsvisning i Delphi-programmer med PDFium-komponenten

At forhåndsvise en ubetroet PDF inden i din egen applikation er en eksekveringsbeslutning, og den del, der betyder noget, er ikke viewer'ens rammeværk, men hvad ruden nægter at gøre på egen hånd. Skriv ikke filen til disk. Lad ikke dens links starte et shell. Giv ikke dens vedhæftninger en sti. Det meste af skaden fra et fjendtligt dokument kommer ikke fra en motor-exploit, men fra en viewer, der gør fuldstændig almindelige ting med input leveret af en angriber: åbner et file://-link til en UNC-share, der lækker NTLM-legitimationsoplysninger, efterlader en midlertidig kopi i temp-mappen, kopierer indlejrede nyttelaster hen, hvor end en filnavnestreng fortæller den. PDFium Component er en kildekodebaseret PDF-viewer til Delphi, C++Builder og Lazarus, og den placerer de relevante kontakter, hvor du kan nå dem: et indlæsningstidsflag, der dræber scripting, link-klik-hændelser du kan nedlægge veto mod, vedhæftningsadgang der løber gennem din egen kode, og tilladelsesbits du kan læse. Rækkefølgen nedenfor følger et dokument fra det øjeblik, det lander, til det øjeblik en bruger klikker på noget i det

Trusselsmodellen for en forhåndsvisningsrude

Vær ærlig om, hvad "sikker forhåndsvisning" reelt giver dig. Rendermotoren parser ubetroede bytes, uanset hvad du gør, og motorens egen hærdning er gulvet, du står på. Alt over det gulv er applikationspolitik: om scripts initieres, hvad et link-klik gør, om indlejrede filer kan nå disken, om udklipsholderen og printeren er døre eller vægge. Én ting, man tidligt bør afskrive, er motorens FPDF_SetSandBoxPolicy-kontakt. De fleste motorbegrænsninger er kompileret ind, kontakten ændrer lidt i praksis, og at budgettere nogen del af sin isolationsfortælling til den producerer blot en falsk følelse af at have gjort noget. Når input reelt er fjendtligt, sig en offentlig upload-portal, er den eneste rigtige isolation at rendre i en separat proces med lave privilegier og sende bitmaps til brugerfladen. In-process-flag er politik. De er ikke isolering

PDFium Component-diagram, der kontrasterer in-process PDF preview-politikskifter med out-of-process-rendering i en lavprivileget worker til fjendtlige dokumenter
In-process-flag hæver barrieren for kendte afsendere, mens anonyme uploads retfærdiggør en separat lavprivilegeret worker, der kun skiber bitmaps til UI'en

To overflader er lette at glemme, netop fordi intet klik nogensinde rører dem. Den første er midlertidige filer. Hvis din pipeline mellemlagrer indkomne dokumenter på disken før forhåndsvisning, overlever de mellemlagrede kopier sessionen, medmindre noget påviseligt sletter dem, og en fil, der er "genskabbar fra temp-mappen", har i det stille overvundet enhver kontrol, ruden selv håndhæver. Indlæs i stedet fra hukommelsen gennem TPdfStreamAdapter, så de fjendtlige bytes aldrig får deres egen sti. Den anden er udklipsholderen. En forhåndsvisning, der tillader marker-og-kopiér, har allerede eksporteret dokumentet, én skærmfuld ad gangen, og ingen linkopfangst vil fange det

Dræb JavaScript ved indlæsning, ikke i brugerfladen

Dokument-JavaScript i PDFium Component initieres kun sammen med formularudfyldningsmiljøet. At indlæse med FormFill := False deaktiverer derfor scripting ved roden i stedet for blot at undertrykke symptomerne:

procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
  Pdf.FileName := FilePath;
  Pdf.FormFill := False;     // intet formularmiljø, derfor ingen JavaScript-motor
  Pdf.Active := True;

  FPermissions := Pdf.Permissions;   // rå flag-word; alle bits sat = ubegrænset
end;

Kompromiset er reelt og hører hjemme i din specifikation. Med formularudfyldning deaktiveret er legitim AcroForm-interaktion og valideringsscripts også væk; felter rendres med deres sidst gemte udseende, men kan ikke redigeres. For en forhåndsvisningsrude er det som regel det rette valg, eftersom forhåndsvisning betyder kig, ikke udfyld. Men hvis det samme vindue også fungerer som en formularudfyldningsflade for betroede interne dokumenter, er svaret to indlæsningsstier med en eksplicit tillidsbeslutning imellem, ikke én sti med en kompromisindstilling, der er for løs til det fjendtlige tilfælde og for stram til det betroede. Formularudfyldningssiden af den opdeling har sine egne faldgruber, dækket i navigation i formularfelter og genopbygning af udseende

Links: standardhåndteringen starter et shell

Overladt til sig selv går link-klik direkte til operativsystemet. Viewer'ens standard-LinkOptions inkluderer loAutoOpenURI, hvilket er file://-til-UNC-share-lækagen, der venter på at ske. To hændelser udgør flaskehalsen: OnWebLinkClick for URL'er registreret i sidetekst, og OnAnnotationLinkClick for link-annotationer, der bærer URI- eller launch-handlinger. Sæt Handled := True i begge, betingelsesløst, før noget som helst besluttes, og tillad derefter kun det, politikken tillader. Som et andet lag skal man fjerne loAutoOpenURI fra LinkOptions for fjendtligt input og sikre, at loAutoLaunch, slået fra som standard, aldrig sniger sig tilbage gennem en kopieret konfiguration:

Flowdiagram over PDF linkklik-interception i en Delphi preview-rude med en råstreng skemapræfiskkontrol og auditlogning af blokerede links
At sætte Handled i begge link-hændelser holder hvert klik under applikationspolitik, og et præfiks-tjek på den rå streng holder file://- og UNC-skemaer ude
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
  const Url: WString; var Handled: Boolean);
begin
  Handled := True;   // falder aldrig igennem til standard-shell-adfærden

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

To detaljer afgør, om dette rent faktisk holder. For det første skal skema-kontrollen være en præfiks-kontrol på den rå streng, før nogen parsing finder sted, fordi file://, UNC-stier og eksotiske skemaer er præcis de værdier, der crasher en naiv URL-parser eller glider igennem en, der normaliserer for ivrigt. For det andet skal man logge hver blokering med dokumentidentiteten vedhæftet. En håndfuld blokerede file://-links er baggrundsstøj; en byge af dem på tværs af mange indkomne dokumenter inden for et kort vindue er en hændelse, dit sikkerhedsteam hellere vil høre om fra dig end fra et andet sted

Vedhæftninger: udvidelsespolitik og filnavnet, du ikke valgte

En PDF er en container, og AttachmentCount sammen med AttachmentName[]-egenskaben fortæller dig, hvad den bærer, før noget rører disken. To separate kontroller betyder noget her, og kun én af dem er indlysende. Den indlysende er typepolitik: en tilladelsesliste over udvidelser, der overhovedet må eksporteres. Den subtile er, at vedhæftningens navn er data kontrolleret af angriberen, punktum. Et indlejret navn som ..\..\Startup\update.exe forvandler et skødesløst gem til en stitraversering, der lægger en eksekverbar fil i en mappe, Windows kører ved login. Komponenten giver dig nyttelasten som bytes gennem Attachment[] og lader din kode vælge stien, så byg den sti ud fra et saneret basisnavn og aldrig fra den rå indlejrede streng:

PDFium Component-pipelinediagram, der saniterer et angriberkontrolleret PDF vedhæftningsnavn gennem ExtractFileName og en extension allowlist, før bytes skrives
Det indlejrede attachment-navn er angriber-input, så eksportvejen genopbygges af et sanitiseret basename og indgærdet af en fail-closed extension-allowlist
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
  RawName, SafeName, Ext: string;
  Data: TBytes;
begin
  RawName := string(Pdf.AttachmentName[Index]);
  SafeName := ExtractFileName(RawName);    // fjerner alle stikomponenter
  Ext := LowerCase(ExtractFileExt(SafeName));

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

  Data := Pdf.Attachment[Index];           // indlejret nyttelast som rå bytes
  TFile.WriteAllBytes(
    IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;

Foretræk tilladelseslisteretningen. En blokliste over "farlige" udvidelser er et kapløb, man taber den dag, nogen bevæbner en udvidelse, man aldrig har hørt om; en tilladelsesliste over .pdf, .png og .csv fejler lukket

Hvad krypteringstilladelser rent faktisk lover

Standardsikkerhedshåndteringen i ISO 32000-1 koder tilladelsesflag til udskrivning, indholdskopiering og ændring, og egenskaberne Permissions og UserPermissions eksponerer dem som rå bitmasker, når dokumentet åbnes. ISO 32000-1 Tabel 22 definerer bittene, og en ukrypteret fil rapporterer hver bit sat. Læs dem, og overhold dem i dit kommandolag, men vær klar over, hvad de er. For et dokument krypteret med en ejeradgangskode og en tom brugeradgangskode dekrypteres indholdet fuldt ud ved åbning, og flagene er en anmodning til konforme viewere, ikke en håndhævelsesmekanisme. Det har to konsekvenser, og de trækker i modsatte retninger. Præsentér aldrig tilladelsesflag for brugere som en sikkerhedsegenskab ved de dokumenter, de modtager, fordi de ikke er det. Samtidig skal man overholde tilgængeligheds-udtræksbitten (bit 10), selv hvor generel kopiering (bit 5) nægtes; skærmlæseradgang er bevidst udskilt separat i tilladelsesmodellen, og at fjerne den, fordi "kopiering er slået fra", ødelægger hjælpeteknologi uden nogen sikkerhedsgevinst

Håndhæv nægtede handlinger på kommandoniveau, ikke ved at skjule værktøjslinjeknapper. Ctrl+C, genvejsmenuer og træk-markering omgår alle sammen en værktøjslinje; en enkelt tilladelseskontrol inde i kopi-kommandoen omgår intet

For dokumenter, der reelt kræver en brugeradgangskode, tildeles Password, før Active := True, og værdien behandles som den hemmelighed, den er: hent den fra din legitimationsopbevaring pr. session, hold den ude af logs og crash-rapporter, og gem den aldrig sammen med dokumentet. En forhåndsvisningsrude, der cacher adgangskoder "for nemheds skyld", er i det stille blevet en adgangskodedatabase uden nogen af beskyttelserne fra en sådan

Udskrivning fortjener sin egen beslutning fremfor at arve, hvad kopieringsreglen landede på. En fysisk udskrift er pr. definition uovervåget, men at blokere udskrivning helt har en tendens til at skubbe brugere mod skærmbilleder, som er værre på alle akser. Et almindeligt kompromis er at tillade udskrivning, men stemple hver side med brugerens identitet og et tidsstempel, håndhævet inde i udskriftskommandoen. Hold blot den rette forventning til det: et vandmærke er afskrækkelse og tilskrivning. Det er ikke forebyggelse

Hvad indtaget allerede burde have fortalt dig

En forhåndsvisningsrude træffer bedre beslutninger, når filen dukker op med et dossier allerede vedhæftet: krypteret eller ej, JavaScript til stede eller ej, en optælling af vedhæftninger, formulartypen. Det inspektionstrin hører hjemme opstrøms for viewer'en, og mønsteret i opbygning af en arbejdsstation til gennemgang af indkomne PDF'er producerer præcis de flag, en forhåndsvisningspolitik ønsker at forbruge. Filer, som indtaget markerede som risikable, åbner automatisk gennem den hærdede sti; rutinemæssige dokumenter beholder deres bekvemmeligheder. Bind de to trin til ét delt politikobjekt fremfor to konfigurationsskærme, som vil glide fra hinanden inden næste udgivelse, uanset hvor omhyggeligt man skriver dem første gang

Hvor grænsen falder mellem in-process og out-of-process, afhænger af, hvem der sender dig filer. For almindeligt forretningsindtag er de personer, der sender dokumenter, kendte og blot skødesløse, og in-process-forhåndsvisning med scripting slået fra og links opfanget er en forsvarlig standard. For anonyme offentlige uploads er det ikke, og ingen mængde in-process-flag-sætning gør det til en; rendre dem i en separat arbejder med lave privilegier, og send bitmaps til brugerfladen, så en motorfejl koster en arbejder i stedet for værtsapplikationen. Beslut den opdeling bevidst, og skriv ned, hvilken kategori hver indtagssti falder i, fordi omkostningen ved at gætte forkert er asymmetrisk

Licensering, den sikkerhedsrelaterede API-flade og en demo af en hærdet viewer findes på produktsiden: PDFium Component