Попередній перегляд ненадійного PDF усередині вашого власного застосунку — це рішення про виконання, і важлива тут не оболонка переглядача, а те, що панель відмовляється робити самостійно. Не записуйте файл на диск. Не дозволяйте його посиланням виходити в оболонку. Не давайте його вкладенням шлях. Більшість шкоди від ворожого документа походить не з експлойту рушія, а з того, що переглядач робить цілком звичайні речі з наданим атакувальником вводом: відкриває посилання file:// на мережевий ресурс UNC, що зливає облікові дані NTLM, залишає підготовлену копію в тимчасовому каталозі, копіює вбудовані навантаження туди, куди йому каже рядок імені файлу. PDFium Component — це переглядач PDF з вихідним кодом для Delphi, C++Builder і Lazarus, і він розміщує відповідні перемикачі там, де ви можете до них дотягнутися: прапорець часу завантаження, що вбиває скриптинг, події кліку по посиланню, які можна ветувати, доступ до вкладень, що проходить крізь ваш власний код, і біти дозволів, які можна прочитати. Порядок нижче слідує за документом від моменту, коли він приземляється, до моменту, коли користувач клацає щось у ньому
Модель загроз панелі попереднього перегляду
Будьте чесними щодо того, що вам дає «безпечний попередній перегляд». Рендерер парсить ненадійні байти, хоч би що ви робили, а власне зміцнення рушія — це підлога, на якій ви стоїте. Усе вище цієї підлоги — це політика застосунку: чи ініціалізуються скрипти, що робить клік по посиланню, чи можуть вбудовані файли дістатися диска, чи є буфер обміну й принтер дверима, чи стінами. Одна річ, яку варто списати з рахунків одразу, — перемикач рушія FPDF_SetSandBoxPolicy. Більшість обмежень рушія вкомпільовані, перемикач мало що змінює на практиці, і закладення на нього хоч якоїсь частини вашої історії ізоляції просто створює хибне відчуття, що щось зроблено. Коли ввід справді ворожий, скажімо, публічний портал завантаження, єдина реальна ізоляція — це рендер в окремому процесі з низькими привілеями й доставка бітмапів в UI. Прапорці в межах процесу — це політика. Вони не є стримуванням
Дві поверхні легко забути саме тому, що жоден клік їх ніколи не торкається. Перша — тимчасові файли. Якщо ваш конвеєр вкладає вхідні документи на диск перед попереднім переглядом, ці підготовлені копії переживають сесію, якщо щось перевірено їх не видалить, і файл, який «можна відновити з тимчасового каталогу», тихо звів нанівець кожен контроль, який застосовує сама панель. Натомість завантажуйте з пам'яті через TPdfStreamAdapter, щоб ворожі байти ніколи не отримували власного шляху. Друга — буфер обміну. Попередній перегляд, що дозволяє виділити-і-скопіювати, вже експортував документ, по одному екрану за раз, і жодне перехоплення посилань цього не зловить
Вбивайте JavaScript під час завантаження, а не в UI
JavaScript документа в PDFium Component ініціалізується лише разом із середовищем заповнення форм. Тому завантаження з FormFill := False вимикає скриптинг у корені, а не пригнічує його симптоми:
procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
Pdf.FileName := FilePath;
Pdf.FormFill := False; // немає середовища форм, а отже, немає рушія JavaScript
Pdf.Active := True;
FPermissions := Pdf.Permissions; // сире слово прапорців; усі біти встановлені = без обмежень
end;
Цей компроміс реальний і має бути прописаний у вашій специфікації. З вимкненим заповненням форм зникає й легітимна взаємодія AcroForm та скрипти валідації; поля рендеряться з їхнім останнім збереженим виглядом, але їх не можна редагувати. Для панелі попереднього перегляду це зазвичай правильне рішення, бо перегляд означає дивитися, а не заповнювати. Але якщо те саме вікно водночас слугує поверхнею заповнення форм для довірених внутрішніх документів, відповідь — два шляхи завантаження з явним рішенням про довіру між ними, а не один шлях із компромісним налаштуванням, яке заслабке для ворожого випадку й затісне для довіреного. Сторона заповнення форм цього розділення має власні пастки, розглянуті у навігації полями форм і регенерації вигляду
Посилання: типовий обробник виходить в оболонку
Залишені без нагляду, кліки по посиланнях ідуть прямо до операційної системи. Типові LinkOptions переглядача включають loAutoOpenURI, що є витоком file:// у мережевий ресурс UNC, який лише чекає на свій момент. Дві події формують вузьке місце: OnWebLinkClick для URL-адрес, виявлених у тексті сторінки, і OnAnnotationLinkClick для анотацій-посилань, що несуть дії URI чи запуску. Встановлюйте Handled := True в обох, безумовно, перш ніж щось вирішувати, а тоді дозволяйте назад лише те, що дозволяє політика. Як другий шар, приберіть loAutoOpenURI з LinkOptions для ворожого вводу і переконайтеся, що loAutoLaunch, вимкнений за замовчуванням, ніколи не прокрадається назад через скопійовану конфігурацію:
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
const Url: WString; var Handled: Boolean);
begin
Handled := True; // ніколи не провалюйтеся до типової поведінки оболонки
if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
and HostIsAllowed(Url) then
OpenInBrowser(Url)
else
FAudit.LogBlockedLink(FDocumentId, Url);
end;
Дві деталі вирішують, чи це справді тримається. По-перше, перевірка схеми має бути перевіркою префікса на сирому рядку до будь-якого парсингу, бо file://, шляхи UNC й екзотичні схеми — це саме ті значення, що ламають наївний парсер URL чи прослизають крізь той, що нормалізує занадто завзято. По-друге, логуйте кожне блокування з прикріпленою ідентичністю документа. Жменька заблокованих посилань file:// — це фоновий шум; сплеск їх серед багатьох вхідних документів за коротке вікно — це інцидент, про який ваша команда безпеки радше почує від вас, ніж звідкись іще
Вкладення: політика розширень і ім'я файлу, яке ви не обирали
PDF — це контейнер, і AttachmentCount разом із властивістю AttachmentName[] повідомляє, що він несе, перш ніж щось торкнеться диска. Тут важливі два окремі контролі, і лише один із них очевидний. Очевидний — це політика типів: дозволений список розширень, які взагалі можна експортувати. Тонкий — те, що ім'я вкладення є даними, контрольованими атакувальником, крапка. Вбудоване ім'я на кшталт ..\..\Startup\update.exe перетворює необережне збереження на обхід шляху, що скидає виконуваний файл у папку, яку Windows запускає при вході. Компонент вручає вам навантаження як байти через Attachment[] і дозволяє вашому коду обрати шлях, тож будуйте цей шлях із санітизованого базового імені й ніколи з сирого вбудованого рядка:
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
RawName, SafeName, Ext: string;
Data: TBytes;
begin
RawName := string(Pdf.AttachmentName[Index]);
SafeName := ExtractFileName(RawName); // прибирає будь-які компоненти шляху
Ext := LowerCase(ExtractFileExt(SafeName));
if not FAllowedExt.Contains(Ext) then // дозволений список, а не заборонений
raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);
Data := Pdf.Attachment[Index]; // вбудоване навантаження як сирі байти
TFile.WriteAllBytes(
IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;
Надавайте перевагу напрямку дозволеного списку. Заборонений список «небезпечних» розширень — це перегони, які ви програєте того дня, коли хтось перетворить на зброю розширення, про яке ви ніколи не чули; дозволений список .pdf, .png і .csv відмовляє за замовчуванням безпечно
Що насправді обіцяють дозволи шифрування
Стандартний обробник безпеки ISO 32000-1 кодує прапорці дозволів для друку, копіювання вмісту й модифікації, а властивості Permissions і UserPermissions виводять їх на поверхню як сирі бітові маски, щойно документ відкривається. Таблиця 22 ISO 32000-1 визначає ці біти, і незашифрований файл повідомляє про всі встановлені біти. Читайте їх і поважайте на своєму рівні команд, але майте ясність щодо того, чим вони є. Для документа, зашифрованого паролем власника й порожнім паролем користувача, вміст повністю розшифровується при відкритті, а прапорці — це запит до відповідних переглядачів, а не механізм примусу. Це має два наслідки, і вони тягнуть у протилежних напрямках. Ніколи не подавайте прапорці дозволів користувачам як властивість безпеки документів, які вони отримують, бо вони нею не є. Водночас поважайте біт вилучення для доступності (біт 10), навіть там, де загальне копіювання (біт 5) заборонене; доступ програм читання з екрана навмисно виокремлений у моделі дозволів, і прибирання його через те, що «копіювання вимкнене», ламає допоміжні технології без жодної вигоди для безпеки
Забезпечуйте заборонені дії на рівні команд, а не приховуючи кнопки панелі інструментів. Ctrl+C, контекстні меню й виділення перетягуванням — усе це обходить панель інструментів; одна перевірка дозволу всередині команди копіювання не обходить нічого
Для документів, що справді вимагають пароля користувача, присвоюйте Password перед Active := True і поводьтеся зі значенням як із тим секретом, яким воно є: отримуйте його зі свого сховища облікових даних щосесії, тримайте подалі від логів і звітів про збої, і ніколи не зберігайте поряд із документом. Панель попереднього перегляду, що кешує паролі «для зручності», тихо перетворилася на базу даних паролів без жодного із захистів такої бази
Друк заслуговує на власне рішення, а не успадкування того, на чому зупинилося правило копіювання. Фізичний роздрук за визначенням непідконтрольний, проте повне блокування друку зазвичай підштовхує користувачів до скріншотів, які гірші за всіма показниками. Звичний середній варіант — дозволити друк, але штампувати кожну сторінку ідентичністю користувача й часовою міткою, забезпечено всередині команди друку. Просто майте правильне очікування щодо цього: водяний знак — це стримування й атрибуція. Це не запобігання
Що прийом мав би вам уже повідомити
Панель попереднього перегляду ухвалює кращі рішення, коли файл з'являється з уже прикріпленим досьє: зашифрований чи ні, JavaScript присутній чи відсутній, перепис вкладень, тип форми. Цей прохід інспекції належить вище за течією від переглядача, і шаблон із побудови робочого столу прийому й перевірки PDF виробляє точно ті прапорці, які хоче споживати політика попереднього перегляду. Файли, які прийом позначив ризикованими, автоматично відкриваються через зміцнений шлях; звичайні документи зберігають свої зручності. Прив'яжіть обидва етапи до одного спільного об'єкта політики, а не до двох екранів конфігурації, які розійдуться вже до другого релізу, хоч би як обережно ви їх писали вперше
Де саме проходить межа між в-межах-процесу й поза-процесом, залежить від того, хто надсилає вам файли. Для звичайного бізнес-прийому люди, що надсилають документи, відомі й просто необережні, і попередній перегляд у межах процесу з вимкненим скриптингом та перехопленими посиланнями — це виправдана планка. Для анонімних публічних завантажень це не так, і жодна кількість налаштувань прапорців у межах процесу цього не змінить; рендерте їх в окремому робочому процесі з низькими привілеями й доставляйте бітмапи в UI, щоб вада рушія коштувала вам робочого процесу, а не хостового застосунку. Вирішуйте цей поділ навмисно й записуйте, до якого кошика потрапляє кожен шлях прийому, бо ціна помилкового припущення асиметрична
Ліцензування, поверхня API, пов'язана з безпекою, і демо зміцненого переглядача — на сторінці продукту: PDFium Component