logo
31.07.2020 07:12
1
Dobrý den,

snažil jsem se si něco o CSRF přešíst, vše, co mi google nabídl, jsou články 2008 - 2012. Něajký pořádný článek 19/20 jsem nenašel a tak se ptám. Ošetřujete své formuláře tokenama? Jak? Má to smysl? Ptám se, protože doba významně pokročila a třeba už dneska běžné CSRF útoky nefungují díky PHP 7 (používám php 7.3) nebo se to ošetřuje na straně nastavení webhostingů či prohlížečů. Opravdu o tomto tématu moc nevím. Se session pracuji na denní bázi a budu rád, když mi řeknete, jak so ošetřujete vstupní formuláře (login hlavně) vy a zda-li bych se měl CSRF zabývat více také či se už není čeho bát :)

Děkuji

Co se právě děje na Webtrhu?

31.07.2020 07:20
2
Vetsina asi pouziva frameworky. Napr. ja delam svoje projekty v Symfony a tam CSRF jede.
31.07.2020 07:42
3
CSRF je nutné používat, každý to bere asi už jako samozřejmost, frameworky to mají jako nativní součást. PHP 7 nic samo neošetřuje, nemá jak.
31.07.2020 07:53
4
Urcite stale aktualni a nutno osetrit. Jak jsi prisel na to, ze si s tim php7 samo poradi?
31.07.2020 09:07
5
Ano, a moderní frameworku to řeší automaticky ( např nette, symfony)
31.07.2020 09:12
6
CSRF řeší CORS. To je řešení na straně prohlížeče, lze obejít. Pozor na technologie jako jsou Websockets, na ty se CORS nevztahuje.

Jinými slovy, stále je třeba používat token.
31.07.2020 10:03
7
Zdravím,

děkuji za zpětné vazby. Na něco málo jsem se koukal. Našel jsem velice jednoduché řešení, ALE....!

Stačí, když v login.php si na začátku dokumentu vytvořil token, pro příklad uvádím toto:
Kód:
$_SESSION['token'] = bin2hex(random_bytes(24));
a tento token si dal do hidden pole ve formuláři, po odeslání formuláře si v souboru, kde zpracovávám formulář napsal něco ve smyslu tohoto...
Kód:
if (hash_equals($_SESSION['token'], $_POST['token'])) {
  // je platný
} else {
  // není platný
}
a tím je hotovo? Vskutku je to tak jednoduché nebo v tomto vidíte problém? Děkuji pěkně za doporučení, rady, tipy.

M.
31.07.2020 11:14
8
Jdeš správným směrem. Neměl bys používat stejný pro každý formulář v rámci sezení, ale generovat nový při každém použití formuláře, jinak to není dostatečně účinné.

random_bytes je zbytečně drahá funkce, může to vést k DoS útokům na tvůj web jen tím, že se bude načítat stránka s formulářem. Doporučuji použít raději openssl_random_pseudo_bytes(24, false), která používá neblokový /dev/urandom (vždy dostaneš nějaká data bez ohledu na množství entropie v OS, není vhodné pro generování klíčů, ale u tokenů, kde jejich predikovatelnost není tak velká zranitelnost to je přijatelný kompromis). hash_equals je funkce s konstantním čase porovnávání (tj. pomalá), která se používá v kryptografii, aby útočník nemohl odhadnout, jak je blízko správné variantě, u porovnávání tokenů, které jsou jednorázové to nedává smysl, naopak lepší je to řešit nějakým QoS.

Token bys měl vztahovat k action adrese formuláře (pokud je uživatel přihlášen nebo má session, tak ještě k uživateli/session) a nedovolit, aby jeden token mohl být použit vícekrát (při první ověření ho musíš smazat) nebo aby se dal použít pro jiný formulář. Stejně tak bys měl učinit rozhodnutí, jestli chceš kvůli formulářům generovat session (cookies a soubor na disku) nebo CSRF uděláš přes databázi. Na některých webech může být session zbytečná brzda a luxus.

Lehká varianta by mohla vypadat nějak takhle, ber to jako inspriaci a ne produkční kód. Osobně bych tam třeba řešil i promazání starých tokenů, abych jich neměl uložen příliš a nebyl to další vektor útoků - zaplnit session storage.

