💡 Повний робочий приклад доступний на GitHub:
skip-external-resources-when-signing-dotnet

Вступ

Документ Word може містити зображення, яке не зберігається у файлі. Документ містить адресу, і будь‑яка програма, яка його відкриває, завантажує цю адресу. На настільному комп’ютері це корисна функція — зображення оновлюється разом із джерелом. На сервері, що приймає завантаження, це означає, що відправник файлу вирішує, які URL‑адреси ваш інфраструктурний код запитуватиме.

Безпечне завантаження документів — це поведінка GroupDocs.Signature для .NET, яка відмовляється виконувати такі запити. Починаючи з версії 26.9, LoadOptions.SkipExternalResources за замовчуванням має значення true. У цій статті порівнюються три режими завантаження на одному і тому ж документі, показано, як дозволити один хост без дозволу всіх, і пояснено, чому підписання недовіреного файлу не потребує жодного мережевого доступу.

Чому це важливо більше, ніж здається

Атака має назву — server‑side request forgery (SSRF) — і три конкретні форми.

Внутрішня адреса, недоступна з інтернету, доступна вашому серверу, тому спеціально підготовлений документ може змусити ваш сервіс запитати http://169.254.169.254/ або адміністративний endpoint на localhost і, залежно від того, що ви робите з результатом, витікнути його. UNC‑шлях у документі може спонукати Windows‑хост аутентіфікуватися назовні, передаючи облікові дані серверу, контрольованому атакувальником. А посилання на хост, який просто не відповідає, блокує потік завантаження до тайм‑ауту, що є дешевим способом вичерпати пул робітників.

Я вважав це теоретичною проблемою, доки не спостерігав, як тестовий документ завантажує зображення через сервіс, який взагалі не повинен робити вихідні запити. Для цього не потрібен баг у бібліотеці документів. Перехід за посиланням — це те, що вимагає формат; питання лише в тому, чи ваш сервер повинен це виконувати.

Метод 1 — нове значення за замовчуванням

Без LoadOptions взагалі:

using var signature = new Signature(sourcePath);
return SavePagePreview(signature, previewPath);

Нічого не завантажується. Попередній перегляд показує порожнє місце, де повинно було бути пов’язане зображення, а PNG‑файл менший, ніж був би інакше. Ця різниця в розмірі — найзручніше підтвердження того, що жоден запит не залишив машину.

Які функції вважаються зовнішніми? Пов’язані зображення замість вбудованих, поля INCLUDEPICTURE, пов’язані зображення в презентаціях і електронних таблицях, а також зображення та таблиці стилів, на які посилається SVG. Вбудований вміст не змінюється — він вже знаходиться у файлі.

Метод 2 — білий список одного адреси

Багато документів посилаються на легітимні ресурси: CDN компанії, внутрішній сервер зображень, сховище шаблонів. Дозвольте лише його і нічого більше:

var loadOptions = new LoadOptions
{
    WhitelistedResources = new List<string> { trustedAddress }
};

using var signature = new Signature(sourcePath, loadOptions);

Правило збігу заслуговує уваги. Це нечутливий до регістру тест підрядка проти адреси ресурсу, що означає, що короткий фрагмент небезпечний: github збігається з github.attacker.example/payload.png так само легко, як і з потрібним хостом. Використовуйте схему, хост і шлях — у прикладі у білий список включено raw.githubusercontent.com/groupdocs-signature/.

Метод 3 — дозволити все

Поведінка до 26.9, все ще доступна:

var loadOptions = new LoadOptions { SkipExternalResources = false };

Розумно для документів, створених вашою власною програмою. Одна пастка, яку варто зазначити: застаріла властивість LoadExternalResources має протилежну полярність, тому SkipExternalResources = false замінює LoadExternalResources = true. Скопіювавши значення зі старої властивості, ви інвертуєте свою безпеку без жодного повідомлення про помилку.

Порівняння трьох режимів: коли який використовувати

Режим Кращий для Ключові переваги Обмеження
За замовчуванням (пропуск) завантаження користувачами, електронна пошта, файли партнерів неможливі вихідні запити пов’язані зображення відображаються як заповнювачі
Білий список документи, що посилаються на ваш власний хост зберігає роботу легітимних посилань збіг підрядка вимагає довгого, конкретного фрагмента
Дозволити все файли, створені вашими системами попередній перегляд виглядає точно так само, як раніше відновлює SSRF‑вразливість, яку усунуло значення за замовчуванням

