💡 Pełny działający przykład dostępny na GitHubie: merge-untrusted-html-uploads-dotnet

Bezpieczne scalanie niezaufanych dokumentów HTML jest przepływem pracy GroupDocs.Merger dla .NET, który odrzuca zewnętrzne zasoby, do których odwołuje się przesyłka – zdalne adresy URL, ścieżki UNC i pliki lokalne – chyba że wyraźnie je zezwolisz.

Stara metoda była bolesna

Wyobraź sobie, że Twój portal fakturujący scala przesłany przez klienta plik faktury z notą dostawy z tego samego zamówienia w jeden dokument HTML do archiwizacji. Faktura jest zwykłym HTML‑em – odnośnik do arkusza stylów, obraz logo, kilka akapitów – z tym że nie Ty go napisałeś, a przeglądarka klienta lub narzędzie eksportujące. Przed wersją GroupDocs.Merger 26.10 scalanie ładowało zewnętrzne zasoby, do których odwoływał się HTML – zdalne arkusze stylów, zdalne obrazy, bezwzględne ścieżki do plików lokalnych – i nie było możliwości tego zatrzymać.

To jest problem w momencie, gdy przesyłka nie jest w pełni zaufana. URL arkusza stylów może być również pikselem śledzącym. Atrybut src obrazu może wskazywać na http://169.254.169.254/latest/meta-data/iam/security-credentials/ – adres metadanych chmury – i Twój krok scalania wykonałby żądanie w imieniu nadawcy. Bezpośrednia ścieżka file:/// również była odczytywana, więc odwołanie do przesyłki innego klienta mogło wciągnąć ten plik do scalonego wyniku.

Rzeczywisty koszt ślepego scalania: zadanie scalania, które pobiera wszystko, o co prosi przesyłka, zamienia Twój serwer w proxy dla SSRF, a odwołanie file:/// w sposób, w którym scalanie jednego klienta może odczytać plik innego klienta.

Jest lepszy sposób

GroupDocs.Merger dla .NET naprawia to domyślnie. Od wersji 26.10 każdy konstruktor LoadOptions ustawia SkipExternalResources = true, a Merger utworzony bez opcji ładowania używa tego samego domyślnego ustawienia – żadne zewnętrzne zasoby nie są ładowane, chyba że wyraźnie zezwolisz.

Wymagania wstępne:

  • .NET (dowolny z czterech docelowych frameworków obsługiwanych przez GroupDocs.Merger dla .NET)
  • GroupDocs.Merger dla .NET 26.10 lub nowszy (uzyskaj tymczasową licencję)
  • Dwa dokumenty HTML do połączenia, przynajmniej jeden z źródła, któremu nie ufasz w pełni

Instalacja jednym poleceniem:

dotnet add package GroupDocs.Merger --version 26.10.0

Nowy sposób: scalanie HTML bez zaufania

To, jak wygląda łączenie dwóch przesłanych plików HTML przy użyciu bezpiecznych ustawień domyślnych, obsługi rejestrującej każdą decyzję oraz – w drugim przebiegu – obsługi dostarczającej jeden zaufany zasób.

Krok 1 – Utwórz Load Options dla formatu

LoadOptions nie ma konstruktora bez parametrów i jest sealed od wersji 26.10; w przykładzie format jest określony explicite.

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

Krok 2 – Połącz przesyłki i sprawdź decyzje

// 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);
}

Kluczowe punkty:

  • SkipExternalResources: domyślnie true w tym przypadku, więc każdy zdalny URL, ścieżka UNC i odwołanie do pliku lokalnego jest odrzucane, w tym plik w własnym folderze przesyłki.
  • Jedno LoadOptions na połączenie: druga przesyłka nie otrzymuje własnych opcji – dziedziczy to, z czym został utworzony Merger, do którego została dołączona.

Krok 3 – Zezwól dokładnie na jeden zaufany zasób

// 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 };

Metoda OnLoading obsługi ustawia args.Skipped = false i args.Data na te bajty, gdy żądany URI się zgadza, więc dokument otrzymuje logo bez tego, aby GroupDocs.Merger wykonywał jakiekolwiek żądanie.

Kiedy uruchomiliśmy domyślne scalanie na fakturze i notatce dostawy, wszystkie siedem zewnętrznych odwołań w obu plikach zostało zablokowanych – zdalny arkusz stylów, zdalne logo, adres w stylu metadanych, obraz z folderu sąsiadującego, ścieżka file:///, ścieżka UNC i URL śledzący z ciągiem zapytania. joined.html zachowuje tekst z obu przesyłek; dla każdego zablokowanego obrazu wstawiany jest placeholder. Po podłączeniu obsługi zaufanego logo, logo zostało dostarczone, a pozostałe sześć pozostało zablokowane.

Porównanie: przed i po

