NGINX: Ujawnianie wersji i sygnatur serwera

19 Jul 2017

best-practices  headers  http  nginx  security  version 

Share on:

Ujawnienie wersji oraz sygnatur serwera NGINX może być niepożądane, szczególnie w środowiskach wrażliwych na ujawnianie informacji (tj. przetwarzających dane krytyczne). NGINX domyślnie wyświetla numer wersji na stronach błędów i w nagłówkach odpowiedzi HTTP.

Informacje te mogą być wykorzystane jako punkt wyjścia dla atakujących, którzy znają określone luki związane z określonymi wersjami i mogą pomóc w lepszym zrozumieniu używanych systemów, a także potencjalnie rozwinąć dalsze ataki ukierunkowane na określoną wersję usługi. Pamiętaj, że atakujący będzie starał się zdobyć możliwie jak najwięcej informacji o aplikacji i środowisku, w którym działa.

Na przykład Shodan zapewnia powszechnie używaną bazę danych zawierającą takie informacje, dzięki czemu jest idealnym miejscem do rozpoczęcia analizy i zbierania informacji o celu. O wiele bardziej wydajne jest po prostu wypróbowanie luki na wszystkich losowych serwerach niż bezpośrednie odpytywanie każdego z nich.

Zlekceważenie tak ważnego czynnika związanego z bezpieczeństwem jest moim zdaniem elementarnym błędem. Oczywiście bezpieczeństwo poprzez zaciemnienie (ang. security through obscurity) nie ma tak naprawdę żadnego wpływu na bezpieczeństwo serwera czy infrastruktury (polecam także ten oraz ten artykuł), jednak jest pewne, że opóźni przeprowadzenie ataku, jeśli znany jest jego wektor specyficzny dla danej wersji usługi.

Całkowite pominięcie tego kroku to bardzo zły pomysł, ponieważ nawet najbezpieczniejsze serwery HTTP mogą zostać złamane. Takie podejście nie daje gwarancji, że ​​jesteś bezpieczny, ale w większości spowalnia atakującego, i to jest dokładnie to, co jest potrzebne w przypadku ataków Zero-day.

Jeżeli masz jakiekolwiek dylematy co do takiego podejścia, RFC 2616 - Personal Information [IETF] będzie tutaj bardzo pomocne w podjęciu decyzji:

History shows that errors in this area often create serious security and/or privacy problems and generate highly adverse publicity for the implementor's company. [...] Like any generic data transfer protocol, HTTP cannot regulate the content of the data that is transferred, nor is there any a priori method of determining the sensitivity of any particular piece of information within the context of any given request. Therefore, applications SHOULD supply as much control over this information as possible to the provider of that information. Four header fields are worth special mention in this context: Server, Via, Referer and From.

W ramach ciekawostki, spójrz, co na ten temat mówi dokumentacja serwera Apache:

Setting ServerTokens to less than minimal is not recommended because it makes it more difficult to debug interoperational problems. Also note that disabling the Server: header does nothing at all to make your server more secure. The idea of "security through obscurity" is a myth and leads to a false sense of safety.

Polecam także:

Ujawnianie wersji serwera #

Ukrywanie informacji o wersji nie powstrzyma ataku, ale sprawi, że będziesz mniejszym celem, jeśli atakujący szukają określonej wersji sprzętu lub oprogramowania. Według mnie, dane transmitowane przez serwer HTTP należy traktować jako dane osobowe (bynajmniej nie jest to stwierdzenie ani trochę na wyrost).

Aby zapobiec ujawnianiu wersji, należy wyłączyć jej rozgłaszanie na stronach błędów oraz w polu nagłówka Server za pomocą poniższej dyrektywy:

server_tokens off;

Dzięki tej zmianie, zamiast tego:

› <html>
› <head><title>403 Forbidden</title></head>
› <body bgcolor="white">
› <center><h1>403 Forbidden</h1></center>
› <hr><center>nginx/1.12.2</center>
› </body>
› </html>

Otrzymamy to:

› <html>
› <head><title>403 Forbidden</title></head>
› <body bgcolor="white">
› <center><h1>403 Forbidden</h1></center>
› <hr><center>nginx</center>
› </body>
› </html>

Dodatkowo istnieje kilka możliwości, aby całkowicie ukryć informację o tym, że serwerem jest NGINX (o tym jednak dokładniej za chwilę):

W przypadku wykorzystania dyrektywy error_page pamiętaj, aby zwracać szczególną uwagę na składnię:

Która co prawda zwraca stronę 404.html ale z kodem 200. Powinieneś ustawić error_page 404 /404.html; a otrzymasz oryginalny kod błędu, tj. 404.

Taki handler jest podatny na atak typu HTTP request smuggling [PDF], umożliwiając osobie atakującej przemycenie żądania i potencjalnie uzyskanie dostępu do wrażliwych zasobów/informacji. Zamiast tego używaj error_page 404 /404.html; + error_page 404 @ 404; — obie konstrukcje nie są podatne.

Przy okazji zapoznaj się także z poniższymi zasobami:

Ukrycie informacji o serwerze z domyślnych stron błędów #

Poruszyłem już ten temat, jednak uważam, że należy go dokładnie opisać, ponieważ istnieje kilka możliwości, aby ukryć ciąg nginx z domyślnych (przechowywanych w kodzie NGINX) statycznych stron (błędów). Pominę jednak możliwość edycji źródeł oraz rekompilacji, mimo tego, że uważam, że jest to najmniej kosztowna opcja biorąc pod uwagę późniejszą obsługę żądań i odpowiedzi, i skupię się na innych dostępnych możliwościach.

Wykorzystanie modułów świetnie się sprawdza, jeżeli chcemy globalnie zmienić pewien ciąg znaków. Należy jednak pamiętać, że będzie się to wiązało z przetworzeniem każdej odpowiedzi, co może zwiększyć obciążenie procesów serwera NGINX.

Wygenerowanie nowych stron statycznych lub wykorzystanie modułu SSI moim zdaniem jest lepsze z punktu wydajności. Wadą jednak jest trochę większa komplikacja, ponieważ musimy dokonać ustawień w kilku miejscach.

Edycja domyślnych plików statycznych #

1) Tworzymy plik conf/custom.conf, w którym zdefiniujemy konkretne odpowiedzi:

error_page 401 /401.html;
location = /401.html {

  root html/custom;
  internal;

}

2) W katalogu html/custom tworzymy plik statyczny 401.html z przykładową zawartością:

<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
</body>
</html>

3) Dołączamy plik conf/custom.conf np. do kontekstu server:

include conf/custom.conf;

SSI i dynamiczne strony błędów #

To podejście może wydawać się trochę bardziej skomplikowane, jednak moim zdaniem jest lepsze (zwłaszcza dla stron, w których zmienia się za każdym razem ten sam fragment odpowiedzi) niż przedstawiony sposób powyżej, ponieważ pozwala lepiej (prościej) kontrolować zawartość, którą chcemy podmienić. Pamiętaj jednak, że nadaje się idealnie do obsługi błędów zwracanych z proxy lub z web’ów, natomiast np. obsługa przekierowań będzie problematyczna jeżeli nie pochodzą one z dyrektyw (np. return) obsługiwanych przez te dwa komponenty. Całość wygląda tak:

1) Poniższą zawartość zapisujemy np. do pliku error_pages/errors.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Error</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!--# if expr="$status = 502" -->
    <meta http-equiv="refresh" content="2">
    <!--# endif -->
  </head>
<body>
  <!--# if expr="$status = 502" -->
  <h1>We are updating our website</h1>
  <p>This is only for a few seconds, you will be redirected.</p>
  <!--# else -->
  <h1>
    <!--# echo var="status" default="" --> <!--# echo var="status_text" default="Something goes wrong..." -->
  </h1>
  <!--# endif -->
</body>
</html>

Możemy także uprościć jego strukturę:

<html>
<head>
<title>
<!--# echo var="status" default="" --> <!--# echo var="status_text" default="Something goes wrong..." -->
</title>
</head>
<body>
<center>
<h1>
<!--# echo var="status" default="" --> <!--# echo var="status_text" default="Something goes wrong..." -->
</h1>
</center>
</body>
</html>

2) Tworzymy mapę kodów błędów w kontekście http lub zapisując ją do pliku, np. conf/ssi-map.conf, który będzie trzeba dołączyć za pomocą dyrektywy include:

map $status $status_text {

  default 'Something is wrong';

  301 'Moved Permanently';
  400 'Bad Request';
  404 'Not Found';

}

3) Aktywujemy dyrektywę error_page:

server {

  ...

  error_page 301 400 401 /errors.html;

  location = /errors.html {

    ssi on;
    internal;
    root /usr/local/etc/nginx/error_pages;

  }

}

ngx_http_sub_module #

Wykorzystanie tego modułu jest bardzo proste:

# http, server, location
sub_filter '<hr><center>nginx</center>' '';
sub_filter_once on;

Dyrektywa sub_filter_once wskazuje, czy szukać każdego ciągu do zamiany raz, czy wielokrotnie.

ngx_http_substitutions_filter_module #

Ten moduł działa podobnie:

# http, server, location
subs_filter '<hr><center>nginx</center>' '';

Może on wykonywać zarówno wyrażenia regularne, jak i stałe podstawienia ciągów znaków w treściach odpowiedzi. Różni się od natywnego modułu (patrz wyżej), ponieważ parsuje bufor łańcuchów wyjściowych i dopasowuje ciąg znaków linia po linii.

replace-filter-nginx-module #

Moduł ten został napisany dla OpenResty i jego działanie polega na strumieniowym zastępowaniu wyrażeń regularnych (jednak nie korzysta z mechanizmów takich jak PCRE tylko z nowej biblioteki sregex) w treściach odpowiedzi w miarę możliwości z pominięciem buforowania (ang. non-buffered manner wherever possible).

# http, server, location, location if
replace_filter '<hr><center>nginx</center>' '';

Po osiągnięciu limitu bufora (domyślnie 8K) natychmiast przerwie przetwarzanie i pozostawi wszystkie pozostałe dane treści odpowiedzi nienaruszone.

LUA #

Podobne rzeczy można zrobić za pomocą języka LUA:

lua_need_request_body on;

location / {

  access_by_lua_block
  {
    ngx.req.read_body()
    local body = ngx.req.get_body_data()
    if body then
      body = ngx.re.gsub(body, "<hr><center>nginx</center>", "")
    end
    ngx.req.set_body_data(body)
  }

}

Ujawnianie sygnatur serwera #

Nagłówek Server zawiera informacje identyfikujące serwer i użyte w nim oprogramowanie. Wartość tego nagłówka jest np. używana do zbierania statystyk o serwerach HTTP przez takie serwisy jak Alexa czy Netcraft. Jednym z najłatwiejszych kroków zabezpieczenia serwera HTTP jest wyłączenie wyświetlania informacji o używanym oprogramowaniu i technologii za pośrednictwem tego nagłówka.

Istnieje kilka powodów, dla których rozgłaszanie wersji jest bardzo niepożądane. Jak już wspomniałem, atakujący zbiera wszystkie dostępne informacje o aplikacji i jej środowisku. Informacje o zastosowanych technologiach i wersjach oprogramowania są niezwykle cennymi informacjami.

Moim zdaniem nie ma żadnego racjonalnego powodu ani potrzeby pokazywania tak wielu informacji o twoim serwerze. Po wykryciu numeru wersji łatwo jest wyszukać określone luki w zabezpieczeniach. Co więcej, nie są to informacje kluczowe i niezbędne do poprawnego działania serwera lub aplikacji (w tym aplikacji zewnętrznych), więc zasadniczo jestem za ich usunięciem, jeśli można to osiągnąć przy minimalnym wysiłku.

Posiadanie danych na temat wykorzystywanych technologii i struktury aplikacji może znacznie ułatwić przeprowadzenie skutecznego ataku poprzez ukierunkowanie go na wykorzystanie znanych słabości w wykorzystywanym oprogramowaniu.

Wyłączenie wersji serwera można wykonać na kilka sposobów. Najbardziej wskazanym sposobem jest usunięcie tego nagłówka za pomocą modułu headers-more-nginx-module:

http {

  more_clear_headers 'Server';

  ...

Innym sposobem, wykorzystującym ten moduł, jest ustawienie własnej wartości tego nagłówka:

http {

  more_set_headers "Server: Unknown";

  ...

Do tego celu możesz wykorzystać także moduł lua-nginx-module:

http {

  header_filter_by_lua_block {
    ngx.header["Server"] = nil
  }

  ...