BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Przegląd wybranych zabezpieczeń dostępu do zasobów na platformie .NET

14-12-2004 17:09 | ptomcz5014
Artykuł ma na celu ułatwić zrozumienie zasad bezpieczeństwa oraz rozwiązań zastosowanych w architekturze platformy .NET poprzez przegląd wybranych mechanizmów zabezpieczeń.

Wstęp

Zagadnienia dotyczące zabezpieczeń zasobów są często pomijane i niejasne, zarówno dla programistów jak i użytkowników końcowych. Każdy użytkownik komputera powinien posiadać świadomość zagrożeń dla systemów komputerowych oraz mechanizmów im zapobiegających. Mając to na uwadze, przyjrzyjmy się różnym aspektom zabezpieczeń wchodzących w skład architektury .NET Framework. Skupimy się na zabezpieczeniach dostępu do kodu (Code Access Security) z wykorzystaniem zabezpieczeń bazujących na ewidencji (Evidence-based security), a także na  zabezpieczeniach bazujących na rolach (Role-based security).

 

Wykorzystywanie wskazanych rozwiązań odciąża  niekompetentnych w dziedzinie bezpieczeństwa  użytkowników. Podobnie jest w przypadku programisty, który swój wkład w powyższe zabezpieczenia ma śladowy, a większość wykonuje za niego .NET Framework. Zanim jednak przejdziemy do omawiania wybranych aspektów zabezpieczeń, kilka słów o mechanizmie w architekturze .NET Framework służącym między innymi do zarządzania ryzykiem związanym z oprogramowaniem.

 

CLR

.NET Framework zapewnia programistom i administratorom przejrzystą kontrolę zabezpieczeń nad zasobami i aplikacjami. W sposób niewidoczny kontroluje zachowanie kodu w różnych okolicznościach i zmniejsza ryzyko powstawania błędów w kodzie, jak i dostępu do tego kodu, co mogłoby narazić aplikację na nieprzewidziane działania lub „dziury” mogące przyczynić się do ułatwienia włamania. Jest to realizowane w dużej części dzięki wbudowanemu narzędziu jakim jest CLR.

Common Language Runtime (CLR) jest komponentem wchodzącym w skład .NET Framework służącym do uruchamiania i zarządzania kodem. Wszelkie zasady bezpieczeństwa określone przez .NET Framework są wymuszane na „kodzie zarządzanym” (managed code) poprzez CLR. Zarządzany kod jest najogólniej mówiąc kodem pisanym przez programistę na przykład w językach C# ,Visual Basic czy J#, który podlega kontroli CLR’a. Każdy programista pracujący nad projektem informatycznym może wybrać dogodny dla siebie język. Niezależnie od wyboru języka programowania, infrastruktura zabezpieczeń będzie obejmowała je wszystkie w ten sam sposób. .Net Framework dostarcza między innymi narzędzia zabezpieczeń takie jak: autoryzacja, uwierzytelnianie, personifikacja, kontrola dostępu do kodu i zasobów, zabezpieczenia przed przepełnieniem, kryptografia i weryfikacja, ustanowienie polityki zabezpieczeń i personifikacja, ukrywanie informacji i danych (jak na przykład ścieżka do miejsca gdzie zapisywane będą dane). Kod ten jest następnie tłumaczony poprzez mechanizm kompilacji „just-in-time” (JIT) do „kodu rodzimego” (native code) i uruchamiany. Całość nadzoruje CLR, który inicjuje działanie JIT.

 

Zabezpieczenia dostępu do kodu (CAS) z wykorzystaniem ewidencji i uprawnień

Podstawową kwestią potrzebną do realizowania zabezpieczeń jest ustalenie polityki zabezpieczeń. Będzie ona określać co można, a czego nie, do których zasobów kod  ma dostęp, a do których nie; innymi słowy polityka zabezpieczeń będzie zbiorem zasad określających sposób zabezpieczania.

 

Każda wykonywalna aplikacja i biblioteka dll posiada swoją ewidencję (evidence):

[Serializable]

public sealed class Evidence : ICollection, IEnumerable

czyli zbiór danych - kolekcję (zawierającą między innymi podpisy cyfrowe, pochodzenie, a także inne dodatkowe wybrane dane identyfikujące aplikację czy bibliotekę). Na jej podstawie, polityka zabezpieczeń jest w stanie określić, do których zasobów kod powinien mieć dostęp. Każdy zasób ma przydzielone określone prawa dostępu i jest nadzorowany przez uprawnienia (permissions). Uprawnienia określają kryteria dostępu do zasobów, oraz  implementują metody umożliwiające wykonywanie żądań i dostęp. Na podstawie ewidencji i uprawnień CLR decyduje, które zasoby mogą być przez kod wykorzystywane i w jakim stopniu. Podstawowym zadaniem polityki zabezpieczeń jest więc porównywanie ewidencji zarządzanego kodu z określonymi uprawnieniami zasobów. Przykłady obiektów, do których .NET Framework domyślnie przydziela uprawnienia:

Web, Security, FileIO, IsolatedStorage, DataAccess, Socket...

 

Programista może zdefiniować reakcje swojego kodu na określone uprawnienia do interesujących go zasobów. Może swoje żądania podzielić na 3 podstawowe typy:

minimal, optional, refuse. Gdy nie ma dostępu do któregokolwiek z zasobów, do których odwołują się żądania minimal, wówczas kod nie zostanie wykonany. Refuse oznacza, że dany zasób nie będzie potrzebny, a optional oczywiście, iż zasób ten jest opcjonalny.

Przykładowo może się zdarzyć, że po porównaniu uprawnień zasobów FileIO i IsolatedStorage z ewidencją uruchamianego kodu, okaże się, że jest możliwy dostęp do IsolatedStorage (do którego odwoływało się żądanie minimal), ale nie ma go do FileIO (do którego odwoływało się żądanie optional) . IsolatedStorage udostępnia dane podobnie jak FileIO, z tą tylko różnicą, że kod jest izolowany od takich niebezpiecznych danych, jak struktura katalogów, nazwy dysków itp. Tak więc, taka konstrukcja kodu wskazuje na to, iż dostęp do danych na dyskach powinien być otrzymany w całości, ale nie musi (jest opcjonalny). Musi natomiast być dostęp do danych w sposób izolowany, inaczej kod się nie uruchomi.

 

Zobaczmy jak zaimplementować w C# żądanie opcjonalnego dostępu do danych na dysku. Zakładam, że istnieje klasa Dysk, która posiada metodę wykonującą operacje na dysku WykonajNaDysku():

 

using System.Security.Permissions;

//Żądanie dostepu

[assembly:FileIOPermission(SecurityAction.RequestOptional, Unrestricted = true)]

using System;

using System.Security;

public class Dostep

{

public static void Main(string[] args)

{

try

{

Dysk InstancjaDysku = new Dysk();

//Podczas wykonywania tej metody, gdy nie uzyskano dostepu zostanie //rzucony wyjatek

//Gdyby zamiast żądania opcjonalnego było minimalne wówczas kod nie //zostalby uruchomiony.

InstancjaDysku.WykonajNaDysku();

}

// wylapuje wyjatek, gdy nie uzyskano uprawnienia

catch(SecurityException)

{

Console.WriteLine("Aplikacja nie ma dostępu do dysku.");

}

}

}

 

Żądanie FileIOPermission używa flagi SecurityAction.RequestOptional, odrzucając inne uprawnienia. Flaga SecurityAction.RequestOptional oznacza, że aplikacja się wykona nawet gdy nie uzyska wymaganych uprawnień (w przeciwieństwie do RequestMinimal, która spowoduje przerwanie wykonywania kodu). Gdy aplikacja nie uzyska dostępu i będzie chciała skorzystać z chronionego zasobu wówczas zostanie rzucony wyjątek SecurityException.

 

Call stack

Call stack (w wolnym tłumaczeniu stos wywołań)  służy do zapobiegania atakom zwanym ‘luring attacks’. Ataki te polegają na tym, że kod nie mający uprawnień dla danego zasobu wywołuje kod posiadający te uprawnienia i wykorzystuje go pośrednio do wykonania nieautoryzowanych operacji. System bezpieczeństwa, aby temu zapobiec sprawdza cały łańuch wywołań w call stack,po każdym nowym żądaniu, porównując przydzielone uprawnienia każdego wywołania z wymaganymi. Jeżeli którekolwiek wywołanie nie posiada odpowiednich uprawnień, wówczas rzucany jest wyjątek SecurityException. Oczywiście zmniejsza to wydajność aplikacji, co można rozwiązać poprzez sprawdzanie wybranej mniejszej ilości wywołań (pamiętając aby nie osłabić przy tym zabezpieczeń zasobu).

 

Role-based security

Zabezpieczenia bazujące na rolach polegają na sprawdzeniu tożsamości użytkownika i pozwoleniu bądź też odrzuceniu żądania do zasobu. Odbywa się to za pomocą dwóch głównych procesów: uwierzytelniania i autoryzacji. Uwierzytelnianie, to proces sprawdzania tożsamości użytkownika, który chce otrzymać dostęp do zasobu. Najczęściej sprawdzanymi danymi są nazwa i hasło. Po potwierdzeniu użytkownik otrzymuje odpowiednią tożsamość. Następnie sprawdzane jest do jakich zasobów dany użytkownik, o określonej tożsamości może uzyskać dostęp - jest to proces autoryzacji.

 

