Category Archives: Bezpieczeństwo

Podpis cyfrowy oraz weryfikacja pakietu

W .NET bardzo łatwo podpisać cyfrowo jakiś pakiet. Przeważnie korzystając z WCF czy z innych technologii jest już to wykonane za nas w ramach framework’a. Bardzo często jednak pisząc aplikacje, chcemy zapewnić integralność danych. Za pomocą kilku linii kodu można tego dokonać. Przede wszystkim warto przyjrzeć się klasom RSACryptoServiceProvider  oraz DSACryptoServiceProvider. Obie posiadają one metody takie jak SignData oraz VerifyData.

Przyjrzymy się najpierw samemu podpisowi:

private static byte[] SignData(byte[] data,out RSAParameters cspParameters)
{
  var rsaCryptoServiceProvider=new RSACryptoServiceProvider();
  cspParameters = rsaCryptoServiceProvider.ExportParameters(false);
  return rsaCryptoServiceProvider.SignData(data, new SHA1Managed());            
}

Co metoda SignData robi? Najpierw wykonuje funkcję haszującą na danych, za pomocą algorytmu przekazanego jako drugi parametr. Potem używając klucza prywatnego, szyfruje dany hash. Zaszyfrowany kluczem prywatnym skrót stanowi podpis. Następnie możemy sprawdzić, czy przekazane nam dane, rzeczywiście pokrywają się z podpisem:

private static bool VerifyData(byte[] data, byte[] signature, RSAParameters cspParameters)
{
  var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
  rsaCryptoServiceProvider.ImportParameters(cspParameters);
  return rsaCryptoServiceProvider.VerifyData(data, new SHA1Managed(), signature);
}

Aby zweryfikować integralność danych należy najpierw wyliczyć skrót przekazanego pakietu. Następnie, korzystając z klucza publicznego, odszyfrowujemy podpis. Jeśli podany hash (z podpisu) pokrywa się z tym co sami wyliczyliśmy to znaczy, że pakiet nie został po drodze zmodyfikowany oraz należy do tej osoby, która wydała nam klucz publiczny.

Całość:

private static void Main(string[] args)
{
  string text = "Hello World";
  byte[] data = Encoding.ASCII.GetBytes(text);

  RSAParameters rsaParameters;
  byte[] signature = SignData(data, out rsaParameters);
  Console.WriteLine(VerifyData(data, signature, rsaParameters));
}
private static bool VerifyData(byte[] data, byte[] signature, RSAParameters cspParameters)
{
  var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
  rsaCryptoServiceProvider.ImportParameters(cspParameters);
  return rsaCryptoServiceProvider.VerifyData(data, new SHA1Managed(), signature);
}