Чи потрібні ресурси під час підписання?

Ні, і це практичний виграш. Підпис QR‑коду застосовується з налаштуваннями за замовчуванням, і жоден зовнішній ресурс не запитується під час завантаження, підписання чи збереження документа:

var options = new QrCodeSignOptions("Approved by GroupDocs.Signature")
{
    EncodeType = QrCodeTypes.QR,
    Left = 400,
    Top = 50,
    Width = 120,
    Height = 120
};

SignResult result = signature.Sign(outputPath, options);

Підписаний вихід зберігає своє посилання, тому користувач, який відкриє документ пізніше, все ще побачить зображення, завантажене на його машині. Пропуск — це політика на боці сервера, а не зміна документа, що робить її безпечною для застосування до файлів, які ви обробляєте від імені інших.

Що змінюється при оновленні

Для більшості сервісів нічого помітного спочатку, і це варто чітко зазначити, бо безпекове значення за замовчуванням, яке змінює поведінку скрізь, не пройде ревізію оновлення. Виняток — будь‑де, де попередній перегляд або мініатюра раніше показували пов’язане зображення, а тепер — заповнювач; це саме зміна, яка виконує свою роботу, і виправлення — додати запис у білий список, якщо хост ваш, або прийняти, якщо документ прийшов зовні.

Чесний спосіб перевірки — той, що використано у прикладі: відрендерити один і той же документ у всіх трьох режимах і порівняти розміри вихідних файлів. Якщо попередні перегляди за замовчуванням і з білим списком однакові за розміром, нічого не було завантажено в обох випадках — зазвичай це означає, що хост недоступний з даної машини, а не що білий список не спрацював, і приклад виводить підказку саме про це.

Помічник попереднього перегляду, бо це не очевидно

Два з трьох режимів вище викликають невеликий помічник, і його варто показати, бо PreviewOptions не приймає шлях:

var previewOptions = new PreviewOptions(
    pageData => File.Create(previewPath),
    (pageData, pageStream) => pageStream.Dispose())
{
    PreviewFormat = PreviewOptions.PreviewFormats.PNG
};

signature.GeneratePreview(previewOptions);

Він приймає два фабричні методи потоків — один для створення потоку на кожну сторінку, інший для його звільнення. У прикладі документ має одну сторінку, тому записується один файл; для багатосторінкового вводу включайте номер сторінки у назву файлу, інакше кожна сторінка перезаписуватиме попередню.

Найкращі практики

  • Ставте все, що ви не створили, під недовірливий статус, включаючи файли від партнерів з високим рівнем безпеки.
  • Робіть фрагменти білого списку достатньо довгими, щоб бути однозначними, і переглядайте їх, коли CDN змінює свої адреси.
  • Ніколи не встановлюйте SkipExternalResources зі значення, яке раніше присвоювалося LoadExternalResources.
  • Перевіряйте за розмірами вихідних файлів, а не лише за налаштуванням; конфігурація, що виглядає правильно, і відсутність запиту — це різні твердження.

Де це залишає SVG

Варто окремо зазначити, бо SVG — це одночасно поширений формат завантаження і поширений вектор SSRF. SVG може посилатися на зображення та таблиці стилів за URL, і ці посилання є зовнішніми ресурсами за тим же правилом — за замовчуванням пропускаються, їх можна додати до білого списку, їх можна відновити. Сервіс, який приймає SVG‑аватари або логотипи і рендерить їх на боці сервера, саме той, який захищає ця зміна.

Якщо ваш конвеєр приймає SVG від користувачів, значення за замовчуванням — це те, що вам потрібно, а білий список — для випадків, коли ваші власні шаблони підтягують спільну таблицю стилів з вашого хоста.

Висновок

За замовчуванням тепер ризикована поведінка вимагає явного рішення, а безпечна — нічого. Тримайте значення за замовчуванням для недовірених вхідних даних, вузько обмежуйте білий список там, де залучені ваші хости, і пам’ятайте, що саме підписання ніколи не потребувало мережі. Запуск прикладу проти одного зі своїх документів займе хвилину і покаже вам у трьох розмірах файлів, що саме ваш сервіс запитував.

Додаткові ресурси