En PDF er ikke bare papir. Det er en beholder, der kan indeholde scripts, som kører, når filen åbnes, links, der starter eksterne programmer, links, der rækker ud til webservere, filer indlejret i filer, og en signatur, der hævder, at dokumentet ikke har ændret sig, siden nogen stod inde for det. Når en fil ankommer fra en kilde, du ikke kontrollerer, er det sikreste første skridt ikke at rendere den. Det er at læse, hvad filen siger om sig selv, og opbygge en oversigt over alt, hvad den kunne forsøge at gøre, så et menneske kan beslutte, om den overhovedet hører hjemme i dit arbejdsflow.
Denne artikel gennemgår en statisk, skrivebeskyttet audit-gennemgang af den risikoflade ved hjælp af PDFium-komponenten til Delphi og Lazarus. Auditeringen tegner aldrig en side. Den parser dokumentstrukturen, oplister de dele af filen, der bærer adfærd, og skriver en enkel rapport. Det er forskellen på at bede en fremmed om at tømme lommerne ved døren og at stole på dem, fordi de smilede.
Hvad en auditering er, og hvad den ikke er
Vær klar over grænsen. En sandkasse-forhåndsvisning (sandboxed preview) renderer en fil under strenge restriktioner, så en bruger kan se på den, uden at filen berører resten af maskinen. En auditering kommer før det. Det er en renderingsfri inspektion, hvis eneste output er en beskrivelse af trusselsfladen: Hvilke scripts der findes, hvilke handlinger der er forbundet til links, om filen er signeret og hvor stramt, og hvad der er vedhæftet. Du kører den, når et dokument krydser en tillidsgrænse, ved modtagelse fra e-mail, en uploadformular eller et partnerfeed, før noget senere trin åbner den for alvor.
Komponenten indlæser et dokument på samme måde til en auditering som til alt andet. Du angiver filnavnet og aktiverer den, hvilket parser krydsreferencedataene (cross-reference data) og dokumentkataloget uden at rendere en eneste side. Alt i det følgende læser fra den indlæste, urenderede tilstand.
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'Incoming_Invoice.pdf';
Pdf.Active := True; // parses structure, renders nothing
// audit the loaded document here
finally
Pdf.Free;
end;
end;
Dokument-JavaScript i navnetræet
Det første, der skal oplistes, er kode. En PDF kan bære JavaScript på dokumentniveau: Scripts, der ikke er knyttet to nogen side eller noget felt, men til selve dokumentet, gemt i /Names-træet under en /JavaScript-post. En standardkonform fremviser kører disse ved åbning. Det er mekanismen bag en lang række PDF-malware, fordi det lader en fil udføre logik i samme sekund, en bruger dobbeltklikker på den, før de har læst et ord.
En auditor ønsker to fakta om hvert sådant script: At det eksisterer, og hvad det indeholder. Komponenten eksponerer antallet og lader dig læse hver handling som en post, der indeholder scriptets navn og dets fulde krop. Reading kroppen er vigtigt. Et script ved navn Doc.0 fortæller dig intet, men dets tekst kan kalde app.launchURL eller samle en streng og sende den et sted hen, hvor den ikke burde komme. At trække kildekoden ud, så en anmelder kan læse den, er hele pointen med at flage en fil, der kører kode ved åbning.
var
I: Integer;
Action: TPdfJavaScriptAction;
begin
if Pdf.JavaScriptActionCount > 0 then
WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
' script(s) on open');
for I := 0 to Pdf.JavaScriptActionCount - 1 do
begin
Action := Pdf.JavaScriptAction[I];
WriteLn(' script "', Action.Name, '":');
WriteLn(Action.Script); // full body, for a human to read
end;
end;
En fil med nul dokument-scripts er ikke automatisk sikker, fordi der også findes side- og felt-scripts, men en fil med dokument-scripts fortjener altid et ekstra kig. Antallet af scripts alene er en nyttig barriere, og scriptkroppen er det, der gør en barriere til en reel vurdering.
Launch- og URI-handlinger
Den næste adfærd, der skal registreres, lever på links og annoteringer. To handlingstyper betyder mest for en auditor. En Launch-handling starter et eksternt program eller åbner en lokal fil, når linket udløses. En URI-handling åbner et webmål. En anmelder, der ser på et mistænkeligt dokument, bør uden at klikke på noget kunne se, at en knap på side tre er forbundet til at starte cmd.exe eller til at åbne en URL, der ikke matcher brandet på siden.
Komponenten klassificerer de links, den finder, og eksponerer handlingstypen og målstien for hver, så en auditering kan liste enhver Launch- og URI-handling med dens destination. Dette is rapportering, ikke udførelse. Auditoren læser handlingen ud af strukturen og skriver den ned. Den følger den aldrig.
Fremviser-komponenten, der renderer dokumenter, er det sted, hvor en handling ville blive fulgt, og dens standardindstilling er bevidst forsigtig. TPdfView-komponenten har et LinkOptions-sæt, der bestemmer, hvilke linktyper der udløses automatisk ved klik. Dens standard er [loAutoGoto, loAutoOpenURI], hvilket betyder, at spring internt i dokumentet og web-URL'er kan åbnes, men loAutoLaunch er fraværende, så launch-handlinger aldrig kører automatisk. Til et audit-arbejdsflow går du videre og rydder sættet helt, så intet overhovedet udløses automatisk, mens du stadig beslutter, om du vil stole på filen.
// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];
// The shipped default already withholds launch:
// default = [loAutoGoto, loAutoOpenURI]
// loAutoLaunch is NOT in the default set, so external programs
// are never started on a stray click out of the box.
Ræsonnementet bag at udelade launch som standard er simpelt. Et spring i dokumentet er harmløst, og en URL er synlig og kan afbrydes, men at starte et vilkårligt eksternt program fra et klik er det absolut farligste, et PDF-link kan bede om, så det er slået fra, medmindre du aktivt tilvælger det. En auditor fravælger selv de sikre adfærdsmønstre, fordi opgaven er at observere, ikke at handle.
Den digitale signaturs MDP-tilladelsesniveau
Signaturer ændrer spørgsmålet. En almindelig signatur bekræfter bytes på signeringstidspunktet. En certificeringssignatur, af den type der oprettes med en regel til detektering og forebyggelse af dokumentændring (modification detection and prevention), går videre: Den erklærer, hvad der legitimt må ændres, efter at dokumentet blev certificeret, og en kompatibel fremviser advarer, hvis der er rørt ved noget uden for den tilladelse. Aflæsning af det tilladelsesniveau fortæller en auditor, om en fil er certificeret, og i givet fald, hvor stramt den er beregnet til at være låst.
MDP-tilladelsen er et heltal med tre definerede værdier. Et niveau på 1 betyder, at der overhovedet ikke er tilladt nogen ændringer; enhver ændring bryder certificeringen. Et niveau på 2 tillader udfyldning af formularer og signering, hvilket er det almindelige tilfælde for en kontrakt, der skal udfyldes og signeres, men ikke ændres på anden vis. Et niveau på 3 tillader desuden annoteringer oven i udfyldning og signering. At kende niveauet lader din modtagelseslogik ræsonnere om hensigten: Et dokument, der er certificeret på niveau 1, men som alligevel indeholder formularfelter eller scripts, modsiger sig selv, og den modsigelse er værd at markere.
Komponenten læser antallet af signaturer og eksponerer hver enkelt som en post, hvis Permission-felt bærer den pågældende MDP-værdi, udfyldt direkte fra det underliggende FPDFSignatureObj_GetDocMDPPermission-kald. En tilladelse på nul betyder, at signaturen ikke er en certificeringssignatur (DocMDP), så der er ingen dokumentlåsning at rapportere.
var
I: Integer;
Sig: TPdfSignature;
begin
if Pdf.SignatureCount = 0 then
WriteLn('document is not signed')
else
for I := 0 to Pdf.SignatureCount - 1 do
begin
Sig := Pdf.Signature[I];
case Sig.Permission of
1: WriteLn('certified: no changes allowed');
2: WriteLn('certified: form fill and signing allowed');
3: WriteLn('certified: form fill, signing and annotations allowed');
else
WriteLn('signed, but not a DocMDP certification');
end;
end;
end;
En auditering validerer ikke kryptografien i signaturen her; verificering af certifikatkæden er en separat sag. Det, den rapporterer, er den erklærede hensigt: Denne fil siger, at den var låst på dette niveau. Det er præcis den kontekst, en anmelder har brug for for at vurdere, om senere ændringer eller blot tilstedeværelsen af aktivt indhold stemmer overens med, hvordan forfatteren forseglede dokumentet.
Resten af overfladen: Indlejrede filer og XFA
Yderligere to punkter fuldender en komplet oversigt. Indlejrede filer er hele dokumenter, der bæres inde i PDF-filen som vedhæftede filer, og de er et klassisk leveringsmiddel, fordi en rapport, der ser harmløs ud, kan transportere en eksekverbar fil eller en anden ondsindet PDF i sit træ af vedhæftede filer. Komponenten eksponerer antallet af vedhæftede filer og navnet på hver vedhæftet fil, så auditeringen kan opliste, hvad der kører med, uden at udtrække eller åbne noget af det.
Tilstedeværelsen af XFA er det andet flag. En XFA-formular erstatter den statiske AcroForm med en XML-baseret formulararkitektur, der bringer sin egen render- og scriptingmodel – en større og mere kompleks overflade end en almindelig formular. Du behøver ikke at behandle XFA for at bemærke, at det er der; dets blotte tilstedeværelse er et signal om, at filen bærer et rigere interaktivt lag, der er værd at kigge nærmere på. Komponenten rapporterer det som en enkelt boolean.
var
I: Integer;
begin
if Pdf.XFA then
WriteLn('NOTE: document contains an XFA form layer');
if Pdf.AttachmentCount > 0 then
begin
WriteLn('embedded files: ', Pdf.AttachmentCount);
for I := 0 to Pdf.AttachmentCount - 1 do
WriteLn(' - ', Pdf.AttachmentName[I]);
end;
end;
Én skrivebeskyttet rutine, der skriver en rapport
Sæt delene sammen, og auditeringen er en enkelt procedure, der indlæser et dokument, oplister dets scripts og deres kroppe, lister dets Launch- og URI-mål, rapporterer signaturens MDP-niveau, bemærker vedhæftede filer og XFA og skriver resultaterne til en logfil. Den renderer intet, så den er billig i drift og kan ikke narres til at vise ondsindet sideindhold. Outputtet er en flad, letlæselig registrering, som en anmelder eller en efterfølgende regel kan handle på.
Den form, der fungerer godt i praksis, er at samle hvert fund som en linje, foranstille et præfiks på de reelt risikable, så de sorteres øverst i en anmeldelseskø, og gemme det hele ved siden af filen. Et dokument uden scripts, ingen launch-handlinger, ingen vedhæftede filer, ingen XFA, og enten ingen signatur eller en sammenhængende certificering passerer stille igennem. Et dokument, der udløser flere flag på én gang, er det, en person bør se, før noget senere trin åbner det. Auditeringen træffer ikke tillidsbeslutningen for dig. Den sikrer, at beslutningen er informeret frem for blind.
Når en fil har klaret auditeringen, og du har brug for at se på den, skal du gøre det under restriktioner frem for i en standardfremviser. Metoden i vores gennemgang af opbygning af et sikkert PDF-preview i Delphi viser, hvordan man forhindrer automatisk linkhåndtering og aktivt indhold i at agere under en kontrolleret visning. For at folde denne oplistning ind i en fuld modtagelsespipeline med anmelder-værktøjer, se artiklen om PDF-modtagelse og anmelder-arbejdsbænk. Begge bygger på det samme skrivebeskyttede, renderingsfrie grundlag og leveres som en del af PDFium Component til Delphi og C++Builder sammen med API'erne til rendering, tekst, formularer og signaturer, der er dækket andre steder på denne blog.