Zmiany w zakresie zabezpieczeń w platformie .NET 4

W platformie .NET 4 wprowadzono dwie ważne zmiany związane z bezpieczeństwem. Usunięto zasady bezpieczeństwa dla całego komputera, nie usuwając jednak systemu uprawnień. Domyślnym mechanizmem egzekwowania stała się przejrzystość zabezpieczeń (j.ang.). Więcej informacji można uzyskać w dokumencie Kod przejrzysty pod względem zabezpieczeń, poz. 2 (j.ang.). Ponadto niektóre operacje związane z uprawnieniami, które mogły stwarzać zagrożenie, są obecnie przestarzałe.

Ważne
Zabezpieczenia dostępu kodu nie zostały usunięte. Z zabezpieczeń dostępu kodu usunięto zasady bezpieczeństwa, ale świadectwa i uprawnienia nadal obowiązują. Wyeliminowano część uprawnień, a przejrzystość uprościła egzekwowanie zabezpieczeń. Krótkie podsumowanie zmian można znaleźć w dokumencie Podsumowanie zmian w zabezpieczeniach dostępu kodu.

 

Należy pamiętać o następujących najważniejszych kwestiach:

  • Przejrzystość oddziela kod uruchamiany jako część aplikacji od kodu uruchamianego jako część infrastruktury. Takie rozwiązanie wprowadzono w platformie .NET 2.0. Zostało ono rozwinięte i przekształcone w mechanizm egzekwowania zabezpieczeń dostępu kodu. W przeciwieństwie do zasad bezpieczeństwa, reguły poz. 2 przejrzystości (j.ang.) są egzekwowane w czasie wykonywania, a nie w czasie wczytywania zestawu. Reguły te obowiązują zawsze – nawet w przypadku zestawów domyślnie działających z pełnym zaufaniem. Jednak poz. 2 przejrzystości nie wpływa na kod w pełni zaufany i niemający adnotacji, np. na aplikacje komputerowe. Zestawy oznaczone atrybutem SecurityTransparentAttribute (j.ang.) i zestawy wywołujące metody oznaczone atrybutem SecurityCriticalAttribute (j.ang.) otrzymują wyjątek MethodAccessException (j.ang.). Można to zmienić, stosując atrybut SecurityRulesAttribute (j.ang.) i ustawiając wartość właściwości SecurityRulesAttribute.RuleSet (j.ang.) na Level1 (j.ang.). Należy to jednak robić tylko ze względu na zgodność wsteczną. Aby zastosować ograniczenia przejrzystości do aplikacji komputerowej, należy w jawny sposób oznaczyć ją jako przejrzystą pod względem zabezpieczeń.
  • Kod wywołujący interfejsy API zasad bezpieczeństwa otrzymuje w czasie wykonywania wyjątek NotSupportedException (j.ang.) oraz ostrzeżenia kompilatora. Zasadę można ponownie włączyć przy użyciu elementu konfiguracji <NetFx40_LegacySecurityPolicy> (j.ang.). Po włączeniu zasady przejrzystość zabezpieczeń nadal obowiązuje. Zasada bezpieczeństwa jest stosowana w czasie wczytywania zestawu i nie wpływa na przejrzystość, egzekwowaną w czasie wykonywania.
  • Przestarzałe żądania uprawnień (RequestMinimum (j.ang.), RequestOptional (j.ang.) i RequestRefuse (j.ang.)) otrzymują ostrzeżenia kompilatora i nie działają w platformie .NET 4, ale nie powodują wygenerowania wyjątku. Żądania Deny (j.ang.) powodują wywołanie wyjątku NotSupportedException (j.ang.) w czasie wykonywania.
  • Akcja zabezpieczeń LinkDemand (j.ang.) nie jest przestarzała, ale nie należy jej używać do weryfikowania uprawnień. Zamiast tego należy używać atrybutu SecurityCriticalAttribute (j.ang.) w przypadku typów i metod wymagających pełnych uprawnień lub metody Demand (j.ang.) w przypadku typów i metod wymagających uprawnień indywidualnych.
  • Jeśli aplikacja została utworzona w programie Visual Studio 2010, można uruchomić ją bez wprowadzania opisanych zmian, określając w ustawieniach projektu Visual Studio wersję platformy .NET starszą od .NET 4. W takim wypadku nie można jednak używać nowych typów i elementów platformy .NET 4. Można również określić wcześniejszą wersję platformy .NET, używając elementu <supportedRuntime> (j.ang.) w schemacie ustawień startowych w pliku konfiguracji aplikacji (j.ang.).

Uproszczenie zasad bezpieczeństwa


