Odpowiedź modelu to niezaufane dane (OWASP LLM05) - XSS, SQL injection i RCE przez asystenta AI
W skrócie: LLM05 Improper Output Handling to kategoria OWASP dla sytuacji, w której aplikacja przekazuje tekst wygenerowany przez model dalej - do przeglądarki, zapytania SQL, powłoki, ścieżki pliku - bez walidacji i kodowania.
To nie jest nowa klasa błędów. To stare dobre XSS, SQL injection i wykonanie kodu, tylko źródłem niebezpiecznego ciągu jest model, a nie formularz. Naprawia się je tak samo: kodowaniem zależnym od kontekstu, zapytaniami parametryzowanymi i brakiem eval().
Poprzednie dwa wpisy z tej serii dotyczyły tego, co wchodzi do modelu (zatruwanie danych) i tego, co model może zrobić narzędziami (nadmierna sprawczość agentów). Ten jest o trzeciej drodze: o tekście, który model po prostu zwraca, i o tym, co aplikacja z nim potem robi.
Skąd bierze się ten błąd
Programiści od lat wiedzą, że danym od użytkownika się nie ufa. Formularz jest walidowany, parametr z adresu kodowany, zapytanie do bazy parametryzowane. Odpowiedź modelu nie przychodzi jednak z formularza, tylko z własnego backendu, z usługi, za którą firma płaci. Łatwo ją potraktować jak dane wewnętrzne.
Tymczasem treść odpowiedzi zależy od wszystkiego, co model przeczytał: od pytania użytkownika, od dokumentów z bazy wiedzy, od maila, który streszcza, od strony, którą odwiedził. Każdy, kto ma wpływ na którekolwiek z tych źródeł, ma wpływ na odpowiedź. W praktyce model jest pośrednikiem, który przenosi niezaufane dane z jednego miejsca do drugiego - i przy okazji może je przeformułować tak, że walidacja na wejściu ich nie rozpozna.
OWASP odróżnia tę kategorię od LLM09 Misinformation. Tam problemem jest to, że odpowiedź jest nieprawdziwa, a człowiek jej wierzy. Tutaj problem jest czysto techniczny: odpowiedź może być w pełni „poprawna", a i tak wykonać kod w przeglądarce.
Dokąd trafia odpowiedź - i co może tam zrobić
| Miejsce docelowe | Co się dzieje bez zabezpieczenia | Poprawka |
|---|---|---|
| HTML w przeglądarce | XSS: odpowiedź zawiera znacznik albo atrybut ze skryptem, który wykonuje się w sesji użytkownika. | Kodowanie HTML, renderowanie jako tekst, sanityzacja przy renderowaniu Markdownu, CSP. |
| Zapytanie do bazy | SQL injection: model „tłumaczy" pytanie na SQL, a ktoś podsuwa mu treść, która zamienia się w DROP albo odczyt cudzych danych. | Model wybiera zapytanie i parametry z gotowego zestawu. Konto bazy tylko do odczytu, z dostępem do wybranych widoków. |
Powłoka lub eval() | Zdalne wykonanie kodu na serwerze. | Nie wykonywać wygenerowanego kodu. Jeśli musi być wykonywany: odizolowany sandbox bez sieci i bez sekretów. |
| Ścieżka pliku | Path traversal: ../ w nazwie pliku, którą zaproponował model. | Model wskazuje identyfikator, aplikacja sama mapuje go na ścieżkę. |
| Szablon maila | Wstrzyknięcie linku albo treści do wiadomości wysyłanej z Waszej domeny - gotowy phishing z prawdziwym nadawcą. | Kodowanie treści, lista dozwolonych domen dla linków, przegląd przed wysyłką. |
| Wygenerowany kod | Asystent programisty proponuje paczkę, która nie istnieje - albo istnieje, bo ktoś ją zarejestrował pod często zmyślaną nazwą. | Weryfikacja każdej zależności przed instalacją, przypięte wersje, prywatny rejestr. |
Markdown - najczęściej przeoczony kanał
Prawie każdy czat renderuje odpowiedź modelu jako Markdown, bo tak wygląda ładniej. To oznacza, że składnia  w odpowiedzi zamienia się w żądanie przeglądarki użytkownika do dowolnego serwera. Automatycznie, bez kliknięcia.
Jeśli atakujący zdoła wstrzyknąć instrukcję (przez dokument, maila, stronę), może kazać modelowi dopisać do adresu obrazka fragment rozmowy albo danych, które model widzi. Przeglądarka ofiary sama wyśle je na serwer atakującego, a w logach tego serwera zostanie gotowy wyciek. To dokładnie trzeci element zabójczego trio, czyli kanał wyjścia - tyle że nie jest nim żadne narzędzie agenta, tylko zwykły renderer odpowiedzi.
Na co patrzymy
Czy odpowiedź może zawierać obrazki i linki do dowolnych domen, czy renderer przepuszcza surowy HTML w Markdownie, czy linki są pokazywane z pełnym adresem, zanim użytkownik w nie kliknie. Polityka CSP z img-src ograniczonym do własnych domen zamyka wariant z obrazkiem niezależnie od tego, co wygeneruje model.
Dlaczego walidacja wejścia nie wystarczy
Model nie przepisuje wejścia na wyjście znak po znaku. Potrafi złożyć ładunek z kawałków, przetłumaczyć go, odkodować z Base64, uzupełnić o brakujący fragment. Filtr, który na wejściu szuka <script>, nie zobaczy prośby „napisz znacznik skryptu, który…" w innym języku ani instrukcji ukrytej w dokumencie, który trafia do modelu z bazy wiedzy, a nie z formularza.
Druga rzecz: wstrzyknięcie drugiego rzędu. Wartość zapisana dziś - imię, preferencja, notatka w pamięci asystenta - trafia do odpowiedzi dopiero w kolejnej sesji, gdy walidacja na wejściu dawno się skończyła. Tak wyglądał przykład z testu opisany w ogólnym wpisie, gdzie zapisany zwrot grzecznościowy skończył jako polecenie wykonane na serwerze.
Stąd zasada, którą OWASP formułuje wprost jako podejście zero trust: odpowiedź modelu traktujemy jak dane od niezaufanego użytkownika i zabezpieczamy w miejscu użycia, nie na wejściu.
Jak to sprawdzamy w teście
- Mapa ujść. Wszystkie miejsca, do których trafia tekst wygenerowany przez model: interfejs czatu, panel administratora, eksporty do PDF i CSV, maile, logi przeglądane w przeglądarce, wywołania API, zapytania do bazy. Najciekawsze są te, których autor czatu nie uważał za część czatu.
- Ładunki przez model. Prosimy model, bezpośrednio albo przez wstrzyknięcie pośrednie, o wygenerowanie treści z ładunkiem XSS, SQL, poleceniem powłoki czy ścieżką z
../i sprawdzamy, czy w miejscu docelowym zostanie zinterpretowana. - Drugi odbiorca. Szczególnie te przypadki, w których odpowiedź generowana dla jednej osoby jest wyświetlana innej: historia rozmów w panelu obsługi, podsumowania zgłoszeń, raporty dla przełożonego. XSS w takim miejscu uderza w konto o wyższych uprawnieniach.
- Renderer. Obrazki i linki do zewnętrznych domen, surowy HTML w Markdownie, nagłówki CSP.
- Wstrzyknięcie drugiego rzędu. Pola, które model zapamiętuje i używa później: profil, pamięć, notatki, nazwy plików.
- Wykonywanie kodu. Czy gdzieś w potoku jest
eval(), interpreter albo powłoka, a jeśli tak, to w jakiej izolacji i z jakimi sekretami w zasięgu.
Co robić
- Kodowanie w miejscu użycia, dopasowane do kontekstu. Inaczej dla HTML, inaczej dla atrybutu, inaczej dla JavaScriptu i adresu URL. To te same zasady co przy każdej innej aplikacji webowej, opisane w OWASP Top 10 w kategorii wstrzyknięć.
- Strukturyzowane odpowiedzi zamiast wolnego tekstu. Tam, gdzie model steruje logiką, niech zwraca wybór z zamkniętego zestawu i parametry, które aplikacja waliduje schematem. Wtedy to kod decyduje, co się wykona.
- Zapytania parametryzowane i konto o minimalnych prawach. Nawet jeśli model generuje zapytanie, baza nie powinna pozwolić na więcej niż odczyt wybranych danych.
- Zero
eval()na wyjściu modelu. Kod, który musi być wykonany, idzie do odizolowanego środowiska bez sieci i dostępu do sekretów. - CSP i ograniczony renderer. Brak surowego HTML w Markdownie, obrazki tylko z własnych domen, linki pokazywane z pełnym adresem.
- Logi odpowiedzi. Przechowujcie to, co model zwrócił, a nie tylko to, o co go zapytano. Inaczej po incydencie nie odtworzycie ładunku.
Czego to nie załatwi
Poprawna obsługa wyjścia chroni systemy za modelem, ale nie chroni człowieka, który tę odpowiedź czyta. Model nadal może napisać przekonujące, nieprawdziwe instrukcje albo podsunąć link, który wygląda wiarygodnie. To już temat dezinformacji i socjotechniki, a na nie działa inna warstwa: szkolenia i procedury weryfikacji.
Dobra wiadomość jest taka, że LLM05 to najbardziej „klasyczna" kategoria z całej listy. Nie wymaga nowych narzędzi ani badań nad modelami - wymaga tego, żeby do tekstu z modelu zastosować reguły, które zespół już zna. Zakres, w jakim to sprawdzamy, opisujemy na stronie testów penetracyjnych aplikacji LLM.
Wasza aplikacja wyświetla albo wykonuje to, co wygeneruje model?
Śledzimy drogę odpowiedzi modelu do każdego miejsca, w którym jest używana: przeglądarki, bazy, powłoki, szablonów maili. Napiszcie - odpiszemy w 24 h.
Zapytaj o wycenę