💡 Повний робочий приклад доступний на GitHub:
merge-untrusted-html-uploads-dotnet

Безпечне об’єднання недовірених HTML‑документів — це робочий процес GroupDocs.Merger для .NET, який відхиляє зовнішні ресурси, на які посилається завантаження — віддалені URL‑адреси, UNC‑шляхи та локальні файли — якщо ви явно не дозволите їх.

Старий спосіб був проблематичним

Уявіть, що ваш портал виставлення рахунків об’єднує завантажений клієнтом рахунок‑фактуру з накладною того ж замовлення в один HTML‑документ для архівації. Рахунок — простий HTML: посилання на таблицю стилів, зображення логотипу, кілька абзаців — лише ви його не писали, його створив браузер клієнта або інструмент експорту. До GroupDocs.Merger 26.10 об’єднання завантажувало зовнішні ресурси, на які посилався HTML — віддалені таблиці стилів, віддалені зображення, абсолютні локальні файлові шляхи — і не було можливості зупинити це.

Це стає проблемою, коли завантаження не є повністю довіреним. URL‑адреса таблиці стилів може бути і трекінговим пікселем. Атрибут src зображення може вказувати на http://169.254.169.254/latest/meta-data/iam/security-credentials/ — адресу метаданих хмари — і ваш крок об’єднання здійснить запит від імені завантажувача. Абсолютний шлях file:/// також читався, тому посилання на завантаження іншого клієнта могло потягнути цей файл у злитий результат.

Справжня вартість сліпого об’єднання: завдання злиття, яке отримує будь‑які ресурси, запитані у завантаження, перетворює ваш сервер у проксі для SSRF, а посилання file:/// стає способом, за допомогою якого злиття одного клієнта читає файл іншого клієнта.

Є кращий спосіб

GroupDocs.Merger for .NET виправляє це за замовчуванням. Починаючи з версії 26.10, кожен конструктор LoadOptions встановлює SkipExternalResources = true, і Merger, створений без параметрів завантаження, використовує те саме значення за замовчуванням — зовнішні ресурси не завантажуються, якщо ви не вкажете інше.

Необхідні умови:

  • .NET (будь‑яка з чотирьох цільових платформ, які підтримує GroupDocs.Merger for .NET)
  • GroupDocs.Merger for .NET 26.10 або новіша (отримати тимчасову ліцензію)
  • Два HTML‑документи для об’єднання, принаймні один з недовіреного джерела

Встановити одним командою:

dotnet add package GroupDocs.Merger --version 26.10.0

Новий спосіб: об’єднання HTML без довіри

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

Крок 1 — Створити Load Options для формату

LoadOptions не має конструктора без параметрів і є sealed з 26.10; у прикладі формат вказується явно.

// LoadOptions(FileType.HTML) already denies everything external by default
var audit = new ResourceAudit();
var loadOptions = new LoadOptions(FileType.HTML) { ExternalResourceHandler = audit };

Крок 2 — Приєднати завантаження та проаналізувати рішення

// Both uploads follow the options of the Merger they are joined to
using (var merger = new Merger(firstUpload, loadOptions))
{
    merger.Join(secondUpload);
    merger.Save(outputPath);
}

foreach (string decision in audit.Decisions)
{
    Console.WriteLine(decision);
}

Ключові моменти:

  • SkipExternalResources: неявно true тут, тому кожен віддалений URL, UNC‑шлях і посилання на локальний файл відхиляються, включно з файлом у власній папці завантаження.
  • Один LoadOptions на приєднання: друге завантаження не отримує власних параметрів — воно успадковує те, з чим був створений Merger.

Крок 3 — Дозволити лише один довірений ресурс

// Supply the logo's bytes directly instead of requesting it over the network
var trustedAssets = new Dictionary<string, byte[]>
{
    [logoUrl] = File.ReadAllBytes(logoPath)
};
var audit = new ResourceAudit(trustedAssets);
var loadOptions = new LoadOptions(FileType.HTML) { ExternalResourceHandler = audit };

Метод OnLoading обробника встановлює args.Skipped = false і args.Data у ці байти, коли запитана URI збігається, тому документ отримує логотип без будь‑якого запиту GroupDocs.Merger.

Коли ми запустили злиття за замовчуванням для завантажень рахунку та накладної, усі сім зовнішніх посилань у обох файлах були заблоковані — віддалена таблиця стилів, віддалений логотип, URL‑адреса у стилі метаданих, зображення у сусідській папці, шлях file:///, UNC‑шлях і трекінговий URL з рядком запиту. joined.html зберігає текст обох завантажень; для кожного заблокованого зображення вставляється заповнювач. Після підключення обробника довіреного логотипу логотип був «наданий», а інші шість залишилися «заблокованими».