Poczynając od platformy .NET 4, aparat plików wykonywalnych języka wspólnego (CLR) przestaje być używany do zapewniania zasad bezpieczeństwa komputerów. Dotychczas w platformie .NET były stosowane zasady zabezpieczeń dostępu kodu (CAS) w roli mechanizmu ścisłej kontroli i konfigurowania możliwości zarządzanego kodu. Zasady CAS dają duże możliwości, ale mogą być skomplikowane i restrykcyjne. Co więcej, zasady CAS nie mają zastosowania do macierzystych aplikacji, tak więc zapewniane bezpieczeństwo jest ograniczone. Administratorzy systemu powinni rozważyć zastąpienie zasad CAS rozwiązaniem na poziomie systemu operacyjnego, takim jak Ograniczenia oprogramowania systemu Windows (j.ang.) (SRP) lub narzędzie AppLocker (j.ang.) w systemie Windows 7 i systemie Windows Server 2008 R2. Zasady SRP i AppLocker udostępniają proste mechanizmy zarządzania zaufaniem, mające zastosowanie zarówno do kodu zarządzanego, jak i macierzystego. Są to prostsze rozwiązania bezpieczeństwa, zapewniające wyższy poziom zabezpieczeń niż CAS.

W platformie .NET 4 zasady bezpieczeństwa dla całego komputera są domyślnie wyłączone. Aplikacje, które nie działają za pośrednictwem hostingu (tzn. aplikacje wykonywane za pośrednictwem Eksploratora Windows lub z wiersza poleceń) są obecnie uruchamiane z pełnym zaufaniem. Dotyczy to również aplikacji znajdujących się w folderach współużytkowanych w sieci lokalnej. Aplikacje udostępniane w hostingu lub działające w piaskownicy działają z ustawieniami zaufania zależącymi od hosta (np. przeglądarki Internet Explorer, rozwiązania ClickOnce lub funkcji ASP.NET). Aplikacje i formanty działające w piaskownicy są uznawane za częściowo zaufane.

W celu uproszczenia zasad bezpieczeństwa, w platformie .NET wdrożono model przejrzystości. Aplikacje i formanty działające na hoście lub w piaskownicy z ograniczonym zestawem uprawnień udostępnianych przez piaskownicę są uznawane za przejrzyste. Przejrzystość oznacza, że nie ma potrzeby sprawdzania zasad CAS przy uruchamianiu aplikacji częściowo zaufanych. Przejrzyste aplikacje są uruchamiane z przyznanym zestawem uprawnień. Programiści muszą jedynie upewnić się, że aplikacje działają poprawnie z przyznanym zestawem uprawnień i nie wywołują kodu wymagającego pełnego zaufania (kod krytyczny pod względem zabezpieczeń).

Ważne

W wyniku opisanych zmian w zasadach bezpieczeństwa mogą pojawić się ostrzeżenia kompilacji i wyjątki czasu wykonywania przy wywołaniu przestarzałych elementów członkowskich i typów zasad. Dotyczy to zarówno wywoływania jawnego, jak i niejawnego (za pośrednictwem innych typów i elementów członkowskich). Lista przestarzałych typów i elementów członkowskich oraz typów i elementów zastępujących jest dostępna w dokumencie Zasady zabezpieczeń dostępu kodu – zgodność i migracja (j.ang.).

Ostrzeżeń i błędów można uniknąć, używając elementu konfiguracji <NetFx40_LegacySecurityPolicy> (j.ang.) w schemacie ustawień startowych w celu włączenia starszego sposobu działania zasad CAS. Jednak nie obejmuje to niestandardowych zasad CAS dla danej wersji, o ile nie zostanie przeprowadzona migracja do platformy .NET 4.

Można również włączyć starsze zasady CAS, ustawiając w projekcie Visual Studio docelową wersję platformy .NET starszą niż .NET 4. Powoduje to korzystanie ze starszych zasad CAS i obejmuje niestandardowe zasady CAS określone dla danej wersji. W takim wypadku nie można jednak używać nowych typów i elementów platformy .NET 4. Można również określić wcześniejszą wersję platformy .NET, używając elementu <supportedRuntime> (j.ang.) w schemacie ustawień startowych.

Poz. 2 przejrzystości zabezpieczeń