Przed (bez opcji) Po (GroupDocs.Merger 26.10)
Zewnętrzne żądania Każde odwołanie ładowane automatycznie Odrzucane, chyba że wyraźnie dozwolone
Bezwzględne ścieżki do plików lokalnych Ładowane do wyniku Odrzucane jak każde inne odwołanie
Zaufane wyjątki Niepotrzebne – wszystko ładowane Jeden zasób przez obsługę lub prefiks z białej listy
Nieprawidłowy wpis na białej liście Brak białej listy Odrzucony z ArgumentException przy konstrukcji
Wymagany kod Brak możliwości ograniczenia ładowania Kilka linii w LoadOptions

Kontrast polega na tym, co jest możliwe, a nie tylko na tym, co jest wygodne: przed wersją 26.10 nie było ustawienia, które zatrzymywało ładowanie; po niej odrzucanie jest domyślne, a każdy wyjątek musi być jawny.

Czy to zepsuje HTML, który już zależał od ładowania zdalnych obrazów?

Jeśli ten HTML jest nadal scalany przy użyciu domyślnych LoadOptions, tak – obrazy nie zostaną załadowane, a w ich miejscu w wyniku pojawi się placeholder. Naprawa jest jawna, a nie obejściem: wymień zaufane źródła w WhitelistedResources lub podłącz IExternalResourceHandler, który dostarczy konkretne zasoby, które nadal chcesz renderować. W każdym przypadku scalanie będzie kontynuowane; po prostu przestanie pobierać cokolwiek, czego nie wymieniłeś.

Przykład z życia: konsolidacja faktury i noty dostawy

Pipeline dokumentów, który scala HTML przesłany przez klienta do kopii archiwalnej, dokładnie odzwierciedla scenariusz modelowany w projekcie przykładowym tej strony: przesyłka faktury i noty dostawy z tego samego zamówienia, połączone w jeden plik. Żadna z przesyłek nie została napisana przez zespół pipeline’u, więc nie można ich uznać za bezpieczne do dosłownego odczytu. Faktura podaje kwotę do zapłaty i klienta, Northwind Traders, oraz zawiera odwołanie do logo, arkusza stylów śledzącego, adresu w stylu metadanych chmury, odwołanie do obrazu w tym samym folderze i ścieżkę file:/// – siedem zewnętrznych odwołań między dwoma przesyłkami, z których żadne nie powinno być pobrane przez pipeline archiwizujący w imieniu klienta.

Zanim zadanie scalania zostanie uruchomione, warto zdecydować, typ dokumentu po typie dokumentu, czy w ogóle potrzebny jest jakiś wyjątek. Scenariusz fakturowania w tym przykładzie wymagał dokładnie jednego: własnego logo firmy, serwowanego z pliku już znajdującego się na dysku, a nie pobieranego z zdalnego URL, który podaje HTML. Wszystko inne – arkusz stylów śledzący, adres metadanych, plik sąsiadujący, ścieżki UNC i file:/// – nie miało uzasadnionego powodu, aby być pobrane, więc same domyślne ustawienia były właściwą odpowiedzią.

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

Przekazanie samej nazwy hosta zamiast bezwzględnego prefiksu URI, takiego jak https://cdn.example.com/brand/, powoduje wyjątek z komunikatem Whitelisted resource 'cdn.example.com' must be an absolute URI without a query, fragment or encoded '/', '\' or '.'. (Parameter 'WhitelistedResources') – zanim którakolwiek z przesyłek zostanie otwarta. Wykrycie niepoprawnie skonfigurowanej białej listy przy starcie jest warte więcej niż wykrycie jej po tym, jak zasób cicho nie załaduje się w produkcji.

Co jeszcze możesz zrobić z GroupDocs.Merger dla .NET?

  • Waliduj wpisy białej listy wcześnie: nieprawidłowy wpis WhitelistedResources powoduje błąd przy konstrukcji Merger, więc kontrole przy uruchamianiu wdrożenia wykrywają go przed przetworzeniem jakiegokolwiek dokumentu.
  • Dostarczaj zaufane zasoby z dysku: IExternalResourceHandler może odpowiedzieć na żądanie bajtami, które już posiadasz, bez żadnego dostępu do sieci.
  • Zastosuj jedną politykę do każdego połączonego dokumentu: LoadOptions jest ustawiane raz na Merger, a każdy dokument dołączany później podąża za tymi samymi zasadami.
  • Obsługuj więcej niż HTML: dokumenty edytorskie, HTML/MHTML, prezentacje, arkusze kalkulacyjne, SVG i EPUB respektują te same opcje.

Podsumowanie

Scalanie HTML, którego nie napisałeś, nie oznacza już zaufania wszystkiemu, co w nim jest. GroupDocs.Merger dla .NET od wersji 26.10 domyślnie odrzuca zewnętrzne zasoby, a dwa sposoby przyznania wyjątku – obsługa zaufanego zasobu i WhitelistedResources – dają dokładnie tyle dostępu, ile potrzebuje dany pipeline, nic więcej.

Gotowy, aby wypróbować to samodzielnie?

Dodatkowe zasoby