Accendo > Publikované > ASPX Úvod do ochrany – 02 Form Security

Autor:                  Libor Bešenyi

Dátum:                2012/07/18

Zmena:                                2012/07/18

Form Security (úvod + tipy)

V predchádzajúcej časti sme si povedali niečo o session security. Do aplikácie musíme pridať vrstvu, ktorá bude riadiť bezpečnostnú vrstvu. Aj keď to riešenie nie je zlé, ukážeme si obrátene riešenie, ktoré je bezpečné no extrémne škálovateľné.

V prípade session security server niesol informáciu o tom, kto je prihlásený a klient (prehliadač) pri requestovaní na server odovzdal kľúč, na základe ktorého aplikácia mohla uznať, či daný užívateľ (nie)je prihlásený. Teda aj keď sa informácia nachádzala na serveri, prehliadač musel niesť informáciu, kde presne na serveri táto informácia uložená je. Na to sa použilo cookies.

Použitie vety „aplikácia mohla uznať“ bolo úmyselné. Ako vieme, Aspx aplikácia nie je EXE súbor. Je to DLL, ktoré si vie nalinkovať iná aplikácia – webový server (IIS). IIS dokáže fungovať aj bez ASPX aplikácie – stačí nakonfigurovať virtuálny adresár (stránku) a nakopírovať nejaké HTML súbory.

IIS je teda zjednodušene nižšia vrstva, ktorá sa stará o to, že ak dostane z prehliadača request napr. na stránku HTML – vracia celý súbor. Ak to je ale napr. ASPX (alebo PHP) – web server požiada iný proces, aby daný súbor spracoval a výstup v podobe Html vracia podobne ako pri čistom Html súbore.

Do hry teda vstupuje nejaký „parser“ – dajme tomu, že je to nejaká vrstva .Net integrovaná priamo vo web serveri. Tá dokáže z našich DLL a ASPX súborov „vyrenderovať“ Html, ktoré IIS vie preposlať na prehliadač. Okrem toho je .Net priamo prepojený s IIS a tak dokáže pracovať na omnoho nižšej úrovni ako naša aplikácia (ktorá už beží ako proces nejakej vrstvy).

Načo všetky tieto reči? Vieme, že pri session security, aby sme mohli ochrániť stránku, musela byť odvodená od master-page, kde aplikácia rozhodla (na základe session), či sa môže zobraziť. Ako ale docieliť, aby sme zakázali napr. užívateľovi pristupovať ku obrázkom na serveri? Obrázok nie je ASPX súbor a teda ho nemôžeme zahrnúť do „masterpagu“.

Schválne, čo sa stane, ak pridáme obrázok do solution a spustíme predchádzajúcu aplikáciu. Namiesto kliknutia na login napíšeme URL na obrázok. Obrázok sa samozrejme otvorí.

Prehliadač totiž nekomunikuje priamo s našou aplikáciou, ale s webovým serverom. Ten zistil, že na zobrazenie obrázku nie je potrebné sa dotazovať našej aplikácie (nie je to ASPX súbor) a tak ho prehliadaču proste vrátil:

Ako teda presvedči webový server, aby „ochránil“ aj súbory, ktoré nie sú súčasťou aplikácie? Na jednej strane by to bola možnosť nejakej konfigurácie IIS. To by nám ale mohlo zviazať ruky v prípade, že potrebujeme našu bezpečnostnú politiku manažovať dynamicky – teda pridávať a mazať práva za chodu systému. Nie pre každého nového užívateľa (alebo rolu) sa musí administrátor prihlásiť na server a ponastavovať čo treba.

A tak vznikla bláznivá myšlienka, že serveru o tom, či je užívateľ prihlásený povie sám užívateľ. V cookies namiesto session key budeme držať nejakú informáciu, ktorú bude vedieť rozšifrovať server a na jej základe bude už spodná vrstva vedieť rozhodovať (IIS/ASPX) bez zásahu aplikácie o autentifikácii užívateľa. Túto informáciu v cookies budeme volať security token (ticket).

O (ne)bezpečnosti cookies sme si povedali už v pred tým. V kombinácii s SSL to pre útoky typu MITM nie je (v zásade) riziko. Teraz sa ale musíme obávať väčšieho útoku. Informáciu totiž nenesie server ale prehliadač. A tak útočník sa môže priamo pokúsiť o komunikáciu s webovou stránkou (bez nejakého prihláseného užívateľa) – na to ale potrebuje rozlúštiť security token. Ak by vedel útočník „napchať“ do tohto cookie to, čo tam dostane server po autentifikácii užívateľa, nemusí poznať ani heslo daného užívateľa (pretože to slúži len na autentifikáciu pri logine – ďalej už nie je potrebné).

Tento fakt je dosť zásadný a dáva väčšie mínus tomuto prístupu. Problém ale je, že rozkódovať security token nie je jednoduché. Na tomto princípe je postavených veľa komerčných webov a tak v prípade prelomenia bude okamžite nasledovať záplata OS... Problém majú len tí prví... ruku na srdce, bude to náš systém? Ak nerobíme systém pre NBÚ (síce tam to je jedno, tam sa dajú heslá aj uhádnuť), alebo pre nejakú banku, určite by som to nepovažoval za riziko.

Tak ako teda naimplementovať tento security model? Volá sa to Forms Authentication. .Net ponúka viacero módov, medzi iným aj model riadený Windows doménou. Tento spôsob sa väčšinou používa pri intranetových aplikáciách vo vnútropodnikovej sieti. Ak sú zadefinovaní užívatelia s právami vo Windowse, na základe nich vieme riadiť prístup do našej aplikácie (teda Windows ACL funguje ako modul na definovanie pravidiel, ktorý využíva sieť, OS ale aj naša aplikácia).

My sa budeme venovať Forms autentifikácii. Zapnutie tohto módu je šialene triviálne. Upravíme si našu predošlú aplikáciu. Vymažeme z master pagu časť kontrolujúcu prihlásenie. Už to nebudeme potrebovať. O to sa postará vrstva nad našou aplikáciou.

Otvoríme si teda web.config a pridáme tam tento element:

<authentication mode="Forms">

       <forms loginUrl="~/WebFormLogin.aspx" />

</authentication>

 

Zapneme si aplikáciu. Pred tým, ako sa prihlásime z prihlasovacej obrazovky skúsime prejsť na modul WebForm1.aspx (ručne). Ako vidíme, je stále prístupný.

My sme totiž IISku ešte „nepovedali“, čo s anonymným prístupom. Tu sa chcem zastaviť nad dvoma pojmami, ktoré si ľudia často pletú: autentifikáciaautorizácia. Autentifikácia je overenie užívateľa (má / nemá prístup do systému). Autorizácia zasa hovorí o tom, na čo (ne)má užívateľ práva. Autorizácia nepriamo súvisí s autentifikáciou. Pretože aj neprihlásení užívatelia môžu mať nejaké práva. To sa stalo v našom prípade. Musíme teda IISku povedať, čo s neprihláseným užívateľom. My to nastavíme takto: „zakáž všetko“. Pridáme novú konfiguráciu:

<authorization>

       <deny users="?" />

</authorization>

 

