Reportování Content Security Policy (CSP) pomocí webového serveru Nginx

30. 11. 2022
Doba čtení: 7 minut

Sdílet

 Autor: Depositphotos
Content Security Policy (CSP) umožňuje uživatele chránit před celou řadou útoků skrze webové stránky. Jeho nasazení ale není triviální a může způsobovat chyby. Abyste o nich věděli, můžete je nechat logovat.

Stručně o CSP

Co se dozvíte v článku
  1. Stručně o CSP
  2. Když se objeví problémy
  3. Reportujeme
  4. Nejen CSP
  5. Přijímáme
  6. Nový log pro Nginx
  7. Cílový bod pro reporty
  8. Vyzkoušíme si to
  9. Odkazy

O HTTP hlavičce Content Security Policy (CSP) jsme na Rootu psali poprvé u před lety. Pokud vůbec netušíte, o co jde, přečtěte si náš předchozí obsáhlý článek. Ve stručnosti jde o standard, jehož úkolem je chránit uživatele před útoky, které jsou způsobeny vložením závadného kódu do jinak důvěryhodné stránky.

K němu může dojít například tím, že útočník nejprve zneužije slabinu v konkrétním webu a vloží do něj vlastní kód – ať už přímo do těla stránky nebo pomocí odkazu. Zneužít se k tomu dají například různé nedokonalosti ve formulářích, ale způsobů injektáže je mnoho. Takto přidaný kód je pak v každém případě spouštěn v kontextu původní stránky a může uživatele různými způsoby ohrožovat.

CSP nabízí standardní metodu, jakou může webový server v hlavičce HTTP odpovědi deklarovat, které zdroje je oprávněn prohlížeč na dané stránce načítat. To zahrnuje objekty nejrůznějšího druhu: obrázky, kaskádové styly, rámy, skripty, písma a další. Nebudeme tu vysvětlovat konkrétní mechanismy, pro ně je potřeba navštívit zmíněný článek nebo třeba dokumentaci u Mozilly.

Když se objeví problémy

Zejména na komplexních webech nemusí být nasazení CSP úplně jednoduché, je potřeba skutečně zahrnout všechny potenciální zdroje dat, ze kterých má být obsah načítán. Přitom ale zároveň nesmí tvůrce pomyslné dveře otevřít příliš, jinak CSP uživatele neochrání. V takové situaci může dojít k mnoha chybám, které povedou k zablokování načítaného legitimního obsahu. Tu se nespustí skript, tady se nenačtou obrázky a podobně.

V takové chvíli je potřeba o blokacích vědět, aby bylo možné zasáhnout a chybu napravit. Víme, že uživatelé trpí tiše a obvykle jim dělá problém napsat jednoduchý mail nebo vám o problému ve firemním systému říct prostě u oběda. Je tedy nutné mít automatický reportovací systém, který se o chybách dozví bez ohledu na uživatele.

Stejně tak chceme vědět o úspěšných útocích na náš web, které se nedostaly až k uživateli právě díky CSP. Tady opět sehraje roli reportovací systém, který je do moderních prohlížečů zabudován. Může upozornit správce na to, že se vyskytl závažný problém, který je potřeba vyřešit.

Reportujeme

Chyby způsobené překročením pravidel v CSP jsou vidět ve vývojářské konzoli uživatelova prohlížeče. Vy ovšem vidíte jen svůj prohlížeč a nemůžete se podívat vzdáleně na to, co uživatel na vašem webu provádí a kde se objevila chyba. Pro tento účel vznikla v CSP volba report-uri  a později samostatná hlavička report-to, které umožňují odesílat chyby na zvolený server.

V obou případech se jedná o jednoduché zprávy ve formátu JSON, které jsou pomocí metody HTTP POST odeslány na URL zvolenou v dané hlavičce. Starší varianta report-uri je přímo součástí hlavičky CSP a jedná se prostě o další z možných voleb v této hlavičce. Dokumentace uvádí následující příklad:

