Dwa sanitizery, jedna spacja

sie 10, 2026 | Cyberflux

Opisywaliśmy, jak pół miliarda stron dostało w rdzeniu wbudowaną bramę do modeli AI, a dwa miesiące później znaleziono w tym samym rdzeniu dziurę, która nie wymaga logowania. Siódmego sierpnia przyszła kolejna, w tym samym rdzeniu i również bez uwierzytelnienia — tym razem na ekranie logowania, w kodzie, który jest w WordPressie od wersji 4.7, czyli od 2016 roku.

Nazywa się XSS2Shell, oznaczono ją jako CVE-2026-64638 i dotyczy każdej wersji WordPressa. Pojedyncza nieudana próba logowania wystarcza, żeby uruchomić kod atakującego w przeglądarce odwiedzającego, a przy spełnieniu dodatkowych warunków — doprowadzić do wykonania kodu PHP na serwerze.

Ale nie to jest w niej najciekawsze. Najciekawsze jest, że cała luka sprowadza się do jednej spacji, którą dwa różne mechanizmy zabezpieczające czytają na dwa różne sposoby. I że znalazł ją autonomiczny system wieloagentowy.

Spacja, której jeden parser nie widzi

Mechanizm jest elegancki w sposób, który docenia każdy, kto kiedykolwiek szukał błędów w kodzie.

Gdy do formularza logowania trafi nieistniejąca nazwa użytkownika, WordPress buduje komunikat o błędzie i przepuszcza tę nazwę przez funkcję wp_strip_all_tags, która ma usunąć z niej wszelkie znaczniki HTML. Pod spodem korzysta ona z natywnego parsera PHP.

I tu badacze z pwn.ai zauważyli rzecz drobną: jeśli między nawiasem otwierającym a nazwą znacznika wstawić spację — na przykład < area zamiast <area — to parser PHP uznaje, że to nie jest znacznik, tylko zwykły tekst. Nie ma czego usuwać, więc przepuszcza ciąg dalej nietknięty.

Problem w tym, że kilka kroków później ten sam ciąg trafia do drugiego mechanizmu sanityzującego, KSES, który ma własny tokenizer i własne reguły. A ten uznaje, że < area to jak najbardziej znacznik — i odpowiednio go interpretuje.

Dwa sanitizery. Każdy z nich działa dokładnie tak, jak go zaprojektowano. Każdy z osobna jest poprawny. Luka nie istnieje w żadnym z nich — istnieje wyłącznie w różnicy między nimi, w wąskim marginesie, gdzie jeden widzi tekst, a drugi znacznik.

To jest ten sam kształt, który opisywaliśmy przy Cordyceps, luce istniejącej wyłącznie w kompozycji — tam niezaufane dane przekraczały granicę zaufania, której nikt nie audytował, bo każdy komponent osobno był w porządku. Tutaj granicą jest przejście między dwoma tokenizerami, z których żaden nie wie o istnieniu drugiego.

Potwierdza to zresztą sama poprawka. WordPress nie zmienił żadnego z tokenizerów. Zamiast tego zakodował nazwę użytkownika przy pomocy esc_html() w miejscu, gdzie trafia ona do komunikatu — czyli zanim w ogóle dotrze do któregokolwiek z sanitizerów. Nie naprawiono niezgodności. Wycofano dane z obszaru, w którym ta niezgodność miała znaczenie.

Od nieudanego logowania do wtyczki na serwerze

Sam XSS jest poważny, ale to, co pwn.ai zbudowało na jego bazie, jest ciekawsze — bo pokazuje, jak wiele funkcji trzeba połączyć, żeby dojść od komunikatu o błędzie do kodu na serwerze.

Punktem wyjścia jest wykonanie skryptu atakującego w origin witryny. Osiąga się to, kierując żądanie do REST API WordPressa z parametrami nadpisania metody i JSONP — odpowiedź wraca opakowana w wykonywalny JavaScript. Ten etap nie wymaga żadnego konta.

Dalsza część łańcucha opiera się na technice sprzed czterech lat: Same Origin Method Execution, opisanej przez Paulosa Yibelo w 2022 roku, która pozwala wywołać metodę w innym oknie przeglądarki przez dozwolony łańcuch JSONP. Za jej pomocą skrypt sięga do natywnej kontrolki zatwierdzania haseł aplikacji w sesji zalogowanego administratora. WordPress tworzy wtedy poświadczenie API i przekierowuje je na wskazany adres.

Ten fragment jest wart podkreślenia, bo pokazuje, jak działa nowoczesny łańcuch ataku: nie trzeba kraść hasła administratora. Hasła aplikacji to odwoływalne poświadczenia przeznaczone do dostępu przez API — legalna funkcja, użyta zgodnie z przeznaczeniem, tylko przez niewłaściwą osobę.