Toto teda zakáže všetko v rámci virtuálneho adresára (WebFormLogin.aspx bude povolený, lebo sme ho uviedli v authentication elemente.

Ak teraz vyskúšame aplikáciu a z login obrazovky sa pokúsime pristúpiť na WebForm1.aspx, môžeme vidieť, že sme boli presunutí späť na login obrazovku. Pribudol tam ale parameter, ktorý povie serveru, že keď sa teraz prihlásime, budeme presmerovaní na tu stránku, „o ktorú sme sa pokúšali“.

Táto vlastnosť je výhodná napr.:

-          Užívateľ si do Favorit pridal nejaký modul už po prihlásení sa. Ak na iný deň klikne na Favorit stránku, stránka zistí, že nie je prihlásený, presmeruje ho na login stránku. Po úspešnej autentifikácii sa ale presmeruje presne na modul, ktorý mal vo Favorit (preto je ináč nie veľmi vhodné písať session „state“ aplikácie, alebo s týmto správaním užívateľa treba rátať).

 

-          Link na aplikáciu môže užívateľ poslať (prijať) emailom. Môže to byť nejaký výstup z našej databázy (graf / report) vyžadujúci prihlásenie.

 

-          Atď.

Takže máme teraz našu stránku ochránenú. Moduly WebForm1 ani 2 nejdú zobraziť. Môžeme ale vyskúšať aj obrázok. Správanie bude úplne rovnaké! Ochrana sa deje už na nižšej úrovni.

No stále nie sme na konci. Klikáme a klikáme na login tlačidlo a nič sa nedeje. Je preto, že s Forms security sa pracuje ináč. Login button prepíšeme týmto kódom:

protected void ButtonLogin_Click(object sender, EventArgs e)

{

       FormsAuthentication.RedirectFromLoginPage(/*userName*/"kuko", /*persitant*/false);

}

 

Toto nám veľmi nepomôže, lebo sme nezadefinovali aká stránka sa má zobrazovať po prihlásení. Opäť musíme updatovať web.config:

<forms loginUrl="~/WebFormLogin.aspx" defaultUrl="~/WebForm1.aspx" />

 

Tak a sme hotoví. Ak teraz klikneme na Login tlačidlo, budeme presmerovaní na WebForm1. Ale prístupný máme aj WebForm2 a aj obrázok.

Teraz načrtnem riešenie pre pár najčastejších problémov s ktorými sa stretneme.

Príklad autentifikácie

Databázu užívateľov môžeme mať teraz kdekoľvek. V databáze, alebo staticky v zdrojovom kóde. Ako môžeme vidieť, samotný proces verifikácie má pod kontrolou naša aplikácia. Po pridelení tokenu vie ale už aj nižšia vrstva či užívateľ bol / nebol verifikovaný. Ináč, kľudne sa pozrime do cookies manažéra, čo naša aplikácia vygenerovala po logine... Skúsme sa prihlásiť a daný cookies vymazať. Potom kliknime na WebForm2 linku... prehliadač stratil informáciu o prihlásení a tak nás server presmeroval späť na Login stránku (ako vidíme, informáciu drží jedine prehliadač – pretože vymazanie cookies nespôsobí postback...)

Kvôli jednoduchosti si vytvoríme zoznam užívateľov mimo DB. Na login stránku pridáme textboxy pre prihlasovacie meno aj heslo. Potom to budeme validovať:

bool validnyUzivatel =

       Uzivatelia.Keys.Contains(TextBoxLoginName.Text) &&

       Uzivatelia[TextBoxLoginName.Text] == TextBoxPassword.Text;

 

if (!validnyUzivatel)

       throw new SecurityException("Pristup odmietnuty!");

 

FormsAuthentication.RedirectFromLoginPage(/*userName*/TextBoxLoginName.Text, /*persitant*/false);

 

Pričom zoznam užívateľov je nejaký dictionary.

Ako zistíme, kto je práve prihlásený

Ak chceme zistiť na stránkach (už po autorizácii), kto to vlastne je nalogovaný, vieme to urobiť takto:

LabelVitaj.Text = "Vitaj spat " + Context.User.Identity.Name;

 

Ak potrebujeme viacej informácii, samozrejme musíme kombinovať session. Nielen autentifikovať užívateľa cez security token, ale treba aj naloadovať nejaké štruktúry a tie uložiť v session.

Rôzne stránky po prihlásení

Neraz potrebujeme viacero vstupných obrazoviek po prihlásení. Ak to chceme urobiť, musíme si vytvoriť security token a potom ručne ošetriť skok na inú stránku, to sa dá urobiť aj takto:

bool validnyUzivatel =

       Uzivatelia.Keys.Contains(TextBoxLoginName.Text) &&

       Uzivatelia[TextBoxLoginName.Text] == TextBoxPassword.Text;

 

if (!validnyUzivatel)

       throw new SecurityException("Pristup odmietnuty!");

 

// Autorizaujeme uzivatela

FormsAuthentication.SetAuthCookie(/*userName*/TextBoxLoginName.Text, /*persitantCookies*/false);

 

// Manualne ho redirectujeme

if (TextBoxLoginName.Text == "admin")

       Response.Redirect("WebForm2.aspx");

else

       Response.Redirect("WebForm1.aspx");

Anonymné výnimky

Niekedy naša login obrazovka potrebuje pristúpiť aj ku iným súborom, ako sú napríklad obrázky. Tie sú ale zakázané, preto môžeme vidieť v prehliadači niečo také:

Na to je tiež jednoduchý liek – jednoducho vieme pridať výnimku vo web.configu, aby sa daný súbor (alebo aj celý adresár) neriadil zákazom:

<location path="Anonym.png">

       <system.web>

             <authorization>

                    <allow users="*" />

             </authorization>

       </system.web>

</location>

Odhlásenie

Niektoré prehliadače môžu mať problém s odhlasovaním, preto pastujem overený postup. Ten ináč môžeme dať na Login obrazovku a teda presmerovanie na login bude vlastne logoutom (vieme to urobiť cez link a nemusí tam ísť postback – alebo logout obrazovka môže byť ASPX / ASHX modul, ktorý potom urobí redirect na Login stránku):

// Clear member access informations

FormsAuthentication.SignOut();

 

// FF caching issue

var formCookie = new HttpCookie(FormsAuthentication.FormsCookieName, /*value*/string.Empty);

formCookie.Expires = DateTime.Now.AddYears(-1);

context.Response.Cookies.Add(formCookie);

 

// Clear session

context.Session.RemoveAll();

context.Session.Clear();

 

if (restoreSession)

{

       context.Session.Abandon();

 

       // FF caching issue

       var sessionCookie = new HttpCookie("ASP.NET_SessionId", /*value*/string.Empty);

       sessionCookie.Expires = DateTime.Now.AddYears(-1);

       context.Response.Cookies.Add(sessionCookie);

} // if

 

Impersonating

Niekedy potrebujeme za chodu aplikácie zmeniť užívateľa. Väčšinou sa jedná o administrátorské prístupy – ak administrátor potrebuje vidieť účet niekoho iného. Keďže túto operáciu vykonáva už overený užívateľ, overenie hesla pre druhého užívateľa nie je nutné – to je len naša nadpráca (v prvom príklade sme nemali žiaden zoznam užívateľov a prístup sme autentifikovali).

Na takúto operáciu ale budeme potrebovať generovať security token manuálne. Pretože niekde si chceme odpamätať napr. pôvodného užívateľa. Samozrejme, vieme to urobiť cez session, ale narazíme na problém pri SSO – teda ak si tento token vymenia dve aplikácie, tá druha by nevedela, že účet bol impersonovaný. Potom by sme v druhej aplikácii nevedeli napr. odchytiť stav, kedy manažovaný účet (administrátorom) uvoľňuje nejaké skryté operácie. Toto už je samozrejme dosť špecifická úloha, ale nezaškodí vedieť robiť token ručne. Môžeme si všimnúť, že je tam jeden parameter (userData) kde môžeme vpašovať napr. pôvodný login name administrátora.

bool validnyUzivatel =

       Uzivatelia.Keys.Contains(TextBoxLoginName.Text) &&

       Uzivatelia[TextBoxLoginName.Text] == TextBoxPassword.Text;

 

if (!validnyUzivatel)

       throw new SecurityException("Pristup odmietnuty!");

 

var ticket = new FormsAuthenticationTicket(

       /*version*/3,

       TextBoxLoginName.Text,

       /*start*/DateTime.Now,

       /*expire*/DateTime.Now.AddMinutes(120),

       /*persistantCookies*/false,

       /*data*/string.Empty);

 

string hash = FormsAuthentication.Encrypt(ticket);

 

var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);

authCookie.Domain = FormsAuthentication.CookieDomain;

 

Context.Response.Cookies.Add(authCookie);

 

// Manualne ho redirectujeme

if (TextBoxLoginName.Text == "admin")

       Response.Redirect("WebForm2.aspx");

else

       Response.Redirect("WebForm1.aspx");

Autorizácia

V aplikácii samozrejme potrebujeme riadiť práva rozličným užívateľom. Napríklad Administrátor môže manažovať zoznam userov, ale tí to zasa nemajú povolené a pod. Na toto vieme využiť iný .Net komponent – tzv. RoleManager. Ako sa s ním pracuje je ale na samostatný článok.

 

Demo projekt na stiahnutie: http://www.accendo.sk/Download/AspxOchranaDemo02.zip