
Rok temu, 21 marca 2019 roku, w na HackerOne zgłoszono bardzo dobry od . Przy wprowadzeniu zera bajtowego (ASCII 0) w parametr POST jednego z zapytań API usługi pocztowej, które zwracało przekierowanie HTTP, w danych przekierowania widniały fragmenty nieinicjowanej pamięci, w których najczęściej ujawniały się fragmenty z parametrów GET i nagłówków innych zapytań do tego samego serwera.
To krytyczna podatność, ponieważ zapytania zawierają również ciasteczka sesyjne. Po kilku godzinach stworzono tymczasowe rozwiązanie, które filtrowało zerowy bajt (jak później się okazało, to nie wystarczyło, ponieważ pozostała możliwość wstrzyknięcia CRLF / ASCII 13, 10, co pozwalało manipulować nagłówkami i danymi odpowiedzi HTTP, jest to mniej krytyczne, ale nadal nieprzyjemne). Jednocześnie problem został przekazany analitykom bezpieczeństwa i programistom w celu znalezienia i likwidacji przyczyn wystąpienia błędu.
Poczta Mail.ru to bardzo złożona aplikacja, w formowaniu odpowiedzi może brać udział wiele różnych komponentów frontendowych/backendowych, zarówno open source (wielkie podziękowania dla wszystkich programistów oprogramowania wolnego), jak i opracowanych wewnętrznie. Udało się wykluczyć wszystkie komponenty poza nginx i openresty i zlokalizować problem do wywołania w skrypcie OpenResty, który działał inaczej, niż oczekiwano (nie można było wstrzyknąć zera bajtowego ani znaku nowej linii przez parametry GET z przepisywaniem w ngx_http_rewrite_module, który, zgodnie z dokumentacją, jest używany i wydawałoby się, że powinien działać dokładnie tak samo). Zminimalizowano możliwe konsekwencje, dodano maksymalnie restrykcyjne filtrowanie i sprawdzono, czy filtracja eliminuje wszystkie możliwe wektory. Ale mechanizm, który prowadził do wycieku zawartości pamięci, pozostał tajemnicą. Po miesiącu raport o błędzie zamknięto jako rozwiązany, a analiza przyczyn wystąpienia błędu została odłożona na lepsze czasy.
OpenResty to bardzo popularny wtyczka umożliwiająca pisanie skryptów Lua wewnątrz nginx, wykorzystywana w kilku projektach Mail.ru, dlatego problem nie był uważany za rozwiązany. I po pewnym czasie powrócono do niego, aby zrozumieć prawdziwe przyczyny, możliwe konsekwencje i opracować zalecenia dla programistów. W odkryciach kodu źródłowego uczestniczył i . Okazało się, że:
- W nginx, przy użyciu rewrite z danymi użytkownika, istnieje możliwość przejścia przez katalog (prawdopodobnie SSRF) w niektórych konfiguracjach, ale to znany fakt, który powinien być wykrywany przez statyczne analizatory konfiguracji w i od Yandex (tak, my też go używamy, dziękujemy). Przy użyciu OpenResty można łatwo przeoczyć tę możliwość, ale nasza konfiguracja nie była nią dotknięta.
przykład konfiguracji:
location ~ /rewrite { rewrite ^.*$ $arg_x; } location / { root html; index index.html index.htm; }wynik
curl localhost:8337/rewrite?x=/..../..../..../..../..../..../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
... - W nginx występuje błąd prowadzący do wycieku zawartości pamięci, jeśli ciąg rewrite zawiera bajt zerowy. Przy zwracaniu przekierowania nginx przydziela nowy bufor pamięci odpowiadający całkowitej długości ciągu, ale kopiuje ciąg przez funkcję, w której bajt zerowy jest terminatorem ciągu, więc ciąg jest kopiowany tylko do bajtu zerowego, a reszta bufora zawiera niezainicjowane dane. Szczegółowe omówienie można znaleźć .
przykład konfiguracji (^@ bajt zerowy)
location ~ /memleak { rewrite ^.*$ "^@asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdasdf"; } location / { root html; index index.html index.htm; }wynik
curl localhost:8337/secret -vv
...
curl localhost:8337/memleak -vv
...
Lokalizacja: http://localhost:8337/secret
...
- Nginx chroni parametry GET przed iniekcją znaków sterujących i umożliwia użycie w rewrite tylko parametrów GET. Dlatego eksploatacja iniekcji przez kontrolowane przez użytkownika parametry w nginx jest niemożliwa. Parametry POST w tym przypadku nie są chronione. OpenResty pozwala pracować zarówno z parametrami GET, jak i POST, więc przy użyciu parametrów POST przez OpenResty pojawia się możliwość iniekcji specjalnych znaków.
przykład konfiguracji:
location ~ /memleak { rewrite_by_lua_block { ngx.req.read_body(); local args, err = ngx.req.get_post_args(); ngx.req.set_uri( args["url"], true ); } } location / { root html; index index.html index.htm; }wynik:
curl localhost:8337 -d "url=secret" -vv
...
curl localhost:8337 -d "url=asdfasdfasdfasdfasdfasdfasdfasdf" -vv
...
Lokalizacja: http://localhost:8337/{...może zawierać secret...}
...
Dalsza reakcja
O problemie poinformowano programistów nginx i OpenResty, programiści nie traktują tego problemu jako błędu bezpieczeństwa w nginx, ponieważ w samym nginx nie ma możliwości eksploatacji błędu przez iniekcję znaków specjalnych, poprawka została opublikowana 16 grudnia. Minęły 4 miesiące od momentu zgłoszenia w OpenResty, w ciągu tych 4 miesięcy nie wprowadzono żadnych zmian, mimo że było zrozumienie konieczności bezpiecznej wersji funkcji ngx.req.set_uri(). 18 marca 2020 opublikowaliśmy informację, a 21 marca OpenResty wydała , która dodaje sprawdzenie URI.
Portswigger Dobry artykuł, który zawiera komentarze od OpenResty i Nginx (jednakże komentarz, że ujawnia się tylko mały fragment pamięci, jest błędny i wprowadza w błąd; to zależy od długości ciągu następującego po zerowym bajcie i w braku jawnych ograniczeń na długość, może być kontrolowane przez atakującego).
Jak więc doszło do tego błędu i co zrobić, aby go zapobiec?
Czy błąd wynikał z nginx? Tak, wynikał, ponieważ wyciek zawartości pamięci to w każdym przypadku błąd.
Czy błąd dotyczył OpenResty? Tak, przynajmniej kwestia bezpieczeństwa oferowanej funkcjonalności OpenResty nie została zbadana ani udokumentowana.
Czy popełniono błąd w konfiguracji / użyciu OpenResty? Tak, ponieważ w braku wyraźnego wskazania, zrobiono niepotwierdzone założenie o bezpieczeństwie używanej funkcjonalności.
Która z tych błędów jest podatnością na bezpieczeństwo z nagrodą w wysokości $10000? Dla nas to w zasadzie nie ma znaczenia. W każdym oprogramowaniu, szczególnie w przypadku złożoności kilku komponentów, zwłaszcza dostarczanych przez różne projekty i deweloperów, nikt i nigdy nie może zagwarantować, że wszystkie cechy ich działania są znane i udokumentowane, a błędów nie ma. Dlatego każda podatność na bezpieczeństwo pojawia się tam, gdzie wpływa na bezpieczeństwo.
W każdym razie dobrą praktyką będzie normalizacja lub maksymalne ograniczanie / filtrowanie danych wejściowych, które trafiają do jakiegokolwiek zewnętrznego modułu / API, jeśli nie ma wyraźnych wskazówek i jednoznacznego zrozumienia, że nie jest to wymagane.
Errata
Z doświadczenia , w celu zachowania czystości języka:
bug bounty — konkurs na wykrywanie błędów
raport o błędzie — powiadomienie o błędzie
przekierowanie — skierowanie
otwartoźródłowy — otwarty kod
errata — prace nad błędami
Źródło: habr.com
