Jak jednoduše na CSRF

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
crempa
Člen | 198
+
0
-

Vzhledem k tomu, ze je ochrana proti CSRF integrovana pouze do formularu a nekdy neni uplne pohodlne resit veskere „invazivni“ operace timto zpusobem tak nadhazuju jedoduche reseni co jsem momentalne vyzkousel.

Princip je opet zalozen na generovanem tokenu, ktery se v ramci sezeni nemeni (a je tak mozne pouzivat i vice zalozek naraz). Pro jednoduchost ukladam token do Identity, generuje se tedy po prihlaseni:

<?php
//trida implementujici IAuthenticator
//...kontrola, ziskani dat do $row atd.

$row['csrfToken'] = md5(microtime().rand(0,999));  //pridame token
return new Identity($row['logname'], $roles, $row); //vytvorime novou identitu

?>

rekneme ze pak mam klasicky odkaz vyvolavajici smazani zaznamu, jako druhy parametr se tedy prida kontrolni token, co se nastavi v presenteru…

<?php
 //...telo presenteru, napr. metoda renderDefault()
 $template->csrfToken = Environment::getUser()->getIdentity()->csrfToken;
 //...
?>

..v sablone je pak tedy neco jako

{*druhy parametr definuje kontrolni token*}
{plink del! $tableRow['id'], $csrfToken}

no a nakonec je nutne zkontrolovat spravnost parametru

<?php

  public function handleDel($delId, $token)
  {
    if($token != Environment::getUser()->getIdentity()->csrf_token)
      $this->terminate(); //nebo neco peknejsiho, jako redirect s flash zpravou a tak

    //..pokracujeme v operaci mazani

  }

?>

je to jen nastrel, ale zda se celkem funkcni..

No a jeste navrh na feature request a to co resit totok uz v ramci generovani odkazu (tj. abych nemusel pridavat a kontrolovat token rucne )? Co treba pouzit nejaky prefix (podobne jako u generovani absolutnich URL apod.) v link metodach a token pro porovnani ukladat do session…?

Jan Tvrdík
Nette guru | 2595
+
0
-

co resit totok uz v ramci generovani odkazu

Co třeba napsat si vlastni macro pro CurlyBracketsFilter třeba safeLink a pak budeš psát jen {safeLink del! $tableRow['id']}.

Editoval Jan Tvrdík (4. 4. 2009 14:07)

Panda
Člen | 569
+
0
-

Už jsem něco podobného řešil a nebavilo mě neustále opakovat vkládání tokenů k odkazům a jejich kontrolu, tak jsem si to zautomatizoval:

<?php
abstract class AuthorizedPresenter extends BasePresenter
{
    const CRSF_PROTECTION_MESSAGE = 'Neplatný ověřovací klíč nebo vypršela platnost sezení. Zkuste požadavek opakovat.';

    /*** Signal protection ***/

    public function link($destination, $args = array())
    {
        if (!is_array($args)) {
            $args = func_get_args();
            array_shift($args);
        }

        $a = strpos($destination, '#');
        if ($a === FALSE) {
            $fragment = '';
        } else {
            $fragment = substr($destination, $a);
            $destination = substr($destination, 0, $a);
        }

        if (substr($destination, -1) === '!' && strpos($destination, '-') === false) {
            static $session;
            if ($session === null) {
                $session = Environment::getSession($this->getName());
            }

            $field = 'signal' . rtrim($destination, '!');
            if (!isset($session->$field))
                $session->$field = $token = base_convert(md5(uniqid($field, true)), 16, 36);
            else
                $token = $session->$field;

            $args['token'] = $token;
        }

        return parent::link($destination . $fragment, $args);
    }

    public function signalReceived($signal)
    {
        static $session;
        if ($session === null) {
            $session = Environment::getSession($this->getName());
        }

        $field = 'signal' . $signal;
        if (!isset($this->params['token']) || !isset($session->$field) || $session->$field != $this->params['token'])
            throw new UnauthorizedRequestException(self::CRSF_PROTECTION_MESSAGE);
        unset($session->$field, $this->params['token']);

        parent::signalReceived($signal);
    }
}
?>

Toto řešení pro každý signál presenteru automaticky vygeneruje vlastní token a pak i automaticky provede kontrolu. Neměl by být problém kód upravit podle vlastního gusta…

David Grudl
Nette Core | 8228
+
0
-

Jen pozor na to, že po každé takové operaci musí dojít k přesměrování, jinak se může token prozradit při přechodu mimo server v refereru.

Tohle zautomatizovat mám v plánu, původně k tomu měla sloužit anotace u metody action- nebo handle-, teď spíš uvažuju nad konvencí podle názvu parametru. Takže pokud bude existovat něco jako

public function handleDel($delId, $_token)

tak předání a kontrola tokenu se uskuteční automaticky. Ale… anotace by byly vhodnější.

phx
Člen | 651
+
0
-

Jen tak mimo. Tento utok predpoklada, ze nekdo na vse stranky umisti nejaky kod, ktery mu zaloguje cookies (SESSSIONID). Nebo se pletu? Jak jinak lze ona cookies ukrast???

