Scriban CVE-2026-73060–74795: 15 sposobów na obejście sandboxa szablonów
Fala CVE w silniku Scriban pokazuje, dlaczego LoopLimit i filtry obiektów nie wystarczają. Analizujemy DoS, zapis właściwości CLR i bezpieczne renderowanie.
- AUTOR
- Karol Rapacz / CEO Breachroad · OSCP · PNPT
- PUBLIKACJA
- 16 sierpnia 2026
- CZAS CZYTANIA
- 16 min czytania
- TEMAT
- Pentest i AppSec
16 sierpnia 2026 roku w bazach CVE opublikowano piętnaście rekordów dotyczących Scriban, silnika szablonów dla platformy .NET. Numery obejmują CVE-2026-73060, CVE-2026-73061, CVE-2026-73062 oraz CVE-2026-74783–74792 i CVE-2026-74794–74795. Nie jest to jedna luka powielona pod wieloma identyfikatorami. Zestaw pokazuje kilka różnych klas błędów: nieograniczone alokacje, obejścia limitu pętli, rekurencję kończącą proces, przenikanie danych między ponownie używanymi kontekstami oraz zapis do właściwości obiektów CLR, które host uznawał za chronione.
Najważniejszy wspólny warunek brzmi: ryzyko pojawia się wtedy, gdy aplikacja pozwala niezaufanemu użytkownikowi kontrolować treść szablonu albo istotną część danych, które uruchamiają kosztowne operacje. Samo użycie Scriban do renderowania statycznych, zaufanych plików nie oznacza automatycznie zdalnej podatności. Jednak systemy raportowe, generatory dokumentów, panele low-code, personalizacja wiadomości i funkcje AI tworzące szablony na żądanie często przesuwają tę granicę dalej, niż zakłada zespół.
Advisory dotyczące CVE-2026-73060 wskazuje wersje 3.0.0–7.2.5 i poprawkę w 7.2.6. To dobry bezpieczny poziom bazowy dla współczesnej linii 7.x, ponieważ zawiera również wcześniejsze poprawki z 7.2.0, 7.2.1 i 7.2.2. Organizacja powinna jednak potwierdzić wersję faktycznie umieszczoną w artefakcie, a nie tylko deklarację w pliku projektu.
Dlaczego limit pętli nie był granicą bezpieczeństwa
Scriban udostępnia LoopLimit, który ma ograniczać liczbę iteracji wykonywanych przez szablon. Problem polegał na tym, że różne ścieżki interpretera rozliczały pracę w różny sposób. Klasyczna pętla mogła zwiększać licznik, podczas gdy funkcja wbudowana, mnożenie tablicy, materializacja leniwej sekwencji albo formatowanie tekstu wykonywały kosztowną pracę poza tym samym budżetem.
CVE-2026-73060 dotyczy mnożenia leniwej sekwencji. Poprawka dla zwykłej tablicy sprawdzała przewidywany rozmiar przed pracą, lecz pokrewna ścieżka ScriptRange.Multiply nadal mogła wykonywać ogromną liczbę nieobciążonych iteracji. To ważna lekcja: kontrola zużycia zasobów musi obejmować jednostkę kosztu, a nie wyłącznie konkretną składnię.
CVE-2026-73062 oraz część rekordów CVE-2026-74784–74789 opisują podobne asymetrie. Mnożnik tablicy, bardzo odległy indeks, przesunięcie liczby całkowitej, wielokrotne dopełnianie ciągu lub działanie funkcji wbudowanej mogły wymusić dużą alokację albo czas procesora przed sprawdzeniem limitu wyniku. Limit zastosowany po utworzeniu obiektu jest spóźniony: pamięć została już przydzielona, a proces może otrzymać OutOfMemoryException.
Wniosek architektoniczny jest szerszy niż Scriban. Budżet wykonania sandboxa powinien obejmować kroki interpretera, liczbę elementów, rozmiar pojedynczej alokacji, łączny rozmiar wyjścia, głębokość stosu i czas ścienny. Każdy licznik osobno pozostawia możliwość przeniesienia kosztu do innego wymiaru.
Rekurencja, której .NET nie może bezpiecznie przechwycić
Kilka rekordów dotyczy głębokiego zagnieżdżenia lub cyklicznych obiektów. Parser rekurencyjny może zejść przez tysiące nawiasów, inicjalizatorów i operatorów. Renderer JSON albo ogólne renderowanie obiektu może z kolei podążać za referencją prowadzącą z powrotem do tego samego obiektu. Bez skutecznego limitu głębokości kończy się to przepełnieniem stosu.
W .NET StackOverflowException jest szczególnie groźny, ponieważ w typowym procesie nie da się go potraktować jak zwykłego błędu żądania. Proces zostaje zakończony, więc pojedynczy szablon może wpływać na wszystkie równoległe żądania obsługiwane przez ten sam worker. Automatyczny restart ogranicza czas niedostępności, ale nie usuwa podatności; atakujący może ponawiać bodziec po każdym uruchomieniu.
CVE-2026-74783 opisuje nieskuteczny strażnik ExpressionDepthLimit, a CVE-2026-74787, 74792, 74794 i 74795 obejmują różne ścieżki niekontrolowanej rekurencji. Nie wystarczy więc dodać sprawdzenia w jednym wejściu parsera. Limit musi być propagowany przez wszystkie funkcje rekurencyjne oraz obowiązywać również w serializacji i renderowaniu grafu obiektów.
Gdy szablon zapisuje do żywego obiektu CLR
Najpoważniejszy problem integralności opisuje CVE-2026-73061. Host może umieścić obiekt CLR w TemplateContext, aby szablon odczytał nazwę użytkownika, cenę zamówienia albo ustawienia raportu. Podatna ścieżka TypedObjectAccessor pozwalała jednak nie tylko czytać, lecz także zmieniać właściwości. Co gorsza, nie respektowała w pełni widoczności setterów, więc szablon mógł dotknąć właściwości z private set, internal set lub init.
Upstreamowe advisory wyjaśnia dwa problemy: masowe przypisywanie do publicznych setterów oraz obejście intencji programisty dla setterów ograniczonych. Zmiana pozostaje w żywym obiekcie po zakończeniu renderowania. Jeśli ten obiekt później steruje autoryzacją, ceną, ścieżką pliku lub operacją biznesową, wpływ może wyjść poza sam wynik tekstowy.
To przykład niebezpiecznej różnicy między „szablon może zobaczyć obiekt” a „szablon otrzymuje referencję do obiektu”. Bezpieczniejszy wzorzec to kopiowanie potrzebnych pól do prostego, niezmiennego modelu widoku. Silnik nie powinien widzieć encji ORM, obiektu sesji, konfiguracji ani modelu domenowego zawierającego metody i zapisowalne właściwości.
Cache i przenikanie polityki między żądaniami
CVE-2026-74790 i CVE-2026-74791 pokazują zagrożenie związane z ponownym użyciem kontekstu. Cache akcesorów był kluczowany typem, lecz nie uwzględniał zmiany MemberFilter. Po użyciu luźniejszej polityki ten sam typ mógł zachować dostęp do pól, które późniejszy, bardziej restrykcyjny kontekst chciał ukryć. Osobny problem dotyczył szablonów pozostających w cache po TemplateContext.Reset().
W aplikacji wielodostępnej oznacza to możliwość naruszenia granicy tenantów lub żądań. Sam reset obiektu nie gwarantuje czystego stanu, jeżeli wewnętrzne cache przeżywają reset. Pula kontekstów poprawia wydajność, ale wymaga formalnego kontraktu: które elementy są usuwane, które są współdzielone i jakie dane mogą wpływać na klucz cache.
Breachroad zaleca traktowanie kontekstu wykonania niezaufanego szablonu jako jednorazowego. Jeżeli pomiary naprawdę uzasadniają pooling, test bezpieczeństwa powinien renderować naprzemiennie szablon uprzywilejowany i ograniczony oraz sprawdzać brak dostępu do poprzednich obiektów, loaderów i członków.
Co powinien zrobić zespół
Najpierw ustal, czy Scriban występuje w produkcyjnym SBOM i czy którakolwiek ścieżka przyjmuje szablon od użytkownika, integracji, importu lub modelu AI. Nie ograniczaj wyszukiwania do formularza „edytuj szablon”. Szablony mogą przychodzić w pliku konfiguracyjnym, definicji raportu, treści kampanii, pakiecie importu albo odpowiedzi narzędzia generatywnego.
Zaktualizuj linię 7.x co najmniej do 7.2.6 i zweryfikuj transitive dependencies. Jeżeli produkt utrzymuje starszą gałąź, porównaj każdy rekord z oficjalnym zakresem oraz wybierz wspieraną wersję zawierającą wszystkie poprawki. Po przebudowaniu odczytaj wersję biblioteki z finalnego obrazu i usuń stare artefakty z cache wdrożeniowego.
Następnie oddziel renderowanie od głównego procesu aplikacji. Dedykowany worker powinien mieć limit pamięci, CPU, czas wykonania, rozmiar wejścia i wyjścia oraz brak niepotrzebnego dostępu do sieci i systemu plików. Awaria workera nie może kończyć API ani kolejki obsługującej inne zadania. Kolejka powinna stosować ograniczenia per tenant, aby jedna organizacja nie zajęła całej puli.
Przekazuj do kontekstu wyłącznie jawny model widoku z typami prostymi. Nie udostępniaj sekretów „bo filtr je ukryje”. Filtr jest dodatkowym zabezpieczeniem, nie mechanizmem izolacji tajemnic. Dla pól wrażliwych najlepszą kontrolą jest ich fizyczna nieobecność w grafie osiągalnym z szablonu.
Detekcja i testy regresji
Monitoruj czas renderowania, przyrost pamięci workera, liczbę restartów, odrzucone limity oraz rozmiar wyniku. Alert powinien łączyć te dane z identyfikatorem szablonu i tenantem, ale nie zapisywać całej treści, jeśli może zawierać dane osobowe lub sekrety. Nagły wzrost GC i zakończenia procesów bez obsłużonego wyjątku są szczególnie istotne.
Testy negatywne powinny obejmować każdą rodzinę kosztu: bardzo głęboką strukturę, cykliczny model, operacje tworzące duży wynik, leniwe sekwencje i funkcje wbudowane. Test integralności musi potwierdzić, że po renderowaniu obiekt hosta nie zmienił właściwości. Test izolacji powinien użyć dwóch kontekstów o różnych filtrach i wykazać brak przenikania cache.
Nie uruchamiaj takich testów na produkcji. W odizolowanym środowisku użyj twardych limitów procesu i asercji, że renderowanie kończy się kontrolowanym błędem przed przekroczeniem budżetu. Celem jest sprawdzenie poprawki, nie zmierzenie momentu awarii maszyny.
Fakty i wnioski Breachroad
Faktem jest, że 16 sierpnia opublikowano piętnaście rekordów CVE obejmujących Scriban, a opisane skutki obejmują DoS, naruszenie integralności obiektów oraz obejścia izolacji kontekstu. Faktem jest również, że poprawki pojawiały się w kilku wydaniach; najnowszy z opisanych problemów z leniwą sekwencją jest naprawiony w 7.2.6. Rekordy i advisory nie stanowią dowodu powszechnego wykorzystywania w Internecie.
Wnioskiem Breachroad jest to, że biblioteka szablonów nie staje się sandboxem tylko dlatego, że ma limit pętli i filtr członków. Bezpieczeństwo wymaga warstw: aktualnej biblioteki, minimalnego modelu danych, izolowanego procesu, wielowymiarowych budżetów i testów między żądaniami.
Jeśli Twój zespół buduje generatory dokumentów, panele low-code lub funkcje AI przetwarzające szablony, zobacz szkolenia cyberbezpieczeństwa dla organizacji. Weryfikację realnych granic wykonania można uzupełnić o testy penetracyjne aplikacji webowych i API.