private static byte[] SignData(byte[] data,out RSAParameters cspParameters)
{
  var rsaCryptoServiceProvider=new RSACryptoServiceProvider();
  cspParameters = rsaCryptoServiceProvider.ExportParameters(false);

ASP.NET MVC – przekierowanie do powrotnej strony, luka w bezpieczeństwie

Ciekawostka: http://www.asp.net/mvc/tutorials/security/preventing-open-redirection-attacks

W ASP.NET MVC 1.0 oraz 2.0 istnieje pewna luka, umożliwiająca przeprowadzanie ataku phishing. Zwykle returnUrl w QueryString zawiera powrotny adres, ale można go zmienić na dowolny a ASP.NET MVC 1.0 i 2.0 nie sprawdza czy link należy do tej samej domeny. Więcej informacji w powyższym linku.

Czym jest “Delayed signing”?

W ostatnim poście napisałem kiedy warto korzystać z strong-name. Jeśli ktoś uważnie prześledził screen’y dołączone do postu, być może dostrzegł, że jest tam opcja taka jak “Delayed Signing”. Do czego to służy?

Sprawa jest bardzo prosta. Delayed signing po prostu nie używa klucza prywatnego. Taka biblioteka nie zawiera więc poprawnego podpisu cyfrowego a w miejsce jego zawiera same zera. Klucz publiczny z kolei jest wstawiany do biblioteki z tym, że nie jest liczony jej hash. Integralność zatem jest niemożliwa (brak hash’u) jak również prawidłowa autoryzacja biblioteka (brak podpisu). Po za tym, biblioteka zachowuje się jak SN więc możliwe jest umieszczenie jej w GAC’u.

Po co nam biblioteka bez podpisu cyfrowego? W dużych organizacjach dostęp do klucza prywatnego ma wyłącznie klika osób. Programiści nie mają dostępu do niego na swoich maszynach deweloperskich. Podpisanie biblioteki odbywa się na osobnych maszynach przez osoby do tego powołane. Żaden z programistów nie może tego zrobić lokalnie na swoim komputerze – klucz prywatny musi być przechowywany w bezpiecznym miejscu. Programiści jednak muszą implementować a potem testować swoje rozwiązania. Jeśli wymagane jest użycie biblioteki SN nie ma sensu za każdym razem odsyłać ją  do osoby mającej potrzebne uprawnienia. Z tego względu programiści używają Delayed Signing w czasie implementacji a dopiero na końcu np. przed wydaniem lub fazą testowania produktu dokonywane jest podpisanie. Delayed signing zatem ma znaczenie wyłącznie dla programistów – jest to taka wersja “DEBUG” ułatwiająca prace nad kodem.

Jest jeszcze jeden mniej ważny powód dla którego Delayed signing może okazać się przydatne. Osoby korzystające z obfuscator’a nie mogą używać go na bibliotece już podpisanej. Jeśli odpalą obfuscator’a na takiej bibliotece wtedy oczywiście zostaną zmienione nazwy wszystkich zmiennych a to poskutkuje niepoprawną sumą kontrolną – po załadowaniu okaże się, że wyliczony hash jest inny niż ten zawarty w podpisie cyfrowym. W przypadku Delayed Signing można najpierw wygenerować bibliotekę SN, odpalić na niej obfuscator a na końcu ją podpisać.

Delayed signing można ustawić za pomocą zakładki signing znajdującej się we właściwościach projektu:

image

Niestety to nie wystarczy. Po uruchomieniu projektu lub próbie zainstalowania go w GAC dostaniemy następujący wyjątek:

“Could not load file or assembly ‘ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=93b3674e817090ba’ or one of its dependencies. Strong name validation failed. (Exception from HRESULT: 0x8013141A)”

Oczywiście jest ro związane z faktem, że hash jest pusty (same zera) i naturalnie nie pokrywa to się z rzeczywistą sumą kontrolną. Z tego względu musimy powiadomić .NET Framework aby nie dokonywał on wspomnianej walidacji:

sn –Vr ClassLibrary1.dll
Polecenie tak naprawdę doda klucz rejestru do: HKEY_LOCAL_MACHINE\SOFTWARE.

Załóżmy, że implementacja została zakończona i kolejnym krokiem jest już podpisanie biblioteki za pomocą klucza prywatnego. Wtedy należy użyć następującego polecenia:

sn -R ClassLibrary.dll sgKey.snk

Po tym biblioteka zostanie już w pełni SN – z sumą kontrolką i podpisem cyfrowym. Warto jednak na zakończenie usunąć bibliotekę z rejestru plików nie sprawdzanych pod kątem integralności:

SN –Vu ClassLibrary.dll

Należy uważać na sn  – Vr ponieważ wiąże się z tym ryzyko – w końcu podpis cyfrowy jest nie sprawdzany i ktoś mógłby podmienić bibliotekę na inną, zawierającą złośliwy kod.

Biblioteki Strong-Named

Nie wiem czy jest polski odpowiednik strong-named(SN) wiec będę używał nazwy angielskiej albo skrótu SN. Do zrozumienia tego wpisu niezbędna jest podstawowa wiedza o asymetrycznych algorytmach szyfrowania (RSA), podpisie cyfrowym , kluczu prywatnym, publicznym oraz zasadzie działania funkcji haszującej. Nie będę tego omawiał w poście, zachęcam więc do zapoznania się na własną rękę jeśli któreś z tych pojęć jest niezrozumiałe.

SN to przede wszystkim sposób na unikalne identyfikowanie bibliotek. Przed pojawieniem się SN trudno było odróżnić dwie różne biblioteki o tej samej nazwie. Kiedyś wszystkie biblioteki składowane były w jednym folderze co powodowało tzw. DLL HELL. W końcu może zdarzyć się, że dwie różne firmy wydają biblioteki o takiej samej nazwie. Inny scenariusz to biblioteki o tej samej nazwie ale o różnych wersjach. Na przykład aplikacja A może być kompatybilna  z wersją wyłącznie 1.00 a program B wyłącznie z wersją 2.00. Mowa oczywiście tutaj o bibliotekach wdrażanych globalnie czyli np. GAC.

Kolejna korzyść z SN to bezpieczeństwo. O tym z pewnością powstaną następne wpisy ale spróbuje to naszkicować dzisiaj. Przede wszystkim biblioteka strong-named jest podpisana i dzięki temu mamy pewność, że pochodzi ona od konkretnego producenta. Podpis gwarantuje integralność biblioteki – przed załadowaniem jej sprawdzana jest suma kontrolna. Jeśli ktoś ją po drodze zmodyfikować (np. hacker) wtedy będzie to wykryte podczas jej ładowania. Dzięki temu można również określić różne przywileje wykonania kodu w zależności kto jest właścicielem biblioteki.

Wiemy, już po co wprowadzano SN (łatwa identyfikacja oraz bezpieczeństwo). Jak wspomniałem ważną role w SN stanowią algorytmy szyfrowania asymetrycznego oraz funkcje haszujące. Cztery atrybuty identyfikują unikalnie bibliotekę:

  1. Nazwa biblioteki (bez rozszerzenia).
  2. Wersja
  3. Culture (np. en-US).
  4. Hash klucza publicznego. Sam klucz publiczny jest bardzo długi dlatego stosuje się po prostu jego hash.

Przykład:

MyLib, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=d375d1c535425e6

Aby biblioteka była SN należy wykonać następujące kroki:

  1. Wygenerować dwa klucze: publiczny oraz prywatny. To one  w skali globalnej identyfikują biblioteki. Powinny być unikalne dla każdej organizacji\firmy.
  2. Klucz publiczny będzie umieszczony w bibliotece. W końcu użytkownik końcowy musi jakoś odszyfrować potem pakiet. W kryptografii klucz publiczny otrzymuje odbiorca a prywatny jest poufny i ma do niego dostęp tylko nadawca (w tym przypadku firma\organizacja). Po umieszczeniu klucza publicznego, biblioteka jest haszowana za pomocą algorytmu SHA-1. Następnie hash jest szyfrowany algorytmem asymetrycznym RSA za pomocą klucza prywatnego. Zaszyfrowany hash stanowi podpis biblioteki. Oczywiście jest to skrót myślowy ponieważ w rzeczywistości szyfrowane są specyficzne pliki ale aby zrozumieć sens SN wystarczy wiedzieć, że wygenerowany zostanie podpis cyfrowy czyli zaszyfrowanie hash’a biblioteki.
  3. Wygenerowany w kroku 2 podpis jest umieszczany również w bibliotece.

Następnie gdy użytkownik próbuje odczytać bibliotekę (wywołać jakąś metodę):

  1. Załadowanie klucza publicznego.
  2. Obliczenie hash’u biblioteki.
  3. Załadowanie podpisu. Odszyfrowanie go za pomocą klucza publicznego.
  4. Wynik z punktu 3 porównywany jest z hash’em obliczonym w punkcie 2. Jeśli są one takie same to znaczy, że biblioteka nie została podmieniona. Gwarantuje to integralność danych.

Na tym etapie, wiemy, już co to znaczy dostarczyć bibliotekę strong-named. Pytanie pozostaje, jak to zrobić?

Istnieje kilka sposobów. Jednym z nich jest użycie komendy SN w wierszu poleceń. Umożliwia to wygenerowanie pary kluczy a nawet wyodrębnienie z pary tylko klucza publicznego.

Osobiście nie lubię wiersza poleceń i dlatego w poście pokażę jak to zrobić z poziomu Visual Studio. Wystarczy przejść do właściwości projektu a następnie wybrać zakładkę Signing:

image

Następny zaznaczamy checkbox Sign the assembly, klikany new  i wpisujemy nasze hasło:

image

Po skompilowaniu biblioteka będzie zawierała Strong Name. Można się o tym przekonać dołączając ją do jakiegoś projektu i sprawdzając właściwość StrongName:

image

Na zakończenie jeszcze kilka słów o integralności. Załóżmy, że ktoś zmienił zawartość biblioteki, doczepiając jakiś złośliwy kod. Dzięki SN wykryjemy to podczas ładowania ponieważ w takim przypadku hash będzie inny. Jeśli ktoś zmieni klucz publiczny również wykryjemy to ponieważ hash został obliczony na bibliotece zawierającej w sobie już  klucz publiczny. Mechanizm gwarantuje nam, że dostaniemy identyczną zawartość, która została podpisana przez wydawcę. W następnym poście napiszę o Delay Singing a potem o sprawach bezpieczeństwa (konkretnie o nadawaniu pozwoleń).

ASP .NET Membership w WinForms.

ASP .NET Membership jest bardzo dobrym rozwiązaniem dla uwierzytelnienia oraz autoryzacji użytkowników w aplikacji webowej. Umożliwia m.in. zarządzanie kontami użytkowników czy tworzenie ról. Często jednak system składa się również z aplikacji typu desktop. W rozbudowanych systemach proponuję zrezygnować z czystego ASP .NET Membership na rzecz Windows Identity Framework. Jeśli jednak tworzymy małą aplikacje z pewnością przyda nam się wsparcie ASP .NET Membership w WinForms.

Rozwiązaniem problemu są tzw. client application services. Zacznijmy jednak od samego początku:

  1. Najpierw tworzymy oczywiście aplikację web ASP .NET.
  2. Oprócz autoryzacji istnieje również możliwość udostępniania właściwości aplikacji WinForms. Dodajemy przykładową właściwość:
    <profile>
      <properties>
          <add name="Color" type="string" defaultValue="Blue"/>
        </properties>
    </profile>
    ...
    </system.web>

  3. W celu udostępnienia właściwości Color wystarczy w w web.config dodać następujący wpis:
    <system.web.extensions>
        <scripting>
            <webServices>
                <authenticationService enabled="true" />
                <roleService enabled="true"/>
                <profileService enabled="true" readAccessProperties="Color" writeAccessProperties="Color"/>
            </webServices>        
        </scripting>
    </system.web.extensions>

Powyższy kod również udostępnia usługę uwierzytelnienia oraz zarządzania rolami.

Następnym etapem jest konfiguracja aplikacji WinForms:

  1. Tworzymy aplikacje typu WindowsForms.
  2. Wchodzimy we właściwości projektu a konkretnie w zakładkę Services. Następnie ustawiamy prawidłowy adres. Jeśli zakłada nie jest aktywna to prawdopodobnie  został ustawiony .NET Framework Client Profile i należy ustawić np. NET Framework 4.0.

    image 

  3. Wchodzimy w zakładkę Settings oraz klikamy w przycisk Load Web Settings. Po wpisaniu loginu i hasła zostaną zwrócone udostępnione właściwości:

image 

Od tej pory w kodzie aplikacji WinForms możemy odwoływać się do właściwości jak do zwykłych zasobów, np:

public Form1()
{
  InitializeComponent();
  if(Login())
    this.BackColor = Color.FromName(Properties.Settings.Default.Color) ;
}
private bool Login()
{
  bool result = System.Web.Security.Membership.ValidateUser("admin", "password");
  if (result)
      MessageBox.Show("Zalogowano.", "Autoryzacja", MessageBoxButtons.OK,MessageBoxIcon.Information);
  else
      MessageBox.Show("Nieprawidłowy login lub hasło.", "Autoryzacja", MessageBoxButtons.OK,MessageBoxIcon.Error);

  return result;
}

Podobnie można zapisywać właściwość:

Properties.Settings.Default.Color = Color.Red.ToString();
Properties.Settings.Default.Save();

W celu przekonania się, że to naprawdę działa ustawmy kolor również w aplikacji web:

protected void Page_Load(object sender, EventArgs e)
{                                        
  Button1.BackColor = System.Drawing.Color.FromName(Context.Profile.GetPropertyValue("Color").ToString());
}

Oczywiście aby kod zadziałał użytkownik musi być zalogowany (np. za pomocą gotowej kontrolki Login).

W podobny sposób można używać metody InRole (sprawdzanie czy użytkownik należy do danej grupy). Warto podkreślić, że za pomocą client application service można korzystać z Membership również w aplikacjach WPF, AJAX czy nawet Silverlight.

Przechowywanie poufnych informacji

W kodzie często używamy klasy Stirng do przechowywania poufnych informacji takich jak np. hasło. Niestety często nie zdajemy sobie sprawy jak niebezpieczne jest takie rozwiązanie. String jest specjalnie zoptymalizowaną klasą przeznaczoną do przechowywania łańcucha znaków, która jednak nie jest odporna na wszelkie ataki związane z podglądaniem pamięci operacyjnej. W skrócie pisząc, wszelkie informacje klasa String trzyma w postaci jawnej. Korzystając więc z odpowiedniego oprogramowania, intruz może bez problemu podejrzeć co aplikacja trzyma w klasie String. Ponadto ze względów na wspomnianą optymalizację String posiada następujące wady(w kwestii bezpieczeństwa):

  1. Brak możliwości wyczyszczenia bufora String. Nawet gdy przypiszemy wartość NULL, napis pozostaje wciąż zapisany w pamięci.
  2. Garbage Collector może robić kopie wartości String. Napisy nie są przechowywane ciągle pod tym samym adresem.
  3. Brak jakiejkolwiek metody szyfrowania danych.

W środowisku .NET rozwiązaniem na powyższe problemy jest klasa System.Security.SecureString. Moim zdaniem jest dosyć niewygodna w użyciu, jednak i tak wartość z niej korzystać.

Przykład zapisania do SecureString wartości podanej przez użytkownika(4 znaki):

SecureString secureString = new SecureString();

secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);