Zadny jiny zpusob krome napadeneho PC klienta me nenapada. Coz me privadi na otazku. Proc resit dusledky kdyz muzeme zabezpecit pricinu (infikovani webu?)

R2D2
Člen | 22
+
0
-

Tento typ útoku se dá provést například tak, že člověku jakkoliv (například v mailu) pošlu odkaz na svoji stránku, ve stylu http://mojestranka.cz/coolobrazek.jpg

Obrázek sic .jpg bude ve skutečnosti html stránka (mod_rewrite), kde zobrazíš správně obrázek, ať to nějak vypadá, ale taky schovaný frame s javascriptovým kódem, který daný frame přesměruje (případně odešle formulář) na patřičnou adresu serveru na který útočíš. Požadavek provádí klient ač nedobrovolně a imho nemáš jak to ze své strany pohlídat, prostě přijde a se správnou cookie.

Automatická kontrola tokenu bude fajn, ať už jakkoliv :)

phx
Člen | 651
+
0
-

Aha uz to chapu. Cilem je donutit prohlizec na pozadi poslat nejaky mnou specifikovany pozadavek na cizi server. Coz predpoklada ze na serveru budu prihlasen a zaroven kliknu na onen coolobrazek.

At premyslim jak premyslim, tak jedine pres ony tokeny. Ale token musi byt vygenerovan az po platnem prihlaseni a muzu byt ulozen pouze v session a v URL jako GET parametr. Navic nesmi existovat stranka ktera by byla zobrazitelna bez tokenu.

Pokud by neco z toho bylo poruseno tak onen script muze token ziskat.

pmg
Člen | 372
+
0
-

Perhaps?

$request = $this->getHttpRequest();
$uri = $request->getUri();
$referer = $request->getReferer();

if ($referer === NULL || $referer->host !== $uri->host) {
	throw new BadSignalException('CSRF protection failed.');
}
crempa
Člen | 198
+
0
-

To je ten nejjednodussi zpusob, do nejakeho intranetu nebo prostredi, kde muzes kontrolovat zasilani refereru proc ne, ale do masovejsi aplikace toto nasadit nelze, protoze tak odstrihnes lidi co jim referer pozira treba firewall apod.

R2D2
Člen | 22
+
0
-

pmg napsal(a):

Perhaps?

$request = $this->getHttpRequest();
$uri = $request->getUri();
$referer = $request->getReferer();

if ($referer === NULL || $referer->host !== $uri->host) {
	throw new BadSignalException('CSRF protection failed.');
}

Na referera spoléhat nemůžeš, jednak občas bývá z bezpečnostních důvodů zakázán (tj těmto lidem by tebou navrhovaný kód paradoxně bezpečnost snížil), druhak jde podvrhnout.

phx napsal(a):

At premyslim jak premyslim, tak jedine pres ony tokeny. Ale token musi byt vygenerovan az po platnem prihlaseni a muzu byt ulozen pouze v session a v URL jako GET parametr. Navic nesmi existovat stranka ktera by byla zobrazitelna bez tokenu.

Pokud by neco z toho bylo poruseno tak onen script muze token ziskat.

podle mě to není nutné, javascriptem nejde manipulovat s dokumentem z jiné domény – mohu ho načíst do framu, ale už pak nepřistoupím k jeho obsahu? Krk bych za to nedal, mohla mi nějaká finta uniknout :) Ale nenapadá mě jak ten token získat.

Ale jak psal David, měl by po každém takovém requestu s tokenem následovat redirect na stránku bez tokenu v url, jinak může dojít k vyzrazení v refereru při přechodu na jinou stránku.

pmg
Člen | 372
+
0
-

crempa napsal(a):

ale do masovejsi aplikace toto nasadit nelze, protoze tak odstrihnes lidi co jim referer pozira treba firewall apod.

Jo, je to spíš zástupné řešení, ale měl bys nějakou statistiku, kolik takových lidí je?

R2D2 napsal(a):

Na referera spoléhat nemůžeš, jednak občas bývá z bezpečnostních důvodů zakázán (tj těmto lidem by tebou navrhovaný kód paradoxně bezpečnost snížil),

Možná by se ta podmínka dala napsat if ($referer !== NULL && $referer->host !== $uri->host), aby to takové lidi neomezovalo, ale bezpečnost by to tak jako tak (oproti žádné ochraně) snížit nemělo.

druhak jde podvrhnout.

Nevím, možná spuštěním cizího javaskriptu na serveru?

Tomik
Nette Evangelist | 485
+
0
-

pmg napsal(a):

druhak jde podvrhnout.

Nevím, možná spuštěním cizího javaskriptu na serveru?

Ano, dá se pomocí spec. rozšíření podvrhnout a to poměrně jednoduše. Např. https://addons.mozilla.org/…ox/addon/953 … :)

R2D2
Člen | 22
+
0
-

což ovšem k útoku až tolik využít nejde, ale třeba byla chyba ve flash playeru, která umožňovala až do verze 9.0??? (nevim přesně) dělat požadavky s libovolným refererem, což zase šlo zneužít stejně jako v příkladu výše, jen místo javascriptu byl použit flash :)

pmg
Člen | 372
+
0
-

Díky, budu si to pamatovat. (-: