PDF ni le papir. Je vsebnik, ki lahko nosi skripte, ki se zaženejo ob odprtju datoteke, povezave, ki zaženejo zunanje programe, povezave, ki dosežejo spletne strežnike, datoteke, gnezdene znotraj datotek, in podpis, ki trdi, da se dokument ni spremenil, odkar je nekdo jamčil zanj. Ko datoteka prispe iz vira, ki ga ne nadzorujete, najvarnejša prva poteza ni njeno izrisovanje. Najbolje je prebrati, kaj datoteka pravi o sebi, in zgraditi popis vsega, kar bi lahko poskusila storiti, da se lahko človek odloči, ali sploh sodi v vaš delovni tok.
Ta članek obravnava statičen revizijski prehod (audit pass) samo za branje čez to območje tveganja z uporabo komponente PDFium za Delphi in Lazarus. Revizija nikoli ne nariše strani. Razčleni strukturo dokumenta, popiše dele datoteke, ki nosijo obnašanje, in napiše preprosto poročilo. To je razlika med tem, da od neznanca zahtevate, da izprazni žepe pri vratih, in tem, da mu zaupate le zato, ker se je nasmehnil.
Kaj revizija je in kaj ni
Bodite jasni glede meje. Predogled v peskovniku (sandboxed preview) izriše datoteko pod strogimi omejitvami, tako da si jo lahko uporabnik ogleda, ne da bi se datoteka dotaknila preostalega dela računalnika. Revizija (audit) pride pred tem. Gre za pregled brez izrisovanja, katerega edini izhod je opis površine groženj: kateri skripti obstajajo, katera dejanja so povezana s povezavami, ali je datoteka podpisana in kako strogo ter kaj ji je priloženo. Zaženete jo, ko dokument prečka mejo zaupanja – ob prejemu iz e-pošte, obrazca za prenos ali partnerskega vira, preden jo katera koli kasnejša faza dejansko odpre.
Komponenta naloži dokument na enak način za revizijo kot za vse ostalo. Nastavite ime datoteke in jo aktivirate, kar razčleni podatke navzkrižnih sklicev (cross-reference) in katalog dokumenta, ne da bi izrisala eno samo stran. Vse spodaj opisano bere iz tega naloženega, neizrisanega stanja.
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;
JavaScript dokumenta v drevesu imen
Prva stvar, ki jo je treba popisati, je koda. PDF lahko nosi JavaScript na ravni dokumenta: skripte, ki niso pripeti na nobeno stran ali polje, temveč na sam dokument, shranjene v drevesu /Names pod vnosom /JavaScript. Skladen pregledovalnik jih zažene ob odprtju. To je mehanizem, ki stoji za dolgo vrsto zlonamerne programske opreme PDF, saj datoteki omogoča izvajanje logike v trenutku, ko uporabnik dvoklikne nanjo, preden sploh prebere eno besedo.
Revizor želi dve dejstvi o vsakem takem skriptu: da obstaja in kaj vsebuje. Komponenta izpostavi število in vam omogoča branje vsakega dejanja kot zapisa, ki vsebuje ime skripta in njegovo celotno telo. Branje telesa je pomembno. Skript z imenom Doc.0 vam ne pove ničesar, toda njegovo besedilo lahko kliče app.launchURL ali sestavi niz in ga posreduje tja, kamor ne bi smel iti. Izvlečenje vira, da ga pregledovalec lahko prebere, je celoten namen označevanja datoteke, ki ob odprtju izvaja kodo.
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;
Datoteka brez skriptov dokumenta ni samodejno varna, saj obstajajo tudi skripti strani in polj, toda datoteka s skripti dokumenta si vedno zasluži drugi pogled. Samo število prisotnosti je koristen filter, telo pa je tisto, kar filter spremeni v presojo.
Dejanja zagona in dejanja URI
Naslednje obnašanje za popis živi na povezavah in pripombah. Za revizorja sta najpomembnejši dve vrsti dejanj. Dejanje zagona (Launch action) zažene zunanji program ali odpre lokalno datoteko, ko se povezava sproži. Dejanje URI odpre spletni cilj. Pregledovalec, ki gleda sumljiv dokument, bi moral imeti možnost videti, ne da bi na kar koli kliknil, da je gumb na tretji strani povezan z zagonom programa cmd.exe ali z odpiranjem naslova URL, ki se ne ujema z blagovno znamko na strani.
Komponenta razvrsti najdene povezave in izpostavi vrsto dejanja ter ciljno pot za vsako od njih, tako da lahko revizija izpiše vsako dejanje zagona in URI z njunim ciljem. To je poročanje in ne izvajanje. Revizor prebere dejanje iz strukture in ga zapiše. Nikoli mu ne sledi.
Kontrolnik pregledovalnika, ki izrisuje dokumente, je mesto, kjer bi se sledenje dejanju sicer zgodilo, njegova privzeta drža pa je namenoma previdna. Kontrolnik TPdfView vsebuje nabor LinkOptions, ki določa, katere vrste povezav se samodejno sprožijo ob kliku. Njegova privzeta vrednost je [loAutoGoto, loAutoOpenURI], kar pomeni, da se skoki znotraj dokumenta in spletni naslovi URL lahko odprejo, loAutoLaunch pa je odsoten, zato se dejanja zagona nikoli ne zaženejo samodejno. Za revizijski delovni tok greste še dlje in nabor v celoti počistite, tako da se nič ne sproži samodejno, medtem ko se še odločate, ali datoteki zaupati.
// 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.
Razlog za privzeto zadržanje zagona je preprost. Skok znotraj dokumenta je neškodljiv, naslov URL pa je viden in ga je mogoče preklicati, medtem ko je zagon poljubnega zunanjega programa s klikom najbolj nevarna stvar, ki jo povezava PDF lahko zahteva, zato je izklopljena, razen če se zanjo odločite. Revizor se odpove celo varnim obnašanjem, saj je njegovo delo opazovanje in ne ukrepanje.
Raven dovoljenja MDP digitalnega podpisa
Podpisi spreminjajo vprašanje. Navaden podpis potrjuje bajte ob času podpisovanja. Certifikacijski podpis (certification signature), tiste vrste, ki je ustvarjen s pravilom za zaznavanje in preprečevanje sprememb dokumenta (modification detection and prevention), gre še dlje: izjavlja, kaj se lahko upravičeno spremeni po tem, ko je bil dokument certificiran, skladen pregledovalnik pa opozori, če se je kdo dotaknil česar koli zunaj tega dovoljenja. Branje te ravni dovoljenja revizorju pove, ali je datoteka certificirana in, če je, kako strogo naj bi bila zaklenjena.
Dovoljenje MDP je celo število s tremi določenimi vrednostmi. Raven 1 pomeni, da spremembe sploh niso dovoljene; vsaka sprememba krši certifikat. Raven 2 dovoljuje izpolnjevanje obrazcev in podpisovanje – to je pogost primer za pogodbo, ki jo je treba izpolniti in podpisati, ne pa kako drugače spreminjati. Raven 3 dodatno dovoljuje pripombe poleg izpolnjevanja obrazcev in podpisovanja. Poznavanje te ravni omogoča vaši logiki vnosa sklepanje o namenu: dokument, certificiran na ravni 1, ki kljub temu vsebuje obrazčna polja ali skripte, je v nasprotju s samim seboj, to protislovje pa je vredno označiti.
Komponenta bere število podpisov in vsakega izpostavi kot zapis, katerega polje Permission nosi to vrednost MDP, pridobljeno neposredno iz temeljnega klica FPDFSignatureObj_GetDocMDPPermission. Dovoljenje nič pomeni, da podpis ni certifikacijski (DocMDP), zato ni zapore na ravni dokumenta, o kateri bi morali poročati.
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;
Revizija tukaj ne preverja kriptografije podpisa; preverjanje verige potrdil je ločeno vprašanje. Kar poroča, je deklarirani namen: ta datoteka pravi, da je bila zaklenjena na tej ravni. To je natanko tisti kontekst, ki ga pregledovalec potrebuje za presojo, ali so kasnejše spremembe ali zgolj prisotnost aktivne vsebine skladne s tem, kako je avtor zapečatil dokument.
Preostali del površine: vdelane datoteke in XFA
Dva dodatna elementa dopolnjujeta celoten popis. Vdelane datoteke so celotni dokumenti, ki se prenašajo znotraj PDF-ja kot priloge, in so klasično dostavno sredstvo, saj lahko na videz neškodljivo poročilo prenaša izvedljivo datoteko ali drug zlonameren PDF v svojem drevesu prilog. Komponenta izpostavi število prilog in ime vsake od njih, tako da revizija lahko izpiše, kaj potuje zraven, ne da bi kar koli od tega ekstrahirala ali odpirala.
Prisotnost XFA je druga zastavica. Obrazec XFA zamenja statični AcroForm z modelom obrazca na osnovi XML, ki prinaša lastno izrisovalno in skriptno okolje – večjo in bolj zapleteno površino kot navaden obrazec. Za opozorilo, da je tam, vam ni treba procesirati XFA; njegova zgolj prisotnost je signal, da datoteka nosi bogatejši interaktivni sloj, vreden podrobnejšega pregleda. Komponenta o tem poroča kot o logični vrednosti.
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;
Ena rutina samo za branje, ki izda poročilo
Sestavite dele skupaj in revizija je en sam postopek, ki naloži dokument, popiše njegove skripte in njihova telesa, navede cilje Launch in URI, poroča o ravni dovoljenja MDP podpisa, zabeleži priloge in XFA ter zapiše ugotovitve v dnevnik. Ničesar ne izrisuje, zato je poceni in je ni mogoče prelisičiti v prikazovanje sovražne vsebine strani. Izhod je raven, človeku berljiv zapis, na podlagi katerega lahko ukrepa pregledovalec ali naslednje pravilo v delovnem toku.
Oblika, ki se v praksi dobro obnese, je zbiranje vsake ugotovitve v eno vrstico, dodajanje predpon resnično tveganim, da se razvrstijo na vrh čakalne vrste za pregled, in shranjevanje celotnega zapisa poleg datoteke. Dokument brez skriptov, brez dejanj zagona, brez prilog, brez XFA in bodisi brez podpisa ali s koherentnim certifikatom gre naprej brez opozoril. Dokument, ki sproži več zastavic hkrati, pa je tisti, ki bi ga morala oseba videti, preden ga katera koli kasnejša faza dejansko odpre. Revizija ne sprejme odločitve o zaupanju namesto vas. Poskrbi le, da je odločitev informirana in ne slepa.
Ko datoteka opravi revizijo in si jo morate ogledati, to storite pod omejitvami in ne v privzetem pregledovalniku. Pristop v našem vodiču o gradnji varnega predogleda PDF v Delphi prikazuje, kako preprečiti samodejno delovanje povezav in aktivne vsebine med nadzorovanim ogledom. Za vključitev tega popisa v celoten vhodni cevovod z orodji za pregledovanje glejte članek o delovni mizi za vnos in pregled PDF. Oboje gradi na isti osnovi samo za branje in brez izrisovanja ter se pošilja kot del komponente PDFium Component za Delphi in C++Builder, skupaj z vmesniki API za izrisovanje, besedilo, obrazce in podpise, ki so obravnavani drugje na tem blogu.