Przejrzystość zabezpieczeń została wprowadzona w platformie .NET 2.0, była jednak bardzo ograniczona i służyła głównie do poprawy wydajności weryfikacji kodu. W platformie .NET 4 przejrzystość stanowi mechanizm egzekwowania i oddziela kod uruchamiany jako część aplikacji od kodu uruchamianego jako część infrastruktury. Przejrzystość tworzy barierę między kodem mogącym wykonywać działania wymagające wyższych uprawnień (kod krytyczny), takie jak np. wywoływanie kodu macierzystego, od kodu niemogącego wykonywać takich działań (kod przejrzysty). Przejrzysty kod może wykonywać polecenia możliwe z zestawem uprawnień, z jakim działa kod. Kod taki nie może wykonywać, wywoływać ani zawierać kodu krytycznego, ani z niego pochodzić.

Głównym celem egzekwowania przejrzystości jest zapewnienie prostego i skutecznego mechanizmu izolowania różnych grup kodu w oparciu o uprawnienia. W modelu z piaskownicą grupy te mają bądź to pełne uprawnienia (tzn. bez ograniczeń), bądź częściowe uprawnienia (tzn. ograniczone do zestawu uprawnień posiadanego przez piaskownicę).

Aplikacje komputerowe działają z pełnymi uprawnieniami, tak więc model przejrzystości ich nie dotyczy. Więcej informacji o zmianach w przejrzystości bezpieczeństwa można uzyskać w dokumencie Kod przejrzysty pod względem zabezpieczeń, poz. 2 (j.ang.).

Przestarzałe żądania uprawnień


Usunięto obsługę egzekwowania żądań uprawnień Deny (j.ang.), RequestMinimum (j.ang.), RequestOptional (j.ang.) i RequestRefuse (j.ang.) w czasie wykonywania. Działanie tych żądań było trudne do zrozumienia, a niewłaściwe ich użycie mogło stwarzać zagrożenie:

  • Akcja Deny (j.ang.) mogła zostać z łatwością zastąpiona akcją Assert (j.ang.). Jeśli odpowiednie uprawnienie znajdowało się w zestawie uprawnień, kod mógł wykonać akcję Assert (j.ang.). Akcja Assert (j.ang.) sprawiała, że akcja Deny (j.ang.) stawała się niewidoczna w stosie, co uniemożliwiało jej działanie.
  • Akcja RequestMinimum (j.ang.) nie mogła być prawidłowo używana poza zakresem aplikacji. Jeśli żądanie RequestMinimum (j.ang.) pojawiała się w pliku wykonywalnym (EXE), a odpowiedni zestaw uprawnień był niedostępny, plik otrzymywał nieobsłużony wyjątek FileLoadException (j.ang.), niezawierający informacji o sposobie usunięcia problemu. Nie można było używać pojedynczego, minimalnego zestawu uprawnień dla bibliotek (pliki DLL), ponieważ różne typy i elementy członkowskie mają z reguły różne wymagania
  • Akcja RequestOptional (j.ang.) działała w sposób mylący i często była używana nieprawidłowo, przynosząc nieoczekiwane wyniki. Deweloperzy mogli ominąć uprawnienia na liście, nie zdając sobie sprawy, że powoduje to w nieprzyznanie tych uprawnień.
  • Akcja RequestRefuse (j.ang.) nie zapewniała skutecznego działania modelu minimalnych uprawnień, ponieważ wymagała określania wszystkich niepożądanych uprawnień zamiast umożliwiać określenie jedynie wymaganych uprawnień. Ponadto w razie pojawienia się nowych uprawnień nie były one uwzględniane na liście. W niektórych przypadkach odrzucenie nie działało w sensowny sposób. Możliwe było np. odrzucenie wartości właściwości UserQuota (j.ang.) w uprawnieniu IsolatedStoragePermission (j.ang.).
  • I wreszcie określanie jedynie niepożądanych uprawnień mogło stwarzać zagrożenie w przypadku pominięcia niektórych z nich.
  • Akcje RequestOptional (j.ang.) i RequestRefuse (j.ang.) pozwalały deweloperom na dzielenie homogenicznych domen, polegające na tworzeniu w ich obrębie wielu zestawów uprawnień.

W platformie .NET usunięto 4 egzekwowanie w czasie wykonywania tych wartości wyliczenia. Zestawy zawierające atrybuty używające tych wartości akcji SecurityAction (j.ang.) będą nadal wczytywane, jednak aparat plików wykonywalnych języka wspólnego nie będzie odmawiał wczytania zestawów, do których są odwołania, ani modyfikowania ich uprawnień.

Warunkowy atrybut APTCA


