Technický článek

Zabezpečený náhled PDF v aplikacích Delphi s PDFium

Náhled nedůvěryhodného PDF uvnitř vlastní aplikace je rozhodnutí o spouštění, a na čem záleží, není vzhled prohlížeče, ale to, co panel odmítá dělat sám od sebe. Nezapisujte soubor na disk. Nedovolte jeho odkazům spouštět shell. Nedávejte jeho přílohám cestu. Většina škod z nepřátelského dokumentu nepochází z exploitu jádra, ale z prohlížeče, který dělá naprosto obyčejné věci se vstupem dodaným útočníkem: otevře odkaz file:// na sdílenou složku UNC, která uniká přihlašovací údaje NTLM, ponechá připravenou kopii v dočasném adresáři, zkopíruje vložené payloady kamkoli, kam mu řekne řetězec s názvem souboru. PDFium Component je zdrojově dostupný prohlížeč PDF pro Delphi, C++Builder a Lazarus a umísťuje příslušné přepínače tam, kam na ně dosáhnete: příznak při načítání, který zabíjí skriptování, události kliknutí na odkaz, které můžete vetovat, přístup k přílohám, který prochází přes váš vlastní kód, a bity oprávnění, které můžete číst. Pořadí níže sleduje dokument od okamžiku, kdy dorazí, až po okamžik, kdy na něco v něm uživatel klikne

Model hrozeb panelu náhledu

Buďte upřímní ohledně toho, co vám „bezpečný náhled" skutečně přináší. Vykreslovač parsuje nedůvěryhodné bajty bez ohledu na to, co uděláte, a vlastní zpevnění jádra je podlaha, na které stojíte. Vše nad touto podlahou je politika aplikace: zda se inicializují skripty, co udělá kliknutí na odkaz, zda se vložené soubory mohou dostat na disk, zda jsou schránka a tiskárna dveřmi, nebo zdmi. Jedna věc, kterou byste měli brzy odepsat, je přepínač jádra FPDF_SetSandBoxPolicy. Většina omezení jádra je zakompilována přímo do něj, přepínač v praxi mění málo, a vyhradit mu jakoukoli část svého příběhu o izolaci vytváří jen falešný pocit, že jste něco udělali. Když je vstup skutečně nepřátelský, řekněme veřejný portál pro nahrávání, jedinou skutečnou izolací je vykreslování v samostatném procesu s nízkými oprávněními a odesílání bitmap do uživatelského rozhraní. Příznaky uvnitř procesu jsou politika. Nejsou to izolace

Diagram PDFium Component kontrastující přepínače politiky náhledu PDF in-process s renderem out-of-process v workerovi s nízkými právy pro nepřátelské dokumenty
Příznaky v procesu zvednou laťku známým odesílatelům, zatímco anonymní uploady ospravedlní samostatný worker s nízkými oprávněními, jenž na UI expeduje jen bitmapy

Dva povrchy se snadno zapomínají právě proto, že se jich žádné kliknutí nikdy nedotkne. Prvním jsou dočasné soubory. Pokud váš proces připravuje příchozí dokumenty na disku ještě před náhledem, tyto připravené kopie přežijí relaci, pokud je něco prokazatelně nesmaže, a soubor, který je „obnovitelný z dočasného adresáře", potichu porazil každou kontrolu, kterou panel sám vynucuje. Místo toho načítejte z paměti přes TPdfStreamAdapter, aby nepřátelské bajty nikdy nedostaly vlastní cestu na disku. Druhým je schránka. Náhled, který umožňuje označit a zkopírovat, už dokument exportoval, obrazovku po obrazovce, a žádné zachytávání odkazů to nezachytí

Zabijte JavaScript při načítání, ne v uživatelském rozhraní

JavaScript dokumentu se v PDFium Component inicializuje pouze společně s prostředím pro vyplňování formulářů. Načtení s FormFill := False proto vypíná skriptování u kořene, místo aby jen potlačovalo jeho příznaky:

procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
  Pdf.FileName := FilePath;
  Pdf.FormFill := False;     // žádné prostředí formulářů, tedy žádný JavaScriptový engine
  Pdf.Active := True;

  FPermissions := Pdf.Permissions;   // syrové slovo příznaků; všechny bity nastaveny = neomezeno
end;

Tento kompromis je skutečný a patří do vaší specifikace. S vypnutým vyplňováním formulářů zmizí i legitimní interakce s AcroForm a validační skripty; pole se vykreslí s naposledy uloženým vzhledem, ale nelze je upravovat. Pro panel náhledu to je obvykle správná volba, protože náhled znamená dívat se, ne vyplňovat. Ale pokud stejné okno slouží zároveň jako plocha pro vyplňování formulářů u důvěryhodných interních dokumentů, řešením jsou dvě cesty načítání s explicitním rozhodnutím o důvěře mezi nimi, ne jedna cesta s kompromisním nastavením, které je příliš uvolněné pro nepřátelský případ a příliš přísné pro důvěryhodný. Strana vyplňování formulářů tohoto rozdělení má svá vlastní úskalí, popsaná v navigaci ve formulářových polích a regeneraci vzhledu

Odkazy: výchozí obslužná rutina spouští shell

Ponechána sama sobě jdou kliknutí na odkazy přímo do operačního systému. Výchozí LinkOptions prohlížeče zahrnují loAutoOpenURI, což je únik typu file:// na sdílenou složku UNC, čekající na to, až se stane. Hrdlo lahve tvoří dvě události: OnWebLinkClick pro URL rozpoznané v textu stránky a OnAnnotationLinkClick pro odkazové anotace nesoucí akce URI nebo launch. Nastavte Handled := True v obou, bezpodmínečně, ještě než o čemkoli rozhodnete, a poté znovu povolte jen to, co politika dovoluje. Jako druhou vrstvu odeberte loAutoOpenURI z LinkOptions u nepřátelského vstupu a ujistěte se, že loAutoLaunch, ve výchozím stavu vypnuté, se nikdy nevplíží zpět přes zkopírovanou konfiguraci:

Vývojový diagram zachycení kliknutí na odkaz PDF v náhledovém panelu Delphi s kontrolou prefixu schématu přes raw-string a auditním logováním blokovaných odkazů
Nastavení Handled v obou událostech odkazů drží každé kliknutí pod politikou aplikace a kontrola prefixu na surovém řetězci drží venku schémata file:// a UNC
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
  const Url: WString; var Handled: Boolean);
begin
  Handled := True;   // nikdy nepropadnout do výchozího chování shellu

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

O tom, zda to skutečně platí, rozhodují dva detaily. Zaprvé, kontrola schématu musí být kontrolou prefixu na syrovém řetězci ještě před jakýmkoli parsováním, protože file://, cesty UNC a exotická schémata jsou přesně ty hodnoty, které shodí naivní parser URL nebo proklouznou tím, který normalizuje příliš horlivě. Zadruhé, zaznamenávejte každé zablokování s připojenou identitou dokumentu. Hrstka zablokovaných odkazů file:// je šum na pozadí; jejich příval napříč mnoha příchozími dokumenty v krátkém okně je incident, o kterém váš bezpečnostní tým raději uslyší od vás než odjinud

Přílohy: politika přípon a název souboru, který jste nevybrali

PDF je kontejner a AttachmentCount spolu s vlastností AttachmentName[] vám řekne, co nese, ještě než se čehokoli dotkne disk. Zde záleží na dvou samostatných kontrolách a pouze jedna z nich je zřejmá. Zřejmou je politika typů: povolovací seznam přípon, které vůbec smí být exportovány. Jemnou je to, že název přílohy jsou data kontrolovaná útočníkem, tečka. Vložený název jako ..\..\Startup\update.exe promění neopatrné uložení v procházení cestou, které umístí spustitelný soubor do složky, kterou Windows spouští při přihlášení. Komponenta vám předá payload jako bajty přes Attachment[] a nechá váš kód zvolit cestu, takže tuto cestu sestavte ze sanitizovaného základního názvu a nikdy ze syrového vloženého řetězce:

Diagram pipeline PDFium Component sanitizující název přílohy PDF ovládaný útočníkem přes ExtractFileName a allowlistu přípon před zápisem bajtů
Název vložené přílohy je vstup útočníka, takže exportní cesta se staví znovu z očisteného basename a branuje se fail-closed allowlist přípon
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
  RawName, SafeName, Ext: string;
  Data: TBytes;
begin
  RawName := string(Pdf.AttachmentName[Index]);
  SafeName := ExtractFileName(RawName);    // odstraňuje jakékoli komponenty cesty
  Ext := LowerCase(ExtractFileExt(SafeName));

  if not FAllowedExt.Contains(Ext) then    // povolovací seznam, ne blokovací seznam
    raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);

  Data := Pdf.Attachment[Index];           // vložený payload jako syrové bajty
  TFile.WriteAllBytes(
    IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;

Dávejte přednost směru povolovacího seznamu. Blokovací seznam „nebezpečných" přípon je závod, který prohrajete v den, kdy někdo zneužije příponu, o které jste nikdy neslyšeli; povolovací seznam s .pdf, .png a .csv selhává uzavřením

Co šifrovací oprávnění ve skutečnosti slibují

Standardní bezpečnostní obslužná rutina ISO 32000-1 kóduje příznaky oprávnění pro tisk, kopírování obsahu a úpravy, a vlastnosti Permissions a UserPermissions je po otevření dokumentu zpřístupňují jako syrové bitové masky. Tabulka 22 normy ISO 32000-1 definuje jednotlivé bity a nezašifrovaný soubor hlásí každý bit nastavený. Čtěte je a respektujte je ve své vrstvě příkazů, ale buďte si jasní v tom, co jsou. U dokumentu zašifrovaného heslem vlastníka a prázdným uživatelským heslem se obsah při otevření plně dešifruje a příznaky jsou žádostí vůči vyhovujícím prohlížečům, nikoli mechanismem vynucení. To má dva důsledky a táhnou opačnými směry. Nikdy nepředkládejte uživatelům příznaky oprávnění jako bezpečnostní vlastnost dokumentů, které dostávají, protože to žádnou nejsou. Zároveň respektujte bit extrakce pro přístupnost (bit 10), i tam, kde je obecné kopírování (bit 5) zakázáno; přístup pro čtečky obrazovky je v modelu oprávnění záměrně vyčleněn zvlášť, a jeho odstranění jen proto, že „kopírování je vypnuto", rozbíjí asistivní technologie bez jakéhokoli bezpečnostního přínosu

Vynucujte zakázané akce na úrovni příkazů, ne skrýváním tlačítek na panelu nástrojů. Ctrl+C, kontextové nabídky a výběr tažením myší všechny obejdou panel nástrojů; jedna kontrola oprávnění uvnitř příkazu kopírování neobejde nic

U dokumentů, které skutečně vyžadují uživatelské heslo, přiřaďte Password ještě před Active := True a zacházejte s hodnotou jako s tajemstvím, kterým je: načtěte ji z úložiště přihlašovacích údajů pro každou relaci, držte ji mimo protokoly a hlášení o pádech a nikdy ji neukládejte natrvalo vedle dokumentu. Panel náhledu, který ukládá hesla do mezipaměti „pro pohodlí", se potichu stal databází hesel bez jakékoli z ochran, které by taková databáze měla mít

Tisk si zaslouží vlastní rozhodnutí, místo aby zdědil cokoli, na čem skončilo pravidlo pro kopírování. Fyzický výtisk je z definice neauditovaný, přesto úplné blokování tisku má tendenci tlačit uživatele směrem ke snímkům obrazovky, které jsou horší na každé ose. Běžným kompromisem je tisk povolit, ale orazítkovat každou stránku identitou uživatele a časovým razítkem, vynuceným uvnitř příkazu tisku. Jen k tomu mějte správné očekávání: vodoznak je odstrašení a přiřazení odpovědnosti. Není to prevence

Co vám příjem měl už dávno říct

Panel náhledu dělá lepší rozhodnutí, když se soubor objeví s dokumentací už připojenou: zašifrovaný, nebo ne, JavaScript přítomný, nebo ne, soupis příloh, typ formuláře. Tento inspekční průchod patří proti proudu od prohlížeče, a vzor v budování pracovní stanice pro kontrolu příjmu PDF vytváří přesně ty příznaky, které politika náhledu chce konzumovat. Soubory, které příjem označil za rizikové, se automaticky otevírají zpevněnou cestou; rutinní dokumenty si ponechávají svá pohodlí. Svažte obě fáze k jednomu sdílenému objektu politiky, místo dvou konfiguračních obrazovek, které se rozejdou nejpozději u druhého vydání, ať už je napíšete napoprvé sebepečlivěji

Kam padne hranice mezi in-process a out-of-process, závisí na tom, kdo vám posílá soubory. Při běžném firemním příjmu jsou lidé posílající dokumenty známí a jen nedbalí, a náhled in-process s vypnutým skriptováním a zachytávanými odkazy je obhajitelná laťka. U anonymních veřejných nahrávání tomu tak není, a žádné množství nastavování příznaků in-process ji takovou neudělá; vykreslujte je v samostatném pracovním procesu s nízkými oprávněními a posílejte bitmapy do uživatelského rozhraní, aby vás chyba jádra stála pracovní proces, ne hostitelskou aplikaci. Rozhodněte toto rozdělení záměrně a zapište si, do kterého koše spadá každá cesta příjmu, protože cena za špatný odhad je asymetrická

Licencování, bezpečnostně relevantní povrch API a demo zpevněného prohlížeče najdete na produktové stránce: PDFium Component