Autoryzacja

Metody kontroli zasobów można podzielić na 3 grupy:

Bezpośrednie sprawdzenie nazwy użytkownika – wykorzystujące metodę Iprincipal.IsInRole służącą do autoryzacji dostępu do określonych bloków kodu w oparciu o rolę.

 

Deklaratywne – zabezpieczonej metodzie przypisujemy atrybuty klasy PrincipalPermissionAttribute. Atrybuty te nie realizują warunku OR, nie można więc określić czy użytkownik ma należeć do którejś z kilku ról.

 

Imperatywne – niskopoziomowa autoryzacja wykonywana za pomocą PrincipalPermission.Demand odnosząca się do kodu w bieżącym bloku.

 

Autoryzacji użytkownika o nazwie Kamil i roli Czytelnik do wykonania pewnej operacji można wykonać następująco (sprawdzamy czy użytkownik ma nazwę Kamil i jest Czytelnikiem):

 

Bezpośrednio:

GenericIdentity uzytkownik = new GenericIdentity(„Kamil”);

string[] role = new string[] {“Czytelnik”,”Wypozyczajacy”, „Nauczyciel”, ”Admin”);

GenericPrincipal  kimJestUzytkownik = new GenericPrincipal(uzytkownik,role);

//Jeśli nazwa uzytkownika to Kamil i spelnia role Czytelnika

if (uzytkownik.Name==”Kamil” && kimJestUzytkownik.IsInRole(„Czytelnik”))

{

// ...zabezpieczony kod

}

 

Deklaratywnie:

[PrincipalPermissionAttribute(SecurityAction.Demand, Name="Kamil",

Role="Czytelnik")]

public void ZabezpieczonaMetoda

{

// ...zabezpieczony kod

}

 

Imperatywnie:

PrincipalPermission sprawdzenie = new PrincipalPermission(“Kamil”, “Czytelnik”);

sprawdzenie.Demand();

// ...zabezpieczony kod

// (jeśli nie uzyska dostępu wówczas zostanie rzucony wyjątek SecurityException)

 

W przypadku, gdy chcemy sprawdzić czy użytkownik należy chociaż do jednej spośród kilku konkretnych ról, możemy to w prosty sposób wykonać za pomocą każdego z powyższych mechanizmów.

 

Bezpośrednio:

if (kimJestUzytkownik.IsInRole(„Czytelnik”)) || kimJestUzytkownik.IsInRole(„Nauczyciel”))

{

// ...zabezpieczony kod

}

 

Deklaratywnie:

[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Czytelnik"), PrincipalPermissionAttribute(SecurityAction.Demand, Role="Czytelnik")]

public void ZabezpieczonaMetoda

{

// ...zabezpieczony kod

}

 

Imperatywnie:

PrincipalPermission sprawdzenieCzytelnik = new PrincipalPermission(null, “Czytelnik”);

sprawdzenie.Demand();

PrincipalPermission sprawdzenieNauczyciel = new PrincipalPermission(null, “Nauczyciel”);

sprawdzenie.Demand();

(sprawdzenieCzytelnik.Union(sprawdzenieNauczyciel)).Demand();

// ...zabezpieczony kod

 

Sprawdzenie czy użytkownik należy do kilku ról naraz w bezpośredni sposób, sprowadza się tylko do zamiany operatorów w warunku z OR na AND. Deklaratywnie nie ma możliwości zaimplementowania warunku AND. Imperatywnie wygląda to następująco:

PrincipalPermission sprawdzenieCzytelnik = new PrincipalPermission(null, “Czytelnik”);

sprawdzenieCzytelnik.Demand();

PrincipalPermission sprawdzenieNauczyciel = new PrincipalPermission(null, “Nauczyciel”);

sprawdzenieNauczyciel.Demand();

// ...zabezpieczony kod

 

Uwierzytelnianie

Powyżej przedstawiliśmy jak autoryzować użytkownika relatywnie do konkretnych zasobów.

Przejdźmy teraz do uwierzytelniania. W ASP.NET wyróżniamy następujące sposoby uwierzytelniania: systemu Windows, za pośrednictwem formularza, przy użyciu usługi Passport.

Uwierzytelnianie systemu Windows polega na wykorzystaniu serwera IIS (Internet Information Server). Tożsamości wykorzystywane przez IIS są adekwatne do kont użytkowników systemu Windows.

Uwierzytelnianie za pośrednictwem formularzy odbywa się za pośrednictwem samej aplikacji ASP.NET. Użytkownik chcący uzyskać dostęp do chronionego zasobu jest przekierowywany do odpowiedniego formularza będącego częścią aplikacji. Dane podane na tym formularzu przez użytkownika, oraz zapisane w bazie są porównywane i ewentualne uprawnienie jest umieszczane w specjalnym cookie autoryzacyjnym.

Usługa Passport jest prowadzona przez Microsoft. Działa niemal identycznie jak w uwierzytelnianiu za pośrednictwem formularzy z tą tylko różnicą, iż nie trzeba implementować żadnych funkcjonalności samodzielnie. Podczas wymagania uwierzytelnienia użytkownika, przekierowywany jest on na odpowiednią chronioną stronę, gdzie za pomocą prostego formularza wykonywane jest porównanie identyfikujących go danych i zapisanych w bazie.

Aby użyć powyższych mechanizmów do zabezpieczenia naszej aplikacji należy w pliku

web.config ustawić atrybut mode znacznika <authentication> na „Windows”, „Forms” lub „Passport”.

 

Poniżej pokażę jak w prosty sposób, można używając uwierzytelniania bazującego na formularzach, zabezpieczyć aplikację lub jej konkretne części, za pomocą nazwy użytkownika i hasła. W web.config w podznaczniku <forms> ustalamy formularz, który ma zostać wczytany jako strona logowania. Dodatkowo w znaczniku <credentials> zapisujemy dane które zostaną porównane z wprowadzonymi przez użytkownika.

<authentication mode=”Forms”>

      <forms name=”Autoryzuj” loginUrl=”login.aspx”>

            <credentials passwordFormat=”Clear”>

                  <user name=”Kamil” password=”abcd”/>

            </credentials>

      </forms>

</authentication>

 

W formularzu logowania wykorzystujemy metody klasy FormsAuthentication. Sprawdzimy czy dane użytkownika są zapisane w credentials oraz przejdziemy do żądanej strony jeśli jego dane będą zgodne. Potwierdzenie tożsamości przechowywane jest w cookie autoryzacyjnym.

if FormsAuthentication.Authenticate(uzytkownik,haslo)

{

      // wraca do strony z formularza do którego był potrzebny dostep

      // zmienna logiczna oznacza czy cookie ma być ustawione na stałe

      FormsAuthentication.RedirectFromLoginPage(uzytkownik,false)

}

 

Zapisujemy w pliku web.config użytkowników, którzy mają mieć dostęp do strony będącej w danym katalogu (autoryzacja).

<configuration>

<location path=katalog/strona.aspx>

            <system.web>

<authorization>

<deny users=”*”>

<allow users=”Kamil”>

</authorization>

            </system.web>

</location>

</configuration>

 

Zabezpieczenia...

Dla programisty powyższe mechanizmy są proste w obsłudze i pozwalają na solidne oraz niezawodne zabezpieczenie aplikacji przy minimalnym, niezbędnym nakładzie pracy. Jest tak, ponieważ większość pracy wykonuje CLR, który to stosuje wszystkie skomplikowane mechanizmy w sposób niewidoczny dla programisty na kodzie zarządzanym.

Można oczywiście odwoływać się bezpośrednio do kodu niezarządzanego (bez kontroli CLR). Należy tego jednak unikać. Może to prowadzić w wielu okolicznościach do niemożności uruchomienia kodu, z powodu braku wysoko-poziomowego zaufania do takiej operacji. Jeśli nawet zmienimy politykę zabezpieczeń, aby zezwoliła na operacje w oparciu o kod niezarządzany, wówczas znacząco obniży to odporność systemu i jego zdolność blokowania większości niepożądanych operacji. W efekcie może to niszcząco wpłynąć na oczekiwane działanie aplikacji.

 

Podobne artykuły

Komentarze 4

niemro
niemro
0 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Artykul ciekawy, pomaga zrozumiec do czego, miedzy innymi, tak naprawde sluzy CLR. Poza tym w klarowny sposob wyjasnia sposob dzialania opisanych zabezpieczen.
jedrekwie
jedrekwie
5 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Kompletne opracowanie metod zabezpieczen w .NET. Autor zamiescil tylko te informacje, ktore sa na prawde niezbednie potrzebne. Wiadomo bowiem, ze sam artykul moze dac jedynie poglad na sprawe - by w pelni wykorzystac omawiane technologie i tak nalezy uwaznie przestudiowac dokumentacje. Bardzo mi sie podobalo!!!
wdon7304
wdon7304
0 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Artykuł bardzo ciekawy, w przejrzysty i jasny sposób prezentuje istotne mechanizmy zabezpieczeń oferowane przez środowisko .NET.
User 85390
User 85390
0 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Bardzo ciekawy artykół, mam jednocześnie pytanko gdyz sam poszukuje materiałów ale po polsku na temat nowych operacji na uprawnieniach (DemandChoice, LinkDemandChoice) które pojawiły się w .NET Framework 2.0. jak najprościej je opisac? pozdrawiam
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.