Warunkowe używanie atrybutu AllowPartiallyTrustedCallersAttribute (j.ang.) (APTCA) umożliwia hostom określanie, które zestawy mają zostać udostępnione elementom wywołującym wczytywanym w kontekście hosta, mającym ograniczone zaufanie. Zestawy takie muszą być zaprojektowane z myślą o ograniczonym zaufaniu, tzn. muszą należeć do APTCA (zabezpieczone pod względem krytycznym w modelu przejrzystości) lub w pełni przejrzyste. Nowy konstruktor atrybutu AllowPartiallyTrustedCallersAttribute (j.ang.) umożliwia określenie przez hosta poziomu widoczności zestawu należącego do APTCA dzięki zastosowaniu wyliczenia PartialTrustVisibilityLevel (j.ang.) w wywołaniu konstruktora.

Obiekty świadectw


Przed wprowadzeniem platformy .NET 4 niemal każdy obiekt mógł zostać użyty jako obiekt świadectwa, jeśli było to określone w kodzie hostingu. Kod platformy .NET mógł np. rozpoznawać obiekty System.Uri (j.ang.) jako świadectwo. Środowisko wykonawcze uznawało obiekty za odwołania do klasy System.Object (j.ang.) i nie stosowało wobec nich żadnych zabezpieczeń.

To z kolei stanowiło problem, ponieważ platforma .NET narzucała w niejawny sposób ograniczenia typów obiektów, które mogły być używane jako obiekty świadectw. Każdy obiekt używany jako świadectwo musiał być możliwy do serializacji i nie mógł być zerowy. W razie niespełnienia tych wymagań aparat plików wykonywalnych języka wspólnego zwracał wyjątek za każdym razem, gdy była wykonywana operacja wymagająca jednego z tych założeń.

Aby umożliwić ograniczenie typów obiektów, których można używać jako świadectw oraz pozwolić na dodawanie nowych funkcji i wymagań do wszystkich obiektów świadectw, w platformie .NET 4 wprowadzono nową klasę bazową – System.Security.Policy.EvidenceBase (j.ang.). Wszystkie obiekty świadectw muszą pochodzić z tej klasy. Klasa EvidenceBase (j.ang.) zapewnia (po utworzeniu wystąpienia), że obiekt świadectwa jest możliwy do serializacji. Ponadto w przyszłości można tworzyć nowe obiekty świadectw, dodając nowe domyślne implementacje do klasy bazowej.

Zgodność wsteczna

Wszystkie typy używane przez aparat plików wykonywalnych języka wspólnego jako obiekty świadectwa zostały w platformie .NET 4 zaktualizowane w taki sposób, aby pochodziły z klasy EvidenceBase (j.ang.). Jednak niestandardowe typy świadectw używane przez aplikacje innych firm nie są rozpoznawane i nie mogą być aktualizowane. Dlatego takich typów świadectw nie można używać z nowymi elementami członkowskimi, które wymagają świadectw pochodzących z klasy EvidenceBase (j.ang.)

Kolekcje świadectw


We wcześniejszych wersjach platformy .NET 4 aparat plików wykonywalnych języka wspólnego generował wszystkie obiekty świadectw mające zastosowanie w zestawie podczas wczytywania zestawu. Obiekty te były przechowywane na liście, z której następnie wybierany był właściwy obiekt. Tak więc dostępne były wszystkie świadectwa, niezależnie od tego, czy były one używane. Przeważnie nie stanowiło to problemu, jednak w przypadku takich obiektów, jak System.Security.Policy.Publisher (j.ang.) (wymagającego uwierzytelnienia Authenticode) było to mało wydajne.

W celu usprawnienia działania w platformie .NET 4 zmodyfikowano interakcję z kolekcją świadectw. Kolekcja świadectw działa obecnie nie jak lista, ale jak słownik. Zamiast przechodzić przez kolekcję świadectw, aby sprawdzić, czy wymagany obiekt świadectwa istnieje, można zażądać określonego typu świadectwa, a kolekcja zwróci je, o ile takie świadectwo istnieje. Tak więc np. wywołanie StrongName name = evidence.GetHostEvidence<StrongName>(); zwróci obiekt StrongName (j.ang.), o ile taki obiekt istnieje. Jeśli taki obiekt nie istnieje, zostanie zwrócona wartość zerowa.

Model słownikowy opóźnia generowanie obiektów świadectw, dopóki nie zostanie wysłane odpowiednie żądanie. W przykładzie z obiektem świadectwa Publisher (j.ang.) podpis Authenticode zestawu jest weryfikowany dopiero, kiedy odpowiednie informacje są potrzebne, co poprawia wydajność. W najczęstszym przypadku, tzn. kiedy uruchamiane są aplikacje o pełnym zaufaniu i świadectwo Publisher (j.ang.) nie jest wymagane, proces weryfikacji jest całkowicie pomijany.