Silverlight - Bezpieczeństwo Udostępnij na: Facebook

Autor: Artur Żarski

Podczas konferencji MIX’07, która odbyła się na początku maja 2007 roku w Las Vegas, została pokazana nowa technologia prezentacji zawartości stron WWW. Jest nią Silverlight, która z założenia ma działać na różnorodnych platformach.

Technologia ta pozwala na tworzenie bogatych, atrakcyjnych wizualnie interfejsów użytkownika, które mogą pracować w dowolnym środowisku — w przeglądarkach internetowych, na różnych urządzeniach i na różnych systemach operacyjnych (np. Apple Macintosh). Podobnie jak w przypadku WPF ( Windows PresentationFoundation), technologii prezentacyjnej stworzonej wraz z .NET Framework 3.0, podstawą Silverlight jest język XAML (eXtensibleApplicationMarkupLanguage). Technologia ta, pomimo tego, że jest ciągle w fazie rozwojowej (w chwili obecnej dostępne są dwie wersje Silverlight – 1.0 RC, czyli kandydat do wersji ostatecznej oraz wersja 1.1 alfa), staje się coraz bardziej popularna i wciąż rośnie liczba serwisów używających jej do celów prezentacji treści. Przy tej okazji bardzo często padają pytania o bezpieczeństwo aplikacji opartych o Silverlight, głównie ze względu na obsługę wielu platform. Artykuł ma na celu wprowadzenie i ogólne przedstawienie modelu bezpieczeństwa tej technologii.

Trudne początki, czyli wstęp do modelu bezpieczeństwa

Jak wiadomo, już od samego początku założeniem nowej technologii było stworzenie  minimalnego silnika do uruchamiania aplikacji opartych o Silverlight, czyli de facto o .NET. Silverlight zawiera bardzo mocno okrojoną wersję CLR (CommonLanguageRuntime). Całość zajmuje ok. 5 MB. Znając podstawy programowania w .NET Framework oraz objętość wszystkich bibliotek i powiązań, można się zastanawiać, jak to wszystko działa – a w szczególności, jak rozwiązane są aspekty bezpieczeństwa. Przecież każdy programista, który pisał bezpieczne aplikacje, musiał poznać różnorodne aspekty takich narzędzi, jak caspol.exe i specyfikę bezpieczeństwa opartego na uprawnieniach kodu (CAS – CodeAccess Security ). Bardzo dobrą informacją dla wszystkich, którzy spędzili długie godziny na rozgryzaniu działania tego rozwiązania oraz wszystkich jego ustawień jest to, że dla Silverlight po prostu przestało ono istnieć. Standardowy CAS znany z pełnej wersji .NET został wycofany. W CoreCLR (tak nazywa się CLR w Silverlight) nie znajdziemy już uprawnień, poziomów zabezpieczeń oraz przejść po stosie zabezpieczeń. Przestrzeń nazw System.Security została bardzo mocno ograniczona. Wnikliwi programiści, którzy wykonają dekompilację biblioteki mscorlib.dll, zauważą jednak, że klasa SecurityPermission nadal istnieje. O co więc chodzi? Jeśli nasza biblioteka zawiera jakiś niesprawdzony kod, wtedy kompilator wywoła flagę RequestMinimum dla właściwości SkipVerification.

Model bezpieczeństwa w CoreCLR, który musi zastąpić CAS, może zostać opisany następująco:

  • Każdy kod użytkownika, który uruchamiany jest w CoreCLR, jest zupełnie przeźroczysty.
  • Kod platformy (charakterystyczny dla danego systemu operacyjnego) może zawierać dwa rodzaje kodu: przeźroczysty i krytyczny. Jest on odpowiedzialny za umożliwienie uruchomienia przeźroczystego kodu w taki sposób, aby miał on bezpieczny dostęp do różnych usług systemowych.

Reasumując, oznacza to, że w modelu bezpieczeństwa CoreCLR aplikacje Silverlight nie mogą zawierać kodu, który nie jest zweryfikowany i mogą wywoływać tylko i wyłącznie metody API, które są przeźroczyste lub krytyczne.

Zagłębiamy się w szczegóły, czyli o co w ogóle chodzi

No dobrze, ale czym właściwie jest kod przeźroczysty? Jest to taki kod, który nie może wykonać żadnej akcji skutkującej odwołaniem do stosu uprawnień. A dokładniej, przeźroczysty kod nie może w żaden sposób spowodować sprawdzenia zabezpieczeń, które zakończy się sukcesem (może natomiast zakończyć się niepowodzeniem). Przeciwieństwem jest kod krytyczny, którego biblioteki mogą zawierać kombinacje kodu krytycznego i przeźroczystego. Pojedyncze metody mogą być przeźroczyste lub krytyczne, jednak nie mogą łączyć obu rodzajów kodu.

Ograniczenia dla kodu przeźroczystego:

  • nie może wykorzystywać atrybutu LinkDemand,
  • nie może wywoływać Assert w CAS,
  • nie może zawierać nieweryfikowalnego kodu,
  • nie może wywoływać natywnego kodu przy użyciu P/Invoke lub COM Interop,
  • nie może zapewniać dostępu do kodu krytycznego lub danych tam, gdzie nie jest to potrzebne.