Порівняння: до і після

До (без параметра) Після (GroupDocs.Merger 26.10)
Зовнішні запити Кожне посилання завантажується автоматично Відхилено, якщо явно не дозволено
Абсолютні локальні файлові шляхи Завантажено у вихідний файл Відхилено, як будь‑яке інше посилання
Довірені виключення Не потрібні — все завантажується Один ресурс через обробник або дозволений префікс
Неправильний запис у whitelist whitelist не існував Відхилено з ArgumentException під час створення
Потрібен код Немає способу обмежити завантаження Кілька рядків у LoadOptions

Контраст полягає в тому, що можливо, а не лише в тому, що зручно: до 26.10 не існувало налаштування, яке зупиняло завантаження; після нього відхилення стало значенням за замовчуванням, а кожне виключення має бути явно зазначене.

Чи порушить це HTML, який вже залежав від завантаження віддалених зображень?

Якщо такий HTML все ще зливається з LoadOptions за замовчуванням, так — зображення не завантажаться, і у вихідному файлі замість них з’явиться заповнювач. Виправлення має бути явним, а не обхідним шляхом: перелічіть довірені джерела у WhitelistedResources або підключіть IExternalResourceHandler, який постачає конкретні активи, які ви все ще хочете відобразити. У будь‑якому випадку злиття продовжуватиметься; просто перестане запитувати все, що ви не назвали.

Реальний приклад: консолідація рахунку‑фактури та накладної

Конвеєр документів, який об’єднує HTML, наданий клієнтом, у архівну копію, саме той сценарій, який моделює зразковий проєкт цієї сторінки: завантаження рахунку‑фактури та накладної з того ж замовлення, об’єднані в один файл. Жодне з завантажень не було написане командою конвеєра, тому їх не можна вважати безпечними для буквального читання. У рахунку вказана сума до сплати та клієнт Northwind Traders, а також посилання на логотип, трекінгову таблицю стилів, URL‑адресу у стилі метаданих хмари, зображення у тій же папці та шлях file:/// — сім зовнішніх посилань між двома завантаженнями, жодне з яких конвеєр архівації не повинен запитувати від імені клієнта.

Перед тим, як завдання злиття вийде в продакшн, варто визначити для кожного типу документу, чи потрібне взагалі якесь виключення. У цьому прикладі сценарію інвойсингу потрібен був лише один: власний логотип компанії, який подається з файлу на диску, а не запитується за віддаленою URL‑адресою, зазначеною в HTML. Все інше — трекінг‑таблиця стилів, адреса у стилі метаданих, файл‑сиблінг, UNC‑ і file:///‑шляхи — не мали легітимної причини для запиту, тому саме значення за замовчуванням були правильним рішенням.

// Reject a whitelist entry that is not an absolute URI prefix
var loadOptions = new LoadOptions(FileType.HTML)
{
    WhitelistedResources = new List<string> { "cdn.example.com" }
};

Передача лише імені хоста замість абсолютного префікса типу https://cdn.example.com/brand/ викликає виключення з повідомленням Whitelisted resource 'cdn.example.com' must be an absolute URI without a query, fragment or encoded '/', '\' or '.'. (Parameter 'WhitelistedResources') — ще до відкриття будь‑якого завантаження. Виявлення неправильно налаштованого whitelist під час запуску варте більше, ніж виявлення його після того, як ресурс безшумно не завантажився у продакшн.

Що ще можна зробити з GroupDocs.Merger для .NET?

  • Раннє валідовання записів whitelist: неправильний запис у WhitelistedResources призводить до помилки під час створення Merger, тому перевірки під час старту розгортання виявляють її до обробки будь‑якого документа.
  • Постачання довірених активів з диска: IExternalResourceHandler може відповісти на запит байтами, які вже є у вас, без жодного мережевого доступу.
  • Застосування однієї політики до кожного приєднаного документа: LoadOptions задаються один раз на Merger, і кожен документ, приєднаний пізніше, дотримується тих самих правил.
  • Підтримка не лише HTML: документи обробки тексту, HTML/MHTML, презентації, електронні таблиці, SVG та EPUB також дотримуються тих самих параметрів.

Висновок

Об’єднання HTML, який ви не писали, більше не означає довіру до всього, що в ньому міститься. GroupDocs.Merger for .NET відхиляє зовнішні ресурси за замовчуванням, починаючи з версії 26.10, а два способи надати виключення — обробник довіреного активу та WhitelistedResources — дають саме такий рівень доступу, який потрібен вашому конвеєру, і нічого зайвого.

Готові спробувати самостійно?

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