Błąd URL encoding często wygląda jak drobny problem z wyświetlaniem. Jeden znak może jednak zmienić gramatykę requestu: ampersand tworzy drugi parametr, ukośnik nowy segment, plus staje się spacją, a kolejne decoding zamienia dane w składnię. Najtrudniejsze awarie pojawiają się wtedy, gdy proxy, framework i aplikacja interpretują ten sam adres w różny sposób.
Każdy komponent ma własne reguły
W path ukośnik rozdziela segmenty. W query ampersand i równość budują parametry. Fragment nie trafia do serwera. Funkcja kodująca cały string nie wie, które znaki są strukturą, a które danymi.
Bezpieczny kod pracuje na surowych wartościach i używa URL buildera, encoderów segmentów oraz API query. Encoding następuje dopiero w miejscu składania końcowego adresu.
Konkatenacja działa tylko dla prostych przykładów
Wartość „R&D” bez poprawnego encodingu tworzy dodatkowy parametr. Plus w numerze, hash w nazwie czy polskie znaki ujawniają kolejne błędy. Test z abc omija wszystkie zarezerwowane znaki.
Biblioteka rozwiązuje też reprezentację pustych wartości i tablic. Kontrakt musi określić, które z wielu technicznie poprawnych postaci API uznaje za kanoniczne.
Podwójny encoding zmienia znaczenie
Spacja jako %20 po drugim kodowaniu staje się %2520. Jeden decode daje literalny tekst %20, drugi spację. Taka niejednoznaczność psuje routing, cache i podpisy.
Wartości wewnętrzne powinny pozostać niezakodowane. Tylko właściciel końcowej konstrukcji URL wykonuje transformację dokładnie raz.
Wielokrotne decoding nie jest naprawą
Reguła „dekoduj aż nic się nie zmienia” pozwala opóźnić pojawienie się składni. Firewall może zobaczyć bezpieczny path po jednym kroku, a router wykonać drugi i przejść do innej trasy.
Infrastruktura potrzebuje jednej zdefiniowanej normalizacji. Ambiguous i wielokrotnie kodowane wejście warto odrzucać zamiast zgadywać intencję.
Plus nie wszędzie oznacza spację
W form-urlencoded plus tradycyjnie reprezentuje spację. W path albo zwykłym komponencie jest literalnym plusem. Standardowy Base64 czy numer telefonu w query może zostać zmieniony jeszcze przed kodem aplikacji.
Query builder koduje prawdziwy plus. Dla tokenów istnieje Base64URL, ale sam wariant alfabetu nie zastępuje poprawnego parsera.
Encoded slash różnie przechodzi przez serwery
%2F może być częścią ID, lecz część serwerów odrzuca go albo dekoduje przed routingiem. Wtedy jeden logiczny segment staje się dwoma. Kontrola path na wcześniejszej warstwie może nie odpowiadać wykonanej trasie.
Publiczne identyfikatory powinny, jeśli to możliwe, używać alfabetu przyjaznego ścieżce. W przeciwnym razie cała infrastruktura musi mieć przetestowane, jednolite zachowanie.
Zduplikowane parametry są decyzją bezpieczeństwa
role=user&role=admin może zostać odczytane jako pierwszy, ostatni lub tablica. Jeśli gateway kontroluje pierwszą wartość, a backend używa ostatniej, atakujący wykorzysta rozbieżność.
Powtórzenia powinny być dozwolone wyłącznie dla udokumentowanych list. Pozostałe duplikaty warto odrzucać na wszystkich warstwach według tej samej reguły.
Podpisane URL wymagają dokładnej canonicalization
HMAC może obejmować metodę, path i query. Kolejność parametrów, zapis procentowy, puste wartości i spacje muszą być identyczne po obu stronach. Dwie semantycznie równe wersje tekstowe mogą mieć inne podpisy.
Specyfikacja powinna zawierać test vectors. Nie należy polegać na domyślnym zachowaniu różnych bibliotek.
Redirect potrzebuje allowlisty, nie tylko encodingu
Parametr next może być poprawnie zakodowany i wskazywać domenę phishingową. Encoding chroni składnię, ale nie cel. Najbezpieczniej przyjmować względne wewnętrzne path albo sprawdzać sparsowany host.
Prefiks tekstowy nie wystarcza wobec userinfo, backslashy i podwójnego kodowania. Walidacja następuje po kontrolowanym rozwiązaniu adresu.
Unicode najpierw staje się bajtami
Percent-encoding pracuje na bajtach. Polski znak jest najpierw zapisany w UTF-8, potem każdy wymagający byte otrzymuje sekwencję procentową. Niezgodny charset daje poprawnie zdekodowany, ale uszkodzony tekst.
Hosty mają dodatkowo reguły IDNA. Decyzja bezpieczeństwa powinna korzystać ze znormalizowanej reprezentacji parsera, nie wyłącznie z wyglądu.
Log może nie pokazywać oryginalnego requestu
Proxy zapisuje czasem URL już znormalizowany, a aplikacja tylko końcowe parametry. Przy incydencie brakuje formy wysłanej przez klienta. Podwójny decode wygląda wtedy jak niewytłumaczalny błąd routera.
W granicach prywatności warto korelować raw request target i postać po walidacji przez request ID. Wrażliwe query muszą być redagowane.
Klient HTTP również może normalizować
Test stringa przed wywołaniem nie dowodzi, jaki adres wyszedł na sieć. Biblioteka może ponownie zakodować procent, posortować query albo dodać slash. Integration test powinien sprawdzać request odebrany przez serwer.
Redirecty też wymagają testu, ponieważ każdy Location przechodzi kolejny cykl parsowania.
Odrzucenie bywa bezpieczniejsze niż „naprawa”
Niepoprawne escape, null byte lub zabronione wielokrotne encoding powinny dać kontrolowany błąd 400. Ciche usuwanie znaków może zamienić niepoprawną wartość w inny poprawny zasób.
Log wewnętrzny może podać komponent i naruszoną regułę. Odpowiedź zewnętrzna nie musi ujawniać szczegółów infrastruktury.
Długość i liczba parametrów także są częścią bezpieczeństwa
Ogromny request target obciąża proxy, parser, cache i logi, zanim aplikacja oceni znaczenie wartości. Infrastruktura powinna mieć zgodne limity długości URL, liczby parametrów oraz rozmiaru pojedynczego pola. Zbyt różne wartości powodują, że jedna warstwa zaakceptuje request odrzucony później przez inną.
Przekroczenie limitu powinno dawać przewidywalny status i nie kopiować całej wejściowej wartości do logu błędu.
Testy muszą przejść przez całą ścieżkę
Zestaw obejmuje spacje, plus, ampersand, równość, hash, slash, Unicode, już zakodowany procent i duplikaty. Należy sprawdzić builder, proxy, router oraz faktycznie wybraną operację.
URL encoding jest przewidywalny, gdy jedna warstwa jest jego właścicielem, a każda kolejna widzi tę samą strukturę. Częściowo przetworzone stringi niszczą tę pewność.