Dokumentacja

Dodaj nagłówki bezpieczeństwa

Dodaj nagłówki bezpieczeństwa

Kilka nagłówków odpowiedzi mówi przeglądarce, jak chronić Twoich odwiedzających: jakie skrypty może uruchomić, czy może zgadywać typ pliku, czy inna strona może załadować Twoją stronę w ramce i jak dużą część adresu przekazać dalej, gdy ktoś kliknie link. Bez nich jeden wstrzyknięty skrypt albo ukryta ramka na cudzej stronie (clickjacking) działa przeciwko Twoim odwiedzającym dokładnie tak, jak zaplanował atakujący.

Wysyłanie tych nagłówków nic nie kosztuje i nikt ich nie zauważa, dopóki jeden z nich nie zatrzyma ataku. Lista kontrolna strony sprawdza cztery podstawowe.

Czego szuka lista kontrolna

Nagłówek Bezpieczna wartość na start Co robi
Content-Security-Policy frame-ancestors 'self'; base-uri 'self'; object-src 'none' Ogranicza, co strona może ładować i kto może ją osadzić w ramce
X-Content-Type-Options nosniff Przeglądarka nie może traktować pliku tekstowego jak skryptu
X-Frame-Options SAMEORIGIN Tylko Twoja własna strona może pokazać ją w ramce
Referrer-Policy strict-origin-when-cross-origin Inne strony widzą tylko Twoją domenę, a nie pełny adres

X-Frame-Options liczy się jako obecny także wtedy, gdy Content-Security-Policy zawiera frame-ancestors, czyli jego nowoczesny zamiennik. Wysłanie obu nie szkodzi.

Zacznij od małego Content-Security-Policy

Pełny Content-Security-Policy wymienia każdą domenę, z której strona może ładować skrypty, style, czcionki i obrazy. Jeden błąd i przeglądarka po cichu blokuje Google Tag Manager, analitykę, baner cookies, czaty albo formularze płatności. Dlatego wartość w tabeli powyżej jest celowo mała: kontroluje ramki, znacznik <base> i stare wtyczki (<object>), a skryptów nie dotyka. Nie zepsuje GTM.

Jeśli chcesz polityki, która kontroluje też skrypty, zbuduj ją w dwóch krokach:

  1. Najpierw wysyłaj ją jako Content-Security-Policy-Report-Only, obok małego, egzekwowanego nagłówka. Przeglądarka nic nie blokuje, ale każde naruszenie pojawia się w konsoli deweloperskiej (F12 → Console).
  2. Przez kilka dni przeglądaj stronę, także koszyk, formularze i strony z osadzonymi treściami, i dopisuj do polityki każdą domenę, którą zobaczysz. Najtrudniejszy jest GTM: każdy tag dodany w GTM może ładować z nowej domeny, więc ścisłą politykę trzeba aktualizować przy każdym nowym tagu.

Dopiero potem zmień nazwę nagłówka na Content-Security-Policy. Lista kontrolna liczy tylko nagłówek egzekwowany; sam Report-Only zostaje żółty.

Ostrzeżenie: Nie kopiuj ścisłego CSP z innej strony. Jej lista dozwolonych domen nie jest Twoja, a pierwsze, co psuje, to zwykle analityka albo płatności, gdzie nikt nie zagląda, dopóki liczby nie spadną.

Sprawdź sam

Zamiast example.com wpisz swoją stronę:

curl -sI https://example.com | grep -iE '^(content-security-policy|x-content-type-options|x-frame-options|referrer-policy):'

Powinieneś zobaczyć wszystkie cztery linie (albo trzy, jeśli Content-Security-Policy zawiera frame-ancestors). Nagłówek, który pojawia się dwa razy z różnymi wartościami, zwykle oznacza, że jest ustawiony w dwóch miejscach, na przykład na serwerze i we wtyczce. Zostaw jeden.

Jak to naprawić

Wybierz miejsce, do którego masz dostęp. Nagłówki ustawione na serwerze WWW albo w CDN obejmują każdą stronę i każdy plik, także strony z cache, więc mają pierwszeństwo przed CMS-em.

WordPress

WordPress nie ma ustawienia dla tych nagłówków. Dodaj je za pomocą darmowej wtyczki Code Snippets (Snippets → Add New, uruchamiaj wszędzie) albo na końcu pliku functions.php motywu potomnego (child theme):

add_action('send_headers', function () {
    header("Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; object-src 'none'");
    header('X-Content-Type-Options: nosniff');
    header('X-Frame-Options: SAMEORIGIN');
    header('Referrer-Policy: strict-origin-when-cross-origin');
});

To działa tylko wtedy, gdy odpowiedź buduje sam WordPress. Wtyczki cache, takie jak WP Rocket czy LiteSpeed Cache, często serwują zapisaną kopię strony bez uruchamiania PHP, a ta kopia wychodzi bez nagłówków. Jeśli po wyczyszczeniu cache sprawdzenie nadal ich nie widzi, ustaw je na serwerze albo w Cloudflare (zobacz te zakładki).

cPanel

cPanel nie ma ekranu do nagłówków odpowiedzi. Dodaj je na początku pliku .htaccess w głównym folderze strony (File Manager, z włączonym Show Hidden Files):

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; object-src 'none'"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

Działa to na Apache i na LiteSpeed, który czyta .htaccess tak samo. Zapisz i uruchom sprawdzenie. Jeśli nic się nie zmieniło, hosting ma wyłączony mod_headers; poproś wsparcie o jego włączenie.

Plesk

Websites & Domains → example.com → Apache & nginx Settings, w Additional nginx directives dodaj:

add_header Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; object-src 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Kliknij OK; Plesk sprawdzi składnię i przeładuje nginx. Jeśli aplikacja na stronie (wtyczka WordPressa, framework) już wysyła któryś z tych nagłówków, odpowiedź będzie go zawierać dwa razy. Usuń go w jednym z tych miejsc.

nginx

Umieść nagłówki w bloku server { … } strony: na Debianie i Ubuntu w /etc/nginx/sites-available/example.com, na Rocky, Alma i CentOS w /etc/nginx/conf.d/example.com.conf:

server {
    # …
    add_header Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; object-src 'none'" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

always dodaje je także do stron błędów i przekierowań. Uważaj na jedną pułapkę nginx: blok location z własnym add_header (na przykład dla nagłówków cache) porzuca wszystkie nagłówki z poziomu server dla plików, które obejmuje. Powtórz w takim bloku cztery linie albo trzymaj je w osobnym pliku i dołącz go przez include w obu miejscach.

sudo nginx -t && sudo systemctl reload nginx

Apache i LiteSpeed

Z dostępem do serwera (Apache). Włącz mod_headers (na Debianie i Ubuntu: sudo a2enmod headers) i dodaj nagłówki do <VirtualHost *:443> strony: na Debianie i Ubuntu w /etc/apache2/sites-available/example.com-le-ssl.conf lub podobnym, na Rocky, Alma i CentOS w /etc/httpd/conf.d/:

Header always set Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; object-src 'none'"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
sudo apachectl configtest && sudo systemctl reload apache2

(Na Rocky, Alma i CentOS httpd zamiast apache2.)

Bez dostępu do serwera (hosting współdzielony, LiteSpeed). Wstaw te same cztery linie do .htaccess w głównym folderze strony, otoczone <IfModule mod_headers.c> … </IfModule> jak w zakładce cPanel.

Cloudflare

Jeśli strona działa za Cloudflare (pomarańczowa chmurka), Cloudflare może dodać nagłówki także na darmowym planie, dla każdej strony i pliku, niezależnie od tego, co wysyła serwer źródłowy:

  1. Otwórz domenę i przejdź do Rules → Transform Rules → Modify Response Header.
  2. Create rule, nazwij ją Security headers i zastosuj do All incoming requests.
  3. Dodaj cztery wpisy z Set static: nazwa nagłówka i wartość jak w tabeli powyżej (na przykład X-Content-Type-Options i nosniff), potem Deploy.

Cloudflare ma też zarządzaną transformację Add security headers. Ustawia ona X-Content-Type-Options, X-Frame-Options i Referrer-Policy, ale nie Content-Security-Policy, więc dla tego nagłówka reguła nadal jest potrzebna.

Node.js i Next.js

Next.js: w next.config.js:

const securityHeaders = [
    { key: 'Content-Security-Policy', value: "frame-ancestors 'self'; base-uri 'self'; object-src 'none'" },
    { key: 'X-Content-Type-Options', value: 'nosniff' },
    { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
    { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
];

module.exports = {
    async headers() {
        return [{ source: '/:path*', headers: securityHeaders }];
    },
};

Express: mały middleware przed Twoimi trasami:

app.use((req, res, next) => {
    res.set({
        'Content-Security-Policy': "frame-ancestors 'self'; base-uri 'self'; object-src 'none'",
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'SAMEORIGIN',
        'Referrer-Policy': 'strict-origin-when-cross-origin',
    });
    next();
});

Popularny pakiet helmet ustawia te i inne nagłówki jedną linią, ale jego domyślny Content-Security-Policy jest ścisły i blokuje skrypty z innych domen, w tym Google Tag Manager. Zanim na nim polegasz, skonfiguruj jego opcję contentSecurityPolicy.

Laravel

Utwórz middleware, na przykład app/Http/Middleware/SecurityHeaders.php:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('Content-Security-Policy', "frame-ancestors 'self'; base-uri 'self'; object-src 'none'");
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');

        return $response;
    }
}

Zarejestruj go dla każdego żądania w bootstrap/app.php (Laravel 11 i nowsze):

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})

Pliki w public/ (CSS, JavaScript, obrazy) serwuje serwer WWW bez Laravela, więc nie dostają tych nagłówków. Dla listy kontrolnej to w porządku: patrzy ona na stronę.

IIS i ASP.NET

W pliku web.config strony:

<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Content-Security-Policy" value="frame-ancestors 'self'; base-uri 'self'; object-src 'none'" />
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Jeśli aplikacja ustawia któryś z nich również w kodzie, IIS wyśle obie wartości. Zostaw jedną.

Platformy hostowane

Na Shopify, Wix, Squarespace i Webflow nagłówki odpowiedzi wysyła platforma i nie możesz dodać własnych. Jeśli lista kontrolna zgłasza tam brak nagłówka, po Twojej stronie nie ma nic do zmiany; pozycja zostaje ostrzeżeniem i na nic innego nie wpływa.

Gdy strona ma być osadzana w ramce

SAMEORIGIN i frame-ancestors 'self' nie pozwalają żadnej innej stronie pokazać Twojej strony w ramce. Jeśli strona partnera, widżet rezerwacji albo Twoja inna domena osadza ją celowo, wpisz ją: frame-ancestors 'self' https://example.org. Potem usuń X-Frame-Options: nie potrafi wymienić innych domen, a starsze przeglądarki, które czytają tylko jego, zablokowałyby osadzenie.

Potwierdź poprawkę

Uruchom ponownie polecenie curl z sekcji Sprawdź sam, następnie otwórz cel w PulseKeeper i kliknij Sprawdź teraz na zakładce Lista kontrolna. Pozycja zmieni się na OK w ciągu kilku minut, a nie dopiero przy następnym codziennym sprawdzeniu. Otwórz kilka stron w przeglądarce z otwartą konsolą (F12), aby upewnić się, że nic nie jest blokowane.