En PDF er ikke bare papir. Det er en beholder som kan bære skripter som kjører når filen åpnes, lenker som starter eksterne programmer, lenker som kontakter webservere, filer nestet inni filer, og en signatur som hevder at dokumentet ikke er endret siden noen gikk god for det. Når en fil kommer fra en kilde du ikke kontrollerer, er det sikreste første trekket å ikke gjengi den. Det er å lese hva filen sier om seg selv og bygge en oversikt over alt den kan prøve å gjøre, slik at et menneske kan avgjøre om den hører hjemme i arbeidsflyten din i det hele tatt.
Denne artikkelen går gjennom en statisk, skrivebeskyttet revisjonsgjennomgang av denne risikoflaten ved bruk av PDFium-komponenten for Delphi og Lazarus. Revisjonen tegner aldri en side. Den tolker dokumentstrukturen, kartlegger delene av filen som bærer adferd, og skriver en ren rapport. Det er forskjellen mellom å be en fremmed om å tømme lommene ved døren og å stole på dem fordi de smilte.
Hva en revisjon er, og hva den ikke er
Vær tydelig på grensen. En sandkasse-forhåndsvisning (sandboxed preview) gjengir en fil under strenge restriksjoner slik at en bruker kan se på den uten at filen berører resten av maskinen. En revisjon (audit) kommer før det. Det er en gjengivelsesfri inspeksjon der det eneste resultatet er en beskrivelse av trusselflaten: hvilke skripter som finnes, hvilke handlinger som er koblet til lenker, om filen er signert og hvor stramt, og hva som er vedlagt. Du kjører den når et dokument krysser en tillitsgrense, ved mottak fra e-post, et opplastingsskjema eller en partner-feed, før noe senere stadium åpner den på ekte.
Komponenten laster inn et dokument på samme måte for en revisjon som for alt annet. Du angir filnavnet og aktiverer det, noe som tolker kryssreferansedataene og dokumentkatalogen uten å gjengi en eneste side. Alt under leser fra denne lastede, ugjengitte tilstanden.
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 navnetreet
Det første som må kartlegges er kode. En PDF kan bære JavaScript på dokumentnivå: skripter som ikke er knyttet til noen side eller felt, men til selve dokumentet, lagret i /Names-treet under en /JavaScript-oppføring. Et samsvarende visningsprogram kjører disse ved åpning. Det er mekanismen bak en lang rekke PDF-skadevare, fordi det lar en fil kjøre logikk i det øyeblikket en bruker dobbeltklikker på den, før de har lest et ord.
En revisor vil ha to fakta om hvert slikt skript: at det eksisterer, og hva det inneholder. Komponenten eksponerer antallet og lar deg lese hver handling som en post som inneholder skriptets navn og dets fulle kropp. Å lese kroppen betyr noe. Et skript kalt Doc.0 forteller deg ingenting, men teksten kan kalle app.launchURL eller sette sammen en streng og sende den et sted den ikke burde gå. Å hente ut kildekoden slik at en evaluator kan lese den, er hele poenget med å flagge en fil som kjører kode ved åpning.
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 null dokumentskripter er ikke automatisk trygg, fordi side- og feltskripter finnes også, men en fil med dokumentskripter fortjener alltid en ekstra titt. Antallet i seg selv er en nyttig port, og kroppen er det som gjør en port til en avgjørelse.
Launch- og URI-handlinger
Den neste adferden som må kartlegges lever på lenker og annoteringer. To handlingstyper betyr mest for en revisor. En Launch-handling starter et eksternt program eller åpner en lokal fil når lenken utløses. En URI-handling åpner et webmål. En evaluator som ser på et mistenkelig dokument bør kunne se, uten å klikke på noe, at en knapp på side tre er koblet til å kjøre cmd.exe eller åpne en URL som ikke samsvarer med merkevaren på siden.
Komponenten klassifiserer lenkene den finner og eksponerer handlingstypen og målbanen for hver av dem, slik at en revisjon kan liste hver Launch- og URI-handling med sin destinasjon. Dette er rapportering, ikke utførelse. Revisoren leser handlingen ut av strukturen og skriver den ned. Den følger den aldri.
Visningskontrollen som gjengir dokumenter er stedet der det å følge en handling ville skje, og dens standardinnstilling er bevisst forsiktig. Kontrollen TPdfView har et LinkOptions-sett som bestemmer hvilke lenketyper som utløses automatisk ved et klikk. Standardinnstillingen er [loAutoGoto, loAutoOpenURI], noe som betyr at interne hopp i dokumentet og web-URL-er kan åpnes, men loAutoLaunch er fraværende, så launch-handlinger kjører aldri automatisk. For en revisjonsarbeidsflyt går du lenger og tømmer settet helt, slik at ingenting i det hele tatt utløses automatisk mens du fortsatt avgjør om du skal 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.
Begrunnelsen bak å holde tilbake launch som standard er enkel. Et hopp innenfor dokumentet er ufarlig og en URL er synlig og kan avbrytes, men å starte et vilkårlig eksternt program fra et klikk er den desidert farligste tingen en PDF-lenke kan be om, så det er slått av med mindre du velger det inn. En revisor velger bort selv de trygge adferdene, fordi jobben er å se, ikke å handle.
Det digitale signatur-MDP-tillatelsesnivået
Signaturer endrer spørsmålet. En vanlig signatur bekrefter bytene på signeringstidspunktet. En sertifiseringssignatur, typen opprettet med en regel for gjenkjenning og forebygging av dokumentendringer (MDP), går lenger: den erklærer hva som lovlig kan endres etter at dokumentet ble sertifisert, og et samsvarende visningsprogram advarer hvis noe utenfor denne tillatelsen er berørt. Å lese dette tillatelsesnivået forteller en revisor om en fil er sertifisert og i så fall hvor låst den er ment å være.
MDP-tillatelsen er et heltall med tre definerte verdier. Et nivå på 1 betyr at ingen endringer er tillatt i det hele tatt; enhver endring bryter sertifiseringen. Et nivå på 2 tillater skjemautfylling og signering, det vanlige tilfellet for en kontrakt som er ment å fylles ut og signeres, men ikke endres på annen måte. Et nivå på 3 tillater i tillegg annoteringer på toppen av skjemautfylling og signering. Å kjenne nivået lar mottakslogikken din resonnere rundt intensjon: et dokument sertifisert på nivå 1 som likevel inneholder skjemafelt eller skripter motsier seg selv, og den selvmotsigelsen er verdt å flagge.
Komponenten leser antallet signaturer og eksponerer hver av dem som en post der Permission-feltet bærer den MDP-verdien, hentet direkte fra det underliggende FPDFSignatureObj_GetDocMDPPermission-kallet. En tillatelse på null betyr at signaturen ikke er en sertifiseringssignatur (DocMDP), så det er ingen dokumentlåsing å 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 revisjon validerer ikke kryptografien til signaturen her; å verifisere sertifikatkjeden er en separat sak. Det den rapporterer er den erklærte intensjonen: denne filen sier den ble låst på dette nivået. Det er akkurat den konteksten en evaluator trenger for å vurdere om senere endringer, eller bare tilstedeværelsen av aktivt innhold, samsvarer med hvordan forfatteren forseglet dokumentet.
Resten av flaten: vedlagte filer og XFA
Ytterligere to elementer fullfører en komplett kartlegging. Innebygde filer er hele dokumenter som bæres inni PDF-en som vedlegg, og de er a klassisk leveringsmetode, fordi en uskyldig utseende rapport kan levere en kjørbar fil eller en ny skadelig PDF i sitt vedleggstre. Komponenten eksponerer antallet vedlegg og navnet på hvert vedlegg, slik at revisjonen kan liste hva som følger med uten å trekke ut eller åpne noe av det.
XFA-tilstedeværelse er det andre flagget. Et XFA-skjema erstatter det statiske AcroForm med en XML-basert skjemastruktur som bringer med seg sin egen gjengivelses- og skriptmodell, en større og mer kompleks flate enn et vanlig skjema. Du trenger ikke å behandle XFA-en for å merke at den er der; dens tilstedeværelse i seg selv er et signal om at filen bærer et rikere interaktivt lag som er verdt en nærmere titt. Komponenten rapporterer det som en enkelt boolsk verdi.
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 som skriver en rapport
Sett sammen bitene, og revisjonen er en enkelt prosedyre som laster et dokument, kartlegger dets skripter og kropper, lister opp dets Launch- og URI-mål, rapporterer signaturens MDP-nivå, noterer vedlegg og XFA, og skriver funnene til en logg. Den gjengir ingenting, så den er billig og kan ikke lures til å vise fiendtlig sideinnhold. Utdataene er en flat, menneskelig lesbar oppføring som en evaluator eller en nedstrøms regel kan reagere på.
Formen som fungerer bra i praksis er å samle hvert funn som en linje, sette prefiks på de som virkelig er risikable slik at de sorteres til toppen av en evalueringskø, og lagre hele greia ved siden av filen. Et dokument uten skripter, ingen launch-handlinger, ingen vedlegg, ingen XFA, og enten ingen signatur eller en sammenhengende sertifisering passerer stille. Et dokument som utløser flere flagg samtidig, er det en person bør se før et senere stadium åpner det. Revisjonen tar ikke tillitsavgjørelsen for deg. Den sørger for at avgjørelsen er informert i stedet for blind.
Once a file clears the audit and you do need to look at it, do so under restriction rather than in a default viewer. The approach in our walkthrough on building a secure PDF preview in Delphi shows how to keep link auto-handling and active content from acting during a controlled look. To fold this enumeration into a full intake pipeline with reviewer tooling, see the PDF intake and review workbench article. Both build on the same read-only, render-free foundation and ship as part of the PDFium Component for Delphi and C++Builder, alongside the rendering, text, form, and signature APIs covered elsewhere on this blog.