A PDF nem csupán papír. Ez egy konténer, amely hordozhat szkripteket, amelyek a fájl megnyitásakor futnak le, külső programokat indító linkeket, webszervereket elérő linkeket, fájlokba ágyazott fájlokat, valamint egy aláírást, amely igazolja, hogy a dokumentum nem változott azóta, hogy valaki jótállt érte. Ha a fájl egy nem ellenőrzött forrásból érkezik, a legbiztonságosabb első lépés nem a renderelése. Hanem az, hogy elolvassuk, mit mond a fájl önmagáról, és leltárt készítsünk mindenről, amit megpróbálhat tenni, így egy ember eldöntheti, hogy egyáltalán beletartozik-e a munkafolyamatba.
Ez a cikk egy statikus, csak olvasható vizsgálatot mutat be ezen a kockázati felületen, a Delphi és Lazarus rendszerekhez készült PDFium komponens segítségével. A vizsgálat soha nem rajzol ki oldalt. Elemzi a dokumentum struktúráját, felsorolja a fájl aktív viselkedést hordozó részeit, és egy egyszerű jelentést készít. Ez a különbség aközött, hogy megkérünk egy idegent, hogy ürítse ki a zsebeit az ajtónál, vagy megbízunk benne csak azért, mert elmosolyodott.
Mi a vizsgálat, és mi nem az
Legyen tisztában a határokkal. Egy homokozóban (sandbox) futó előnézet szigorú korlátozások mellett rendereli a fájlt, így a felhasználó ránézhet anélkül, hogy a fájl érintené a gép többi részét. A vizsgálat ez előtt történik. Ez egy renderelés nélküli ellenőrzés, amelynek egyetlen kimenete a fenyegetési felület leírása: mely szkriptek léteznek, milyen műveletek kapcsolódnak a linkekhez, alá van-e írva a fájl és mennyire szigorúan, valamint mik a mellékletek. Ezt akkor futtatja, amikor a dokumentum átlép egy bizalmi határt – például e-mailből érkezik, feltöltő űrlapon vagy partnercsatornán keresztül –, mielőtt bármelyik későbbi szakasz valóban megnyitná.
A komponens ugyanúgy tölti be a dokumentumot a vizsgálathoz, mint bármi máshoz. Beállítja a fájlnevet és aktiválja, ami leképezi a kereszt-hivatkozási adatokat és a dokumentum-katalógust egyetlen oldal renderelése nélkül. Minden alábbi lépés ebből a betöltött, de le nem képezett állapotból olvas.
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;
Dokumentum JavaScript a név-fában
Az első dolog, amit össze kell írni, a kód. A PDF hordozhat dokumentumszintű JavaScript-et: olyan szkripteket, amelyek nem kapcsolódnak egyetlen oldalhoz vagy mezőhöz sem, hanem magához a dokumentumhoz, a /Names fában tárolva a /JavaScript bejegyzés alatt. A szabványnak megfelelő megjelenítő ezeket megnyitáskor futtatja le. Ez áll a PDF-alapú kártevők hosszú sorának hátterében, mivel lehetővé teszi a fájl számára, hogy abban a pillanatban hajtson végre logikát, amikor a felhasználó duplán rákattint, még mielőtt egyetlen szót is elolvasott volna.
A vizsgálandó személynek két tényre van szüksége minden ilyen szkriptről: hogy létezik-e, és mit tartalmaz. A komponens elérhetővé teszi a darabszámot, és lehetővé teszi az egyes műveletek beolvasását olyan rekordként, amely a szkript nevét és teljes törzsét tartalmazza. A törzs elolvasása fontos. Egy Doc.0 nevű szkript semmit sem mond, de a szövege meghívhatja a app.launchURL függvényt, vagy összeállíthat egy karakterláncot, és továbbíthatja azt oda, ahova nem kellene. A forrás kinyerése azért fontos, hogy a felülvizsgáló elolvashassa, és ez a lényege annak, hogy megjelöljük a megnyitáskor kódot futtató fájlokat.
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;
A dokumentumszkriptek nélküli fájl nem feltétlenül biztonságos automatikusan, mert oldalszintű és mezőszintű szkriptek is létezhetnek, de a dokumentumszkripteket tartalmazó fájl mindig megérdemel egy második pillantást. A jelenlét darabszáma önmagában is hasznos szűrő, és a törzs az, ami a szűrőt ítéletté alakítja át.
Launch és URI műveletek
A következő vizsgálandó viselkedés a linkeken és annotációkon él. Két művelettípus számít leginkább a vizsgálónak. A Launch (indítási) művelet elindít egy külső programot vagy megnyit egy helyi fájlt a link aktiválásakor. Az URI művelet megnyit egy webes célt. A gyanús dokumentumot vizsgáló személynek látnia kell – anélkül, hogy bármire rákattintana –, ha a harmadik oldalon lévő gomb a cmd.exe indítására vagy olyan URL megnyitására van bekötve, amely nem felel meg az oldalon lévő márkának.
A komponens osztályozza a talált linkeket, és elérhetővé teszi az egyes elemek művelettípusát és célútvonalát, így az audit listázni tudja az összes Launch és URI műveletet a célállomásukkal együtt. Ez jelentéskészítés, nem végrehajtás. A vizsgáló kiolvassa a műveletet a struktúrából és leírja. Soha nem követi azt.
A dokumentumokat leképező megjelenítő vezérlő az a hely, ahol a művelet követése történne, és annak alapértelmezett beállítása szándékosan óvatos. A TPdfView vezérlő rendelkezik egy LinkOptions készlettel, amely eldönti, hogy mely linktípusok aktiválódjanak automatikusan kattintásra. Alapértelmezése a [loAutoGoto, loAutoOpenURI], ami azt jelenti, hogy a dokumentumon belüli ugrások és a webes URL-ek megnyílhatnak, de a loAutoLaunch hiányzik, így az indítási műveletek soha nem futnak le automatikusan. Az audit munkafolyamathoz érdemes teljesen kiüríteni ezt a készletet, hogy egyáltalán semmi se aktiválódjon automatikusan, amíg döntést hoz a fájl megbízhatóságáról.
// 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.
Az indítás (launch) alapértelmezett letiltása mögött meghúzódó érvelés egyszerű. A dokumentumon belüli ugrás ártalmatlan, az URL pedig látható és visszavonható, de egy tetszőleges külső program indítása kattintásra a legveszélyesebb dolog, amit egy PDF link kérhet, így ez ki van kapcsolva, hacsak nem dönt mellette. A vizsgáló még a biztonságos viselkedésekről is lemond, mert a feladata a megfigyelés, nem pedig a cselekvés.
A digitális aláírás MDP engedélyezési szintje
Az aláírások megváltoztatják a kérdést. Az egyszerű aláírás az aláírás időpontjában meglévő bájtokat igazolja. A tanúsító aláírás (certification signature) – az a fajta, amely dokumentummódosítás-észlelési és megelőzési szabállyal jön létre – továbbmegy: kijelenti, hogy mi változhat jogszerűen a dokumentum tanúsítása után, és a szabványnak megfelelő megjelenítő figyelmeztet, ha az engedélyen kívüli dologhoz nyúltak hozzá. Ennek az engedélyezési szintnek az olvasása megmondja a vizsgálónak, hogy a fájl tanúsítva van-e, és ha igen, mennyire szigorúan zárolt.
Az MDP engedély három definiált egész értékkel rendelkezik. Az 1-es szint azt jelenti, hogy egyáltalán semmilyen változtatás nem megengedett; bármilyen módosítás érvényteleníti a tanúsítást. A 2-es szint engedélyezi az űrlapkitöltést és aláírást, ami a kitöltendő és aláírandó, de egyébként nem módosítható szerződések általános esete. A 3-as szint ezen felül engedélyezi az annotációk elhelyezését is. Az engedélyezési szint ismerete lehetővé teszi a beléptetési logika számára a szándék elemzését: az 1-es szinten tanúsított dokumentum, amely mégis űrlapmezőket vagy szkripteket tartalmaz, ellentmondásban van önmagával, és ezt az ellentmondást érdemes megjelölni.
A komponens beolvassa az aláírások számát, és mindegyiket olyan rekordként teszi elérhetővé, amelynek a Permission mezője hordozza ezt az MDP értéket, közvetlenül az alatta lévő FPDFSignatureObj_GetDocMDPPermission hívásból feltöltve. A nulla értékű engedély azt jelenti, hogy az aláírás nem tanúsító (DocMDP) aláírás, így nincs jelentendő dokumentumszintű zárolás.
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;
A vizsgálat itt nem ellenőrzi az aláírás kriptográfiáját; a tanúsítványlánc ellenőrzése különálló feladat. Amit jelent, az a deklarált szándék: ez a fájl azt állítja, hogy ezen a szinten lett zárolva. Pontosan ez az a kontextus, amelyre a felülvizsgálónak szüksége van annak megítéléséhez, hogy a későbbi módosítások vagy az aktív tartalom puszta jelenléte összhangban van-e azzal, ahogyan a szerző lezárta a dokumentumot.
A felület többi része: beágyazott fájlok és XFA
Két további elem teszi teljessé a leltárt. A beágyazott fájlok a PDF-en belül csatolmányként hordozott egész dokumentumok, és ezek klasszikus terjesztési eszközök, mert egy ártalmatlannak tűnő jelentés végrehajtható fájlt vagy egy második, rosszindulatú PDF-et hordozhat a csatolmányfájában. A komponens elérhetővé teszi a csatolmányok számát és mindegyik nevét, így a vizsgálat kilistázhatja a csatolmányokat anélkül, hogy bármelyiket is kicsomagolná vagy megnyitná.
Az XFA jelenléte a másik jelző. Az XFA űrlap lecseréli a statikus AcroForm-ot egy XML-alapú űrlap-architektúrára, amely saját leképezési és szkriptelési modellt hoz magával, ami egy egyszerű űrlapnál nagyobb és összetettebb felület. Nem kell feldolgoznia az XFA-t ahhoz, hogy feljegyezze a jelenlétét; a puszta jelenléte jelzi, hogy a fájl gazdagabb interaktív réteget hordoz, amelyet érdemes közelebbről megvizsgálni. A komponens ezt egyetlen logikai értékként jelzi.
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;
Egyetlen csak olvasható rutin, amely jelentést ír
Rakjuk össze a darabokat, és a vizsgálat egyetlen eljárás lesz, amely betölti a dokumentumot, felsorolja a szkriptjeit és azok törzseit, listázza a Launch és URI céljait, jelzi az aláírás MDP szintjét, feljegyzi a csatolmányokat és az XFA-t, majd naplóba írja az eredményeket. Semmit nem renderel, így olcsó, és nem lehet rávenni ellenséges oldaltartalom megjelenítésére. A kimenet egy lapos, ember által olvasható rekord, amelyre a felülvizsgáló vagy egy későbbi szabály támaszkodhat.
A gyakorlatban jól működő forma az, ha minden megállapítást sorként gyűjtünk össze, a valóban kockázatosakat előtaggal látjuk el, hogy a felülvizsgálati sor elejére kerüljenek, és az egészet a fájl mellett tároljuk. A szkript, indítási művelet, csatolmány és XFA nélküli dokumentum, amely nem rendelkezik aláírással vagy koherens tanúsítással bír, csendben átmegy a szűrőn. Az a dokumentum, amely egyszerre több jelzőt is aktivál, az, amelyet egy embernek kell megnéznie, mielőtt bármelyik későbbi fázis megnyitná. A vizsgálat nem hozza meg a bizalmi döntést Ön helyett. Gondoskodik arról, hogy a döntés megalapozott legyen, ne vak.
Miután a fájl átment a vizsgálaton, és meg kell néznie, tegye azt korlátozások mellett, ne pedig alapértelmezett megjelenítőben. A biztonságos PDF előnézet készítéséről szóló útmutatónk bemutatja, hogyan lehet megakadályozni a linkek automatikus kezelését és az aktív tartalom működését az ellenőrzött megtekintés során. Ennek az összeírásnak a felülvizsgálati eszközökkel ellátott beléptetési folyamatba való beépítéséhez lásd a PDF beléptetési és felülvizsgálati munkaasztaláról szóló cikket. Mindkettő ugyanarra a csak olvasható, renderelésmentes alapra épül, és a Delphi és C++Builder platformokhoz készült PDFium Component részét képezi, a blogunkon máshol ismertetett renderelő, szöveg-, űrlap- és aláírás-kezelő API-kkal együtt.