PHP kód:
$form_url "/login.php";
if (!isset(
$_SESSION['tokens'])) $_SESSION['tokens'] = array();
if (!
sset($_SESSION['tokens'][$form_url])) $_SESSION['tokens'][$form_url] = array();

// login.php - render formu
$token bin2hex(openssl_random_pseudo_bytes(24false));
$_SESSION['tokens'][$form_url][] = $token

// login.php - process form
$valid_token false;
if (isset(
$_POST['token'])) {
  foreach(
$_SESSION['tokens'][$form_url] as $i => $token) {
    if (
strcmp($token$_POST['token'])) {
      
$valid_token true;
      unset(
$_SESSION['tokens'][$form_url][$i]);
    }
  }
}

// zpracovani formure podle #vaid_token 
31.07.2020 11:50
9
Původně odeslal josef.jebavy
Ano, a moderní frameworku to řeší automaticky ( např nette, symfony)
nette to praveze automaticky neriesi, je nutne csrf zapinat pre kazdy formular rucne (aspon to tak bolo)

... berem spat, vidim, ze uz to konecne prerobili
31.07.2020 11:55
10
Původně odeslal ne
nette to praveze automaticky neriesi, je nutne csrf zapinat pre kazdy formular rucne (aspon to tak bolo)
To neplatí od myslím Nette 3.0, kdy je CSRF zapnutý automaticky a je možné ho volitelně deaktivovat, viz aktuální dokumentace https://doc.nette.org/cs/3.0/vulnerability-protection. Stejně tak to je automaticky zapnuté u Symfony i Laravel. Všichni asi usoudili, že bezpečnost by měla být ve výchozím stavu zapnutý a vypínat jí, když vím proč a nikoliv naopak.
31.07.2020 12:03
11
asi najjednoduchsie riesenie je vygenerovat token, symetricky zasifrovat, ulozit ho do cookies zo samesite Strict ..

pri generovani form-u zobrat tuto cookie, desifrovat a jej obsah pouzit ako token (pripadne posolit, zahashovat)..

po odoslani formu overit prichadzajuci token s tokenom v cookies..

netreba potom ziadne session, databazu a pod... v principe je to neprestrelne

---------- Příspěvek doplněn 31.07.2020 v 12:06 ----------

Původně odeslal TomášX
To neplatí od myslím Nette 3.0, kdy je CSRF zapnutý automaticky a je možné ho volitelně deaktivovat, viz aktuální dokumentace https://doc.nette.org/cs/3.0/vulnerability-protection. Stejně tak to je automaticky zapnuté u Symfony i Laravel. Všichni asi usoudili, že bezpečnost by měla být ve výchozím stavu zapnutý a vypínat jí, když vím proč a nikoliv naopak.
ano, z pohladu bezpecnosti je lepsie, ak ma formular automaticku ochranu, ktoru zabudnem vypnut, ako vynutenu, ktoru zabudnem zapnut.. je fajn, ze uz to upravili

---------- Příspěvek doplněn 31.07.2020 v 12:12 ----------

Původně odeslal TomášX
Jdeš správným směrem. Neměl bys používat stejný pro každý formulář v rámci sezení, ale generovat nový při každém použití formuláře, jinak to není dostatečně účinné.

....

Token bys měl vztahovat k action adrese formuláře (pokud je uživatel přihlášen nebo má session, tak ještě k uživateli/session) a nedovolit, aby jeden token mohl být použit vícekrát (při první ověření ho musíš smazat) nebo aby se dal použít pro jiný formulář.
v tomto pripade ale musis dbat nato, aby kazdy vygenerovany token bol platny a zneplatnit iba ten pouzity.. a to kvoli moznosti, ze uzivatel bude mat formular otvoreny viac krat (vo viac taboch) ... tu ale potom vidim problem v riziku velkeho mnozstva vygenerovanych tokenov, ktore sa nasledne nepouziju
31.07.2020 12:17
12
Původně odeslal ne
asi najjednoduchsie riesenie je vygenerovat token, symetricky zasifrovat, ulozit ho do cookies zo samesite Strict ..

pri generovani form-u zobrat tuto cookie, desifrovat a jej obsah pouzit ako token (pripadne posolit, zahashovat)..

po odoslani formu overit prichadzajuci token s tokenom v cookies..

netreba potom ziadne session, databazu a pod... v principe je to neprestrelne
hashovat nebo jinak zamaskovat původní hodnotu, kterou mám u sebe je samozřejmě dobré, při ukládání do databáze to nemá smysl řešit jinak. U cookies je pak nezbytné nastavit SameSite=Strict nebo aspoň lax flag, jinak je obrana nedostatečná. Při šifrování token do cookies je potřeba správně zvolit nonce a sůl, aby se zabránilo replay útoku, což je největší slabina tohoto algoritmu, dělá se v tom příliš chyb a pak je obrana neúspěšná.

Nechtěl jsem do tohoto způsobu řešení CSRF zabředávat, přináší to mnohem více pastí než pamatování tokenů a jejich validace. Při revizích zabezpečení webových aplikací je právě při použití encryption nebo HMAC nejvíce nalezených chyb (české prostředí enterprise aplikací). Dokonce z dnešního pohledu ani ukázková poslední stabilní implementace od OWASP (CSRFGuard) v javě není dobře udělaná.
31.07.2020 12:21
13
Původně odeslal ne
v tomto pripade ale musis dbat nato, aby kazdy vygenerovany token bol platny a zneplatnit iba ten pouzity.. a to kvoli moznosti, ze uzivatel bude mat formular otvoreny viac krat (vo viac taboch) ... tu ale potom vidim problem v riziku velkeho mnozstva vygenerovanych tokenov, ktore sa nasledne nepouziju
Jednorázové použití v ukázkovém kódu řeším, přetečení uložiště tokenů jen zmiňuji. Proto je lepší použít databázi, kde ta kapacita je vyšší a je více času promazání. U session je musí omezovat počet, případně doplnit timestamp a dělat retenci při každém přidání nového.

V produkčních aplikacích nasazujeme třeba Redis či jinou databázi, která má TTL a tokeny se generují jen s pár minutovou platností a pak se promazávají. Jeden redis cluster pak dává kapacitu i 100m tokenů za hodinu.
31.07.2020 12:27
14
pri samesite Strict aplikacia nedostane cookie s tokenom, neni co s cim porovnat, formular je neplatny

bez samesite ho sice dostane, ale utocnik by musel poslat spravny token, co pri nejakej rozumnej dlzke je nerealne

samozrejme ale za podmienky, ze token v cookies neunikne cez XSS (httponly) a pod.. ak je spravne zasifrovany, tak v podstate ani unik nevadi (berem spat - toto by vlastne vadilo :D )

netvrdim, ze je tento sposob dokonaly (to urcite nie), ale pokryje potrebu 99% webov..

---------- Příspěvek doplněn 31.07.2020 v 12:34 ----------

Původně odeslal Michael91
... Se session pracuji na denní bázi a budu rád, když mi řeknete, jak so ošetřujete vstupní formuláře (login hlavně) vy a zda-li bych se měl CSRF zabývat více také či se už není čeho bát :)

Děkuji
login nebyva predmetom utoku csrf.. podstata csrf spociva v nevedomom vykonavani akcii uz prihlaseneho uzivatela, ktorych o tychto akciach netusi, z cudzich stranok.. napr. odoslanie penazi, zmena uctu, udajov a pod...

samozrejme aj akcie neprihlasenych uzivatelov (fake nakupy v konkurencom eshope a pod)
31.07.2020 13:11
15
Ano, Samesite=strict znamená, že formulář musím mít na stejné stránce jako action pro něj, např. login.php musí zajišťovat oboje (vykreslení i zpracování).

Útočník může vzít token z formuláře a cookies se zašifrovaným tokenem (token lze získat třeba přes XSS zranitelnost, reklamní banery) a provádět tzv. replay attack, o tom jsem mluvil, bránit se tomu dát tak, že token šifruji se solí, která pochází ze session uživatele a k tomu tam je třeba časový údaj. Pokud dovolím vícekrát použít stejný token, pořád je možný útok na jakémkoliv cizím webu ve stylu <img src=https://ciziweb.cz/posli_formuar?token=9db1cb88c37ce... />, prohlížeč pošle odpovídající cookies s token, které má uložené, stačí aby návštěvník již na tom webu před tím byl.