Następnie powinno się zablokować dostęp do wszelkich modyfikacji:

secureString.MakeReadOnly();

Wszelkie próby modyfikacji od tej chwili zakończą się wyrzuceniem wyjątku InvalidOperationException.

RSA oraz faktoryzacja klucza 768 bitowego

Dopiero dzisiaj przypadkiem napotkałem na dokument przedstawiający łamanie klucza 768 bitowego. Zainteresowanych odsyłam tu.

Nie jest to żaden przełom ponieważ w praktyce stosuje się 1024 bitowe klucze, które są znacznie trudniejsze w złamaniu(jak to zresztą podkreślają autorzy powyższej pracy). Wiadomość stanowi raczej ciekawostkę.  Z drugiej strony jednak, rozwój technologiczny prawdopodobnie w przeciągu 1-2 dekad pozwoli łamać 1024 bitowe klucze i konieczne okaże się wykorzystywanie kluczy 2048 bitowych.

Autorzy(m.in. Microsoft Research) eksperymentu wykorzystali klaster do złamania klucza. Dla porównania korzystając z jednordzeniowego procesora 2.2ghz operacja trwałaby 1500 lat. Nadal jedynym sposobem na złamanie klucza są metody wywodzące się z brute-force, więc na dzień dzisiejszy RSA jest wciąż bezpiecznym algorytmem.