Na początku tekstu mówiliśmy, że w Silverlight nie ma CAS, więc o co chodzi w pierwszych dwóch ograniczeniach? Przecież bez CodeAccess Security nie można wywołać Assert, a nawet zwykłego LinkDemand. Póki nie mamy koncepcji żądań w Silverlight, którego mechanizm wywołania nie jest właściwy, zamiast tego wywołujemy wyjątek klasy MethodAccessException – o ile kod przeźroczysty próbuje złamać ustalone zasady. Najbardziej interesujące jest ostatnie ograniczenie. W przypadku, gdy kod przeźroczysty próbuje bezpośrednio wywołać kod krytyczny, to wywołany będzie wyjątek klasy MethodAccessException. Niemniej jednak większość z interesujących nas usług wymaga implementacji jako kod krytyczny (np. dostęp do systemu plików). Jeśli mamy do czynienia z takim właśnie przypadkiem, to jak Silverlight może uzyskać dostęp do tego typu usług?

Wspominałem wcześniej o tym, że dostęp do plików oraz wszelkie operacje wejścia/wyjścia powinny być implementowane jako bezpieczny kod. Aby mieć możliwość trwałego przechowywania danych, musimy posiadać jakąś warstwę dla kodu krytycznego, który będzie mógł być wywoływany. W Silverlight tą krytyczną warstwą jest IsolatedStorage. Kiedy aplikacja Silverlight wywołuje IsolatedStorage, wtedy API sprawdza żądanie, aby mieć pewność, że aplikacja żąda prawidłowego pliku i że nie jest przekroczony dla niej tzw. przydział (ang. quote). Jest to analogiczne do modelu wywołań w systemie operacyjnym (patrz Tabela 1).

System operacyjny CoreCLR Przykład IsolatedStorage
Kod w trybie użytkownika Kod przeźroczysty Aplikacja Silverlight
Wywołanie systemowe Bezpieczne krytyczne API System.IO.IsolatedStorage
Kod jądra Krytyczne API System.IO.FileStream

Tabela 1. Przykłady zastosowania IsolatedStorage.

Podobnie jak aplikacje uruchamiane na MacOS i Windows nie mogą odwoływać się bezpośrednio do jądra systemu operacyjnego bez przejścia przez powłokę wywołań systemowych, tak też aplikacje Silverlight nie mogą bezpośrednio wywoływać krytycznego kodu bez przejścia przez bezpieczną powłokę krytyczną. W Silverlight cały kod jest standardowo przeźroczysty (i to odróżnia go od CLR w wersji desktop,gdzie cały kod z definicji jest krytyczny).

Kod platformy może być przeźroczysty, krytyczny oraz bezpieczny krytyczny ( ang. safecritical). CoreCLR automatycznie wykryje kod platformy, wiedząc, skąd ładowana jest biblioteka (biblioteki platformy muszą być ładowane z katalogu, w którym jest zainstalowany Silverlight) oraz poprzez sprawdzenie klucza publicznego, którym ta biblioteka została podpisana. Tylko i wyłącznie biblioteki podpisane specyficznym kluczem, którym podpisuje je Microsoft, będą traktowane jako biblioteki platformy.

Jeśli metoda w bibliotece platformy jest opisana atrybutem SecurityCriticalAttribute, oznacza to, że zawiera ona kod krytyczny. Kod przeźroczysty nie może wywołać żadnej metody kodu krytycznego – nawet, jeśli jest ona publiczna. Każda próba złamania tej reguły spowoduje wywołanie wyjątku MethodAccessException.

Listing 1 pokazuje konstruktor dla klasy FileStream w Silverlight 1.1. Jest on krytyczny (widać to w linii 5), ponieważ został opisany atrybutem SecurityCriticalAttribute.

Listing 1. Przykład konstruktora dla klasy FileStream.

1 .method public hidebysig specialname rtspecialname 2 instance void .ctor(string path, 3 valuetype System.IO.FileMode mode) cil managed 4 { 5 .custom instance void System.Security.SecurityCriticalAttribute::.ctor() = ( 01 00 00 00 ) 6 7 // ... 8 }

Podobnie jest z każdą metodą w klasie Marshal– są one również krytyczne, dopóki będą opisane atrybutem SecurityCriticalAttribute (linia 4 na Listingu 2).

Listing 2. Przykład metody w klasie Marshal.

1 .class public abstract auto ansi sealed beforefieldinit System.Runtime.InteropServices.Marshal 2 extends System.Object 3 { 4 .custom instance void System.Security.SecurityCriticalAttribute::.ctor() = ( 01 00 00 00 ) 5 6 // ... 7 } // end of class System.Runtime.InteropServices.Marshal

Każda metoda w bibliotece platformy oznaczona atrybutem SecuritySafeCriticalAttribute jest oczywiście bezpieczna krytyczna (safecritical). Kod aplikacji może wywoływać te metody, dopóki kod przeźroczysty może wywoływać bezpieczny kod krytyczny. Przykładem krytycznego API jest metoda IsolatedStorageFileStream.Write (linia 6 na Listingu 3).

Listing 3. MetodaIsolatedStorageFileStream.Write.

1 .method public hidebysig virtual instance void 2 Write(uint8[] buffer, 3 int32 offset, 4 int32 count) cil managed 5 { 6 .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 ) 7 8 // ... 9 }

Ciekawostką jest fakt, żeatrybutSecuritySafeCriticalAttribute jest w rzeczywistościpołączeniemdwóchatrybutów z pełnejwersji .NET:  SecurityCriticalAttribute orazSecurityTreatAsSafeAttribute.

Potrzeba jawnego określania metod atrybutami SecurityCritical oraz SecurityTreatAsSafe stała się powszechna, dlatego też stworzono atrybut SecuritySafeCritical. Dzięki stosowaniu tego atrybutu skracamy czas pisania aplikacji, a dodatkowo zmniejszamy wielkość metadanych. Jeśli metoda nie posiada ani atrybutu SecurityCritical, ani atrybutu SecuritySafeCritical, wtedy musi być ona przeźroczysta. Jeśli metoda jest widoczna dla kodu aplikacji, oznacza to, że aplikacja ma możliwość wywołania jej – ponieważ przeźroczysty kod ma zawsze możliwość wywołania innego transparentnego kodu.

Dokładnie tak samo jest w sytuacji, w której aplikacja Silverlight chciałaby „być sprytna” i opisać się jednym z atrybutów SecurityCritical lub SecuritySafeCritical. W tym momencie nie wydarzy się nic, ponieważ CLR wie, że kod aplikacji musi być przeźroczysty.

Bardzo ważnym jest fakt, że reguły te nie zastępują standardowych reguł dostępu (Public, Private, Internal), ale je dopełniają. Dlatego też kod aplikacji nie może wywoływać wewnętrznych metod w bibliotekach systemowych – nawet, jeśli są one przeźroczyste. Tabela 2 pokazuje wszystkie te zależności.

 

  Atrybut bezpieczeństwa Rodzaj Możliwość wywołania przez kod aplikacji
(jeśli metoda jest widoczna)
Kod aplikacji - Przeźroczysty Tak
Platforma None Przeźroczysty Tak
Platforma SecuritySafeCritical Bezpieczny krytyczny Tak
Platforma SecurityCritical Krytyczny Nie

Tabela 2. Zależności przy stosowaniu różnych atrybutów i możliwość wywoływania metod.

Dziedziczenie

Na koniec jeszcze kilka słów na temat dziedziczenia w Silverlight. Na początek określmy jednak logiczny porządek pomiędzy poziomami transparentności:

  • przeźroczysty (aplikacja i platforma),
  • bezpieczny krytyczny (tylko platforma),
  • krytyczny (tylko platforma).

Ale jak się to ma do dziedziczenia? Otóż kolejność ta jest bardzo istotna, kiedy chcemy określić czy dana klasa może pochodzić z klasy rodzica. Każda klasa może pochodzić z innej, ale tylko i wyłącznie z takiej, która jest na takim samym lub wyższym poziomie hierarchii. Nie może ona nigdy pochodzić z niższej klasy bazowej w hierarchii. Oznacza to, że klasy krytyczne mogą pochodzić z innych dowolnego typu, podczas gdy klasy transparentne mogą pochodzić tylko i wyłącznie z innych transparentnych.

Kolejną istotną kwestią jest możliwość przeciążania metod wirtualnych podczas dziedziczenia. Kiedy chcemy przeciążać metody, warto je wcześniej pogrupować według różnych poziomów bezpieczeństwa:

  • przeźroczysty oraz bezpieczny krytyczny kod,
  • kod krytyczny.

Po pogrupowaniu metod w taki właśnie sposób zauważyć można, że kod przeźroczysty ma dostęp do wszystkiego, podczas gdy kod krytyczny ma dostęp do grupy z poziomu drugiego. Te same reguły stosuje się podczas implementacji interfejsów. Wszystkie te reguły dziedziczenia można opisać za pomocą następujących zasad:

  • typy mogą pochodzić tylko z typów bazowych, które są przeźroczyste,
  • tylko przeźroczyste lub bezpieczne krytyczne metody mogą być nadpisywane,
  • tylko transparentne lub krytyczne-bezpieczne metody interfejsu mogą być implementowane.

Podsumowanie

Jak widać, model bezpieczeństwa Silverlight znacznie odbiega od tego, który znamy z pełnej wersji .NET Framework. Został on znacznie uproszczony, dzięki czemu jest łatwiejszy w implementacji. Ważne jest, aby programista zwracał uwagę na wszystkie wymienione kwestie.

O autorze

Autor jest pracownikiem firmy Microsoft. Na co dzień zajmuje się m. in. tworzeniem rozwiązań w oparciu o SQL Server w różnych aspektach – bazy relacyjne, usługi integracyjne, usługi analityczne. Jest certyfikowanym administratorem baz danych (MCDBA). Kontakt z autorem: *arturz@microsoft.com*