Správně by token tedy měl být nepředvídatelný, použitelný pouze jednou a pro konkrétní formulář (ideálně svázaný i s readonly hodnotami), jinak jeho funkce není dostatečná. Webová aplikace by také měla bránit opakovaným (třeba 100+) pokusům o poslání formuláře s neplatným tokenem.
31.07.2020 13:28
16
Původně odeslal TomášX
... Útočník může vzít token z formuláře a cookies se zašifrovaným tokenem (token lze získat třeba přes XSS zranitelnost, reklamní banery) ...
ziskanie tokenu cez XSS je ale chybou fatalnou.. pokial by utocnik vedel ziskat cookies, tak sa na csrf vykasle a rovno pouzije session cookie na ziskanie identity a vykonavanie zelanych akcii

pokial je na webe XSS diera, tak vsetky obrany idu do haja a nemopoze nic.. v momente utoku si cez XSS vytiahnes session cookie obete, s cookie zavolas stranku s formom a mas token na zlatom podnose.. ale ako pisem, je to zbytocne, kedze mam session cookie
31.07.2020 14:08
17
I reklamní baner má často přístup na stránku a ne všechny reklamy jsou řádně kontrolovány, viz i zde, zranitelný může být plugin v prohlížeči atd. Stejně tak si útočník může stránku otevřít sám, uložit si cookies a token a opakovaně posílat formulář, to je také případ, kterému má CSRF bránit, ač původně kvůli tomu nevznikl. XSS i CSRF mají společné to, že se jedná o zranitelnosti, které jsou vykonány na straně klienta, kde mohu očekávat různě dobře či špatně konfigurované prostředí.

Proto je důležité, aby CSRF token měl jednorázovou platnost a byl omezen časem, tím se brání replay či delay útokům. V logách aplikace bych pak měl být schopný svázat konkrétní vygenerovaný formulář s jeho konkrétním a jediným odesláním, tím dávám zodpovědnost aplikaci, aby měla kontrolu nad daty, které dostane do formuláře.

Varianta, kterou jsi navrhl s tokenem a jeho šifrováním do cookies není obecně špatná, jen jsem chtěl upozornit, že v takovéhle jednoduché podobně nebrání všemu, nic víc nic mín. Ani můj příklad, který jsem uvedl není dokonalý. Správně implementovat CSRF není snadné a dá to dost práce. Pokud se dělají javascriptové klientské aplikace, ve velkém se právě používá šifrování tokenu a poslání přes cookies nebo přes vlastní http hlavičku. Klíč pro šifrování je pak součástí klientské aplikace a pravidelně se refreshuje, aby se snížilo okno pro únik a tím vektor útoku.
31.07.2020 14:10
18
Původně odeslal TomášX
Jdeš správným směrem. Neměl bys používat stejný pro každý formulář v rámci sezení, ale generovat nový při každém použití formuláře, jinak to není dostatečně účinné.

random_bytes je zbytečně drahá funkce, může to vést k DoS útokům na tvůj web jen tím, že se bude načítat stránka s formulářem. Doporučuji použít raději openssl_random_pseudo_bytes(24, false), která používá neblokový /dev/urandom (vždy dostaneš nějaká data bez ohledu na množství entropie v OS, není vhodné pro generování klíčů, ale u tokenů, kde jejich predikovatelnost není tak velká zranitelnost to je přijatelný kompromis). hash_equals je funkce s konstantním čase porovnávání (tj. pomalá), která se používá v kryptografii, aby útočník nemohl odhadnout, jak je blízko správné variantě, u porovnávání tokenů, které jsou jednorázové to nedává smysl, naopak lepší je to řešit nějakým QoS.

Token bys měl vztahovat k action adrese formuláře (pokud je uživatel přihlášen nebo má session, tak ještě k uživateli/session) a nedovolit, aby jeden token mohl být použit vícekrát (při první ověření ho musíš smazat) nebo aby se dal použít pro jiný formulář. Stejně tak bys měl učinit rozhodnutí, jestli chceš kvůli formulářům generovat session (cookies a soubor na disku) nebo CSRF uděláš přes databázi. Na některých webech může být session zbytečná brzda a luxus.

Lehká varianta by mohla vypadat nějak takhle, ber to jako inspriaci a ne produkční kód. Osobně bych tam třeba řešil i promazání starých tokenů, abych jich neměl uložen příliš a nebyl to další vektor útoků - zaplnit session storage.

PHP kód:
$form_url "/login.php";
if (!isset(
$_SESSION['tokens'])) $_SESSION['tokens'] = array();
if (!
sset($_SESSION['tokens'][$form_url])) $_SESSION['tokens'][$form_url] = array();

// login.php - render formu
$token bin2hex(openssl_random_pseudo_bytes(24false));
$_SESSION['tokens'][$form_url][] = $token

// login.php - process form
$valid_token false;
if (isset(
$_POST['token'])) {
  foreach(
$_SESSION['tokens'][$form_url] as $i => $token) {
    if (
strcmp($token$_POST['token'])) {
      
$valid_token true;
      unset(
$_SESSION['tokens'][$form_url][$i]);
    }
  }
}

// zpracovani formure podle #vaid_token 
Jste úžasný, děkuji za Váš čas :)

Všem ostatním taky!!!!

Abych se přiznal, učím se v Laravelu, ale zatím nejsem v takové pozici, abych si troufl své projekty překopat právě do něj :) proto u současných projektů musím zajistit takovou aktualizaci, aby odpovídala dnešním bezpečnostním standartům a SVÉPOMOCÍ. A to musím udělat tak, jak to zatím umím, ručně psané php.
01.08.2020 09:11
19
Původně odeslal TomášX
... Varianta, kterou jsi navrhl s tokenem a jeho šifrováním do cookies není obecně špatná, jen jsem chtěl upozornit, že v takovéhle jednoduché podobně nebrání všemu, nic víc nic mín. Ani můj příklad, který jsem uvedl není dokonalý ....
tak tak.. bohuzial ziadna ochrana nie je 100% a v podstate vsade sa da najst nejaka slabina ktora sa da zneuzit. Cim zlozitejsia aplikacia, tym viac chyb
01.08.2020 10:39
20
Původně odeslal ne
tak tak.. bohuzial ziadna ochrana nie je 100% a v podstate vsade sa da najst nejaka slabina ktora sa da zneuzit. Cim zlozitejsia aplikacia, tym viac chyb
Jsem zastáncem toho, že bych měl bránit všem známým variantám a ty neznámé být schopný aspoň auditovat a minimalizovat jejich dopady.

Proto zabezpečení aplikací (a SW) obecně je vrstvené, rozdělené do perimetrů, které mají každý svoji roli. CSRF a XSS patří do zranitelností na klientské straně, kdy klient se vmanipuluje do chování, které není běžné a ani cílené. V žádném případě nezabezpečují samotný SW a ani by to tak nemělo být, samotný SW by měl být bezpečný i bez nich, ale s nimi ho není možné zneužít na straně klienta k jinému účelu. Např. zde by CSRF nemělo být jedinou obranou proti robotickému zneužití formuláře, ale samotný formulář by měl mít vlastní QoS (či rate limiting, block list).

Nepletl bych dohromady složitost aplikace (byznysovou) a její vnitřní členění, jedno je možné od druhého oddělit a mít byznysově složitou aplikaci, která má jasnou strukturu, vnitřní logiku a z toho plynoucí bezpečnost.

Wordpress a jiné redakční systémy jsou úspěšným příkladem toho, jak se aplikace z pohledu zabezpečení nemá strukturovat. Vertikální integrace vrstev je dnes přežitek. Naopak třeba linuxový kernel je ukázkovým příkladem jak byznysově složitou aplikaci je možné dělit do horizontálních perimetrů a zachovat zřetelnou strukturu a jednoduché členění.

Nepřu se s tebou, máš v tom také pravdu a nemám pocit, že bychom si v tom nerozuměli, jen to chci vysvětlit pro jiné čtenáře, když už se tady to téma objevilo.
01.08.2020 16:05
21
suhlasim..

len dodam, ze je az s podivom, kolko webov, a to aj weby ktore pracuju s peniazmi, kreditom a pod. nema csrf vobec poriesene..