Content-Security-Policy: default-src https:; report-uri /csp-violation-report-endpoint/

Tento způsob reportování je dnes silně nedoporučován a zřejmě časem dojde k jeho odstranění z prohlížečů. Je možné jej používat kvůli zpětné kompatibilitě, ale pro nová nasazení nemá z dlouhodobého hlediska příliš smysl. Náhradou je samostatná hlavičky report-to, která je komplexnější a umožňuje nastavit více různých reportování. Příklad může vypadat takto:

Report-To: { "group": "default",
              "max_age": 10886400,
              "endpoints": [
                { "url": "https://example.com/csp-reports" }
              ] }

Content-Security-Policy: …; report-to csp-endpoint

Obě hlavičky mohou takto koexistovat, což se hodí kvůli zmíněné zpětné kompatibilitě. Pokud prohlížeč rozumí modernějšímu report-to, bude automaticky ignorovat starší  report-uri.

Nejen CSP

Prohlížeče umí odesílat informace také z dalších subsystémů, například relativní novinkou je Network Error Logging (NEL), který umí provozovatele webu informovat o tom, že se něco nepovedlo během komunikace se serverem. Pokrytá je celá řada oblastí od práce s DNS, přes správu certifikátů až po problémy s navázáním TCP. Podrobně všechny možnosti rozebírá na svém blogu Scott Helme.

Pokud už používáte hlavičku report-to, stačí přidat další hlavičku, která zapne reportování a použije přitom stejnou konfiguraci. Do stejného cíle pak budou putovat také informace o sítích. Pokud chcete oba typy reportů oddělit, vytvoříte si druhý samostatný cíl, na který odkážete jen z NEL. Hlavička bude vypadat třeba takto:

NEL: {"report_to": "default", "max_age": 2592000, "include_subdomains": true}

Přijímáme

Vložením výše zmíněné hlavičky zajistíme u uživatelů odesílání zpráv v případě chyby. Potřebujeme však také zajistit příjem a uložení na adrese, která je v hlavičce uvedena. Můžeme k tomu využít jednu z mnoha služeb, které jsou na internetu dostupné. Sice nás to bude stát nějaké peníze, ale vyřeší se tím pro nás vše od sběru dat, ukládání, grafování, odesílání zpráv o výskytech problémů a podobně.

Druhou variantou je napsat si vlastní řešení, které se nám bude o zmíněné reporty starat. Třetí variantou, kterou si ukážeme dále, je zpracování přijímaných zpráv přímo na webovém serveru. Ten už stejně používáte, HTTP z principu rozumí, takže nic nebrání v tom, abychom jej použili.

Konkrétní řešení si ukážeme na webovém serveru Nginx, kterému stačí jen upravit konfiguraci. Využijeme přitom jeho logovacích schopností, nadefinujeme si nový formát logu a při zavolání příslušné URL do něj prostě zalogujeme událost. Tím získáme přehled o tom, jestli a co náš web u uživatelů dělá.

Tip: Pokud nevíte, jak webový server Nginx funguje a chtěli byste se to naučit, můžete navštívit školení Webový server Nginx, které Root.cz pořádá.

Nový log pro Nginx

Nejprve musíme definovat nový formát logu. Nginx dovoluje vytvořit libovolný počet různých formátů, které si můžeme pojmenovat a poté je využívat v různých částech konfigurace. Vytvoření nové definice musí proběhnout v kontextu http, tedy ne uvnitř konkrétní definice serveru, ale o úroveň nad ní. Je pak společná pro všechny servery.

Logovací formát můžeme sestavit libovolně, ale my využijeme toho, že reporty k nám přicházejí ve validním formátu JSON. Uložíme si tedy celý logovací řádek tak, aby obsahoval další doplňující informace od webového serveru a jako celek byl zase platným JSONem.

Nejjednodušší je hodit konfiguraci logu do samostatného souboru v /etc/nginx/conf.d/csp.conf. Z tohoto adresáře Nginx načte všechny soubory tak, jak jdou za sebou. Do nového konfiguračního souboru zapíšeme následující řádek:

log_format CSP escape=json '{"date":"$time_local", "IP address":"$remote_addr", "http_x_forwarded_for":"$http_x_forwarded_for", "status":"$status", "http_user_agent":"$http_user_agent", "body_bytes_sent":"$body_bytes_sent", "request":"$request","request_body": "$request_body"}';

Vytvořili jsme tím nový typ logovacího záznamu zvaný CSP, pro který nám Nginx automaticky oescapuje znaky, které nejsou povolené ve formátu JSON. Dále jsme vyplnili samotný formát, kde používáme celou řadu interních proměnných od času, přes IP adresu, spoustu položek z hlavičky dotazu a nakonec také samotný obsah, který nám uživatel zašle. Takto bude řádek vložen do logu vždy pro každý požadavek.

Cílový bod pro reporty

Nyní zbývá konfigurace samotného webového serveru, aby nám pro určitou URL správně odpovídal a zároveň každý požadavek zalogoval v námi vytvořeném formátu. Do příslušné sekce server tedy vložíme novou location pro cílovou URL, kterou máme uvedenu v hlavičce.

Otevřeme tedy soubor s naším webem, který se nachází v /etc/nginx/sites-enabled/, a dovnitř konfigurace přidáme něco takového:

location /csp-reports/ {
              access_log /var/log/nginx/csp-reports.log CSP;
              proxy_pass https://www.example.com/_csp/;
      }

location /_csp/ {
              access_log off;
              return 204;
      }

Možná vám tahle konstrukce přijde podivná, ale má své opodstatnění. My totiž do logu potřebujeme vyplnit proměnnou $request_body, která obsahuje tělo požadavku zaslaného pomocí POST. Nginx ovšem tato data načítá jen v případě, že je bude potřebovat předat v roli proxy serveru. Musíme tedy udělat tuhle fintu a v konfiguraci předat požadavek sami sobě. Viz dokumentaci k této proměnné v Nginx. Místo example.com v příkladu samozřejmě vyplňte svou doménu.

ict ve školství 24

Vyzkoušíme si to

Zkoušku provedete jednoduše tak, že do stránky přidáte objekt, který podle CSP nesmí být načten. Pokud například povolujete jen obrázky ze stejné domény, přidáte do HTML jednoduše tag, který se pokusí obrázek stáhnout odjinud. Chyba se objeví v konzoli prohlížeče (stiskněte před načtením F12) a pokud jste vše udělali dobře, objeví se i v souboru  /var/log/nginx/csp-reports.log.

Pokud je chceme zobrazit pěkně naformátovaně, můžeme použít utilitu jq a nechat si filtrovat jen příslušnou část:

$ jq -r '.request_body | fromjson' < csp-reports.log
[
  {
    "age": 30988,
    "body": {
      "blockedURL": "https://i.iinfo.cz/images/537/film.jpg",
      "disposition": "enforce",
      "documentURL": "https://www.example.com/",
      "effectiveDirective": "img-src",
      "lineNumber": 125,
      "originalPolicy": "upgrade-insecure-requests;\tdefault-src 'self'; script-src 'self' https://*.googletagmanager.com; script-src-elem 'self' https://*.googletagmanager.com https://*.google-analytics.com 'sha256-ot7YyssQ15DKMe7LjktD4B0qetrF9cz7c3dE5jl8i4Y='; style-src 'self' https://fonts.googleapis.com; style-src-attr 'unsafe-inline'; img-src 'self' data: https://*.google-analytics.com; font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; frame-ancestors 'none'; base-uri 'none'; object-src 'none'; connect-src https://*.google-analytics.com; report-to default",
      "referrer": "",
      "sample": "",
      "sourceFile": "https://www.example.com/",
      "statusCode": 200
    },
    "type": "csp-violation",
    "url": "https://www.example.com/",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
  }
]

Odkazy

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.