Mając to poświadczenie, atakujący publikuje przez REST stronę WordPressa zawierającą własny JavaScript. Gdy zachowana sesja administratora otworzy tę stronę, skrypt pobiera token zabezpieczający formularz wgrywania wtyczek i przesyła przygotowane archiwum ZIP. Wtyczka to kod PHP, a kod PHP wykonuje się na serwerze.

Każdy krok tego łańcucha jest funkcją WordPressa działającą poprawnie. Zsumowane dają przejęcie serwera.

Znalazł to system wieloagentowy

I tu dochodzimy do części, która dla nas jest najważniejsza.

Pwn.ai podaje, że łańcuch został odkryty całkowicie autonomicznie przez wieloagentowy system, zasilony wcześniejszymi badaniami Yibelo nad Same Origin Method Execution z 2022 roku. Nie chodzi więc o narzędzie, które przeczesuje kod w poszukiwaniu wzorców — chodzi o system, który połączył niezgodność dwóch parserów z czteroletnią techniką badawczą i zbudował z tego wieloetapowy łańcuch aż do wykonania kodu.

Zestawmy to z dwoma wcześniejszymi przypadkami, o których pisaliśmy. Na początku lipca model znalazł jeden błąd w łatce jądra i przeoczył bliźniaczy, a pod koniec miesiąca badacz opisał wyścig w jądrze, przy którym AI pomogła mu dopiero wtedy, gdy pokazał jej, gdzie patrzeć. Postawiliśmy wtedy tezę, że granica biegnie nie między klasami błędów, lecz między trybami pracy: autonomiczne przeczesywanie ma ślepe pole, prowadzona asysta go nie ma.

XSS2Shell tę tezę doprecyzowuje, a nie obala. Bo różnica między tym, co system znalazł tutaj, a tym, co przeoczył przy Bad Epoll, jest konkretna. Wyścig w jądrze istnieje wyłącznie w czasie — w oknie szerokim na kilka instrukcji, w splocie dwóch wątków, bez śladu w kodzie i bez sygnału w runtime. Niezgodność dwóch parserów istnieje w kodzie: jest deterministyczna, powtarzalna i da się ją wywnioskować, czytając oba tokenizery i porównując ich reguły. To wciąż jest kompozycja, ale statyczna.

Zgadza się to z całą teksturą, którą opisujemy od miesięcy. AI radzi sobie znakomicie z tym, co da się rozstrzygnąć, patrząc na kod — nawet gdy wymaga to porównania dwóch odległych komponentów i zbudowania wieloetapowego łańcucha. Wciąż potyka się na tym, co istnieje tylko w konkretnej sekwencji zdarzeń w czasie.

Warto też zauważyć, że autonomia nie oznaczała tu pracy od zera. System był zasilony konkretnym ludzkim badaniem sprzed czterech lat — to człowiek opisał technikę, którą maszyna zastosowała w nowym kontekście. To jest ten sam wzorzec co przy wyścigu w jądrze, tylko z dłuższym dystansem między ludzkim wkładem a maszynowym wykonaniem.

Uczciwie: skala i warunki

Kilka rzeczy trzeba postawić proporcjonalnie, bo ta luka jest opisywana w mediach bardzo różnie.

Ocena to 8,9 punktu w skali CVSS — wysoka, nie krytyczna, i to rozróżnienie ma znaczenie przy kalibrowaniu realnego ryzyka. Sam XSS nie wymaga uwierzytelnienia i tu nie ma dyskusji: każdy może przygotować odpowiedni adres. Ale eskalacja do wykonania kodu PHP wymaga dodatkowo zalogowanego administratora, jego świadomej interakcji ze stroną kontrolowaną przez atakującego oraz określonych warunków wdrożenia. Sam WordPress opisuje to jako wymagające skutecznej socjotechniki i wyraźnego działania ofiary.

Na dzień 8 sierpnia nie potwierdzono wykorzystania w realnych atakach, a pwn.ai wstrzymało publikację pełnego dowodu koncepcji, żeby dać czas na wdrożenie łatki.

Reakcja WordPressa zasługuje przy tym na dobre słowo. Zgłoszenie wpłynęło 27 lipca, tego samego dnia projekt uznał ryzyko, a 6 sierpnia wyszło wydanie 7.0.3 — osiem dni od zgłoszenia do poprawki, wraz z jedenastoma innymi łatkami bezpieczeństwa, wypłaconą nagrodą i przeniesieniem poprawki wstecz aż do gałęzi 4.7. Publiczne ujawnienie nastąpiło dzień po łatce, w skoordynowanym trybie.

Jest tu jednak przypis, który wynika wprost z naszego lipcowego wpisu o WordPressie. Wtedy CERT Polska ostrzegał, że wymuszona automatyczna aktualizacja w licznych przypadkach nie zadziałała. Ta sama uwaga obowiązuje teraz: automatyczne aktualizacje bezpieczeństwa są tu głównym mechanizmem ochrony i dla większości witryn zadziałają bez żadnego działania administratora — ale zakładanie, że zadziałały, jest tym samym błędem, który popełniono trzy tygodnie temu.

Co z tego wynika dla obrońcy

Rzecz pierwsza jest natychmiastowa: sprawdź faktycznie zainstalowaną wersję, nie to, czy automatyczne aktualizacje są włączone. Docelowo 7.0.3 lub odpowiednia załatana wersja ze starszej gałęzi. Instalacje na wersjach starszych niż 4.7 nie dostały poprawki i wymagają przeniesienia na wspieraną gałąź.

Rzecz druga wynika z łańcucha eskalacji i jest tania: przejrzyj hasła aplikacji w kontach administracyjnych i odwołaj te, których nie rozpoznajesz. To jest poświadczenie, które w tym ataku powstaje legalnie i bez kradzieży hasła głównego, więc nie zostawia śladu w postaci nieudanych logowań. Warto też ograniczyć liczbę kont z uprawnieniami do wgrywania wtyczek — cały łańcuch kończy się na tej jednej funkcji.

Rzecz trzecia jest ogólniejsza i wynika z mechanizmu. Wszędzie tam, gdzie dane przechodzą przez więcej niż jedenmechanizm sanityzujący, powstaje margines między ich interpretacjami. Poprawka WordPressa pokazuje właściwe podejście: zamiast uzgadniać dwa tokenizery, zakodowano wartość, zanim do nich dotarła. Jeśli w twoim kodzie dane przechodzą przez łańcuch filtrów, warto sprawdzić, czy wszystkie czytają je tak samo — a jeśli nie masz pewności, wycofać je z tego łańcucha przez wcześniejsze kodowanie.

Jedna myśl na koniec

Ta luka siedziała w WordPressie dziesięć lat i nie była ukryta. Oba sanitizery są publiczne, oba mają otwarty kod, a różnica w ich interpretacji jednego znaku dała się wykazać jednym żądaniem. Nikt jej przez dekadę nie znalazł nie dlatego, że była trudna do zobaczenia, lecz dlatego, że wymagała jednoczesnego czytania dwóch komponentów, które w żadnym przeglądzie kodu nie występują razem — jeden jest funkcją pomocniczą, drugi biblioteką filtrowania, a łączy je tylko to, że dane przechodzą przez oba po kolei.

To jest dokładnie ten rodzaj pracy, w którym maszyna nie ma naszych ograniczeń. Nie nudzi się porównywaniem reguł dwóch tokenizerów linijka po linijce i nie zakłada, że skoro każdy z nich przeszedł przeglądy, to ich złożenie też jest bezpieczne. XSS2Shell nie jest dowodem, że AI wymyśliło coś, czego człowiek nie mógłby wymyślić. Jest dowodem, że potrafi wykonać żmudne porównanie, którego przez dziesięć lat nikt nie chciał zrobić — i że w kodzie, którym stoi połowa internetu, takich porównań czeka jeszcze bardzo wiele.

Źródła

pwn.ai — oryginalne badanie XSS2Shell z opisem łańcucha od nieudanego logowania do wykonania kodu PHP, informacją o autonomicznym odkryciu przez system wieloagentowy oraz pełną osią czasu ujawnienia: https://pwn.ai/blog/xss2shell

WordPress — ogłoszenie wydania 7.0.3 z 6 sierpnia 2026, zawierającego poprawkę dla tej luki oraz jedenaście innych łatek bezpieczeństwa: https://wordpress.org/news/2026/08/wordpress-7-0-3-release/

WordPress / GitHub Security Advisory — oficjalny opis podatności z potwierdzeniem, że dotyczy wszystkich wersji, wraz z warunkami eskalacji do zdalnego wykonania kodu i zakresem przeniesienia poprawki wstecz do gałęzi 4.7: https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-52p2-r8wf-jcrf

Hadrian — analiza techniczna z opisem miejsca poprawki w wp-includes/user.php, zaznaczeniem, że żaden tokenizer nie został zmieniony, oraz informacją o zasileniu systemu badaniami Yibelo z 2022 roku: https://hadrian.io/blog/wordpress-xss2shell-unauthenticated-login-screen-xss-to-php-code-execution-cve-2026-64638

Cybersecurity News — szczegółowy opis niezgodności między parserem PHP a sanitizerem KSES przy ciągu ze spacją po nawiasie otwierającym oraz kontekst skali wdrożeń WordPressa: https://cybersecuritynews.com/wordpress-xss2shell-vulnerability/

The Hacker News — potwierdzenie oceny 8,9 punktu, opisu ścieżki przez hasła aplikacji i warunków wymaganych do eskalacji: https://thehackernews.com/2026/08/new-wordpress-pre-auth-xss-could-lead.html