Narzędzia osobiste
Start > Aktualności > Security blog > Bezpieczeństwo .NET

Bezpieczeństwo .NET

Przegląd po wybranych aspektach bezpieczeństwa .NET

Od redakcji ;-)

Autorem poniższego postu jest Piotr Łaskawiec. Serdecznie dziękujemy Piotrowi za umożliwienie nam publikacji tekstu :-) Jeśli ktoś chciałby opublikować swój tekst na naszym security blogu, gorąco do tego zachęcam (wystarczy wysłać e-maila na adres security@webservice.pl). 
--Michał Sajdak


 

Aplikacje pisane w środowisku .NET mają opinię bezpiecznych i pozbawionych błędów programów. Oczywiście końcowe bezpieczeństwo programów zależy od programisty, procesu wdrożenia do użytku i etapu projektowania, ale .NET zapewnia cały szereg ułatwień i mechanizmów nadzorujących pracę developera i eliminujących możliwość wystąpienia błędów. Jednak nawet najbardziej zaawansowana technologicznie platforma nie jest w stanie zagwarantować pełnego bezpieczeństwa.

W wyniku ciągłego wzrostu popularności platformy .NET, nie tylko jako narzędzia do tworzenia aplikacji B2B, ale także jako środowiska będącego fundamentem wielu stron czy portali internetowych warto znać podstawowe błędy popełniane przez programistów i metody ich wykorzystywania.

Słowem wstępu

.NET Framework jest platformą programistyczną stworzoną jako odpowiedź na język Java. W jego skład wchodzi środowisko uruchomieniowe i cały zestaw wyspecjalizowanych bibliotek. Środowisko uruchomieniowe odpowiedzialne jest za zarządzanie pamięcią i obiektami oraz zapewnia nam możliwość uruchamiania programów na różnorodnych platformach sprzętowych i systemach operacyjnych. Co więcej, programiści piszący „pod .NET” nie są zmuszeni do korzystania z jednego języka. Framework umożliwia korzystanie z całej gamy współpracujących języków takich ja C#, Visual Basic .NET, Python (IronPython) czy zarządzany C++.

.NET można także traktować jako zbiór wielu technologii przeznaczonych do różnych celów. W tym artykule poruszane będą głównie tematy związane z technologią ASP.NET. ASP.NET jest frameworkiem wchodzącym w skład platformy .NET i pozwalającym na pisanie dynamicznych stron internetowych i aplikacji sieciowych.

Iluzoryczne bezpieczeństwo

Jak już wiemy cała platforma .NET bazuje na środowisku uruchomieniowym, które powinno zabezpieczać przed takimi błędami jak przepełnienie bufora czy nieprawidłowe wykorzystanie ciągów formatujących. CLR (Common Language Runtime) nie pozwala na wykonanie dowolnego kodu w wyniku wystąpienia błędów typu Buffer Overflow, ale tylko i wyłącznie podczas pracy z zarządzanym kodem. W wielu aplikacjach pisanych pod .NET łączy się kod zarządzany z wywołaniami WinAPI, komponentami COM czy kontrolkami ActiveX, które nie podlegają kontroli. Wiele osób o tym zapomina i w efekcie tworzy programy podatne na błędy związane z nieprawidłowym zarządzaniem pamięcią. W wyniku użycia elementów niezarządzanych, całościowe bezpieczeństwo aplikacji zostaje zredukowane. Fragmenty kodu podatne na ataki typu „Buffer overflow” czy „Format string vulnerability” stają się najsłabszymi ogniwami programu i nie są monitorowane przez CLR. Należy o tym pamiętać podczas tworzenia aplikacji oraz mieć pewność, że kod niezarządzany używany w naszej aplikacji jest bezpieczny i pozbawiony jakichkolwiek wad mogących być przyczyną włamania.

Pokaż mi kod a powiem Ci kim jesteś

Kolejna ważna cecha platformy .NET związana jest z procesem kompilacji kodu. Kod źródłowy aplikacji zamieniany jest na kod wykonywalny, zapisany w języku pośrednim MSIL (Microsoft Intermediate Language). MSIL zawiera cały szereg metadanych opisujących typy obiektów, implementowane interfejsy, zdarzenia itd. W czasie wykonywania aplikacji, Common Language Runtime tłumaczy kod MSIL na kod maszynowy procesora, na którym wykonywana jest aplikacja. Operacja ta nazywana jest „JIT – Just-in-time compilation”. Oprócz niewątpliwych zalet takiego rozwiązania (przenośność kodu, współpraca z różnymi architekturami, w pełni zarządzany kod) istnieją także wady… a szczególnie jedna najpoważniejsza. MSIL bardzo łatwo zamienić z powrotem na kod źródłowy za pomocą dekompilatorów. Dzięki informacjom przechowywanym w MSIL jest to proces łatwy, a efekt pracy dekompilatora jest w większości przypadków zbliżony do oryginalnego kodu źródłowego aplikacji. Aby zobrazować ryzyko płynące z tego typu błędów warto posłużyć się przykładem. Załóżmy, że mamy prosty program, w którym zapisane jest hasło. Hasło jest sprawdzane dopóki użytkownik nie poda odpowiedniego ciągu znaków. Kod naszego programiku wygląda następująco:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace obftest

{

    class Program

    {

        static string password = "Verysecurepassword";

        static void Main(string[] args)

        {

            Console.WriteLine("Type the password...");

            while (Console.ReadLine() != password)

                Console.WriteLine("Bad password!");

            Console.WriteLine("Gratz! Password is fine!");

        }

    }

}

 

Przyjmijmy, że jest to fragment większej aplikacji a powyższe kilka linijek przedstawia proces uwierzytelniania użytkowników. Będąc w posiadaniu już skompilowanego programu jesteśmy w stanie bezproblemowo odtworzyć kod źródłowy i poznać wewnętrzną logikę aplikacji. W sieci istnieje wiele dekompilatorów dla platformy .NET - .NET Reflector, Dis# czy Salamander .NET Decompiler. Sprawdźmy jakie wyniki otrzymamy po dekompilacji naszego przykładowego programu.

Wynik działania programy .NET Reflector:

namespace obftest

{

    class Program

    {

        static string password = "Verysecurepassword";

        static void Main(string[] args)

        {

            Console.WriteLine("Type the password...");

            while (Console.ReadLine() != password)

                Console.WriteLine("Bad password!");

            Console.WriteLine("Gratz! Password is fine!");

        }

    }

}

Wynik jest dość jednoznaczny. Uzyskaliśmy identyczny kod źródłowy. W przypadku gdy przechowujemy w kodzie wrażliwe informacje użycie dekompilatora doprowadzi do ich przejęcia. Łatwo sobie wyobrazić sytuację, w której po uruchomieniu dekompilatora włamywacz staje się posiadaczem hasła do bazy danych czy identyfikatorów użytkowników w systemie logowania. Aby zaradzić takiej sytuacji musimy zmienić styl pisania programów i zmniejszyć prawdopodobieństwo pozyskania danych przez potencjalnego napastnika. Oczywiście, najprostszym wyjściem jest przetrzymywanie wrażliwych danych poza kodem, ale nie zniweluje to ryzyka poznania logiki programu. Możemy (a nawet powinniśmy) skorzystać z szyfrowania nazw użytkowników oraz haseł ale takie rozwiązanie jest nieefektywne w odniesieniu do adresów internetowych itd.

Z pomocą przychodzą nam programy, które zamieniają kod wynikowy na mniej czytelny i nie pozwalają na łatwe przechwycenie danych - obfuscatory. Nawet z najprostszych kawałków kodów tworzą one nieczytelną plątaninę i skutecznie utrudniają życie atakującemu. Prześledźmy jak wygląda nasz testowy program po przepuszczeniu go przez wbudowany w Visual Studio (popularne IDE Microsoftu) obfuscator (z uwzględnieniem opcji „string encryption”). Wynik powinien być zbliżony do tego listingu:

using System;

 

internal class a

{

    private static string a = "\u246d\u112a\u213k\u1128…";

 

    private static void a(string[] A_0)

    {

        Console.WriteLine("\u1272\u1273…");

        while (Console.ReadLine() != a)

        {

            Console.WriteLine("\u246l\u2478t\u3433…");

        }

        Console.WriteLine("\u1234\u1289\u1273…");

    }

}

Można zauważyć znaczne zmniejszenie się czytelności kodu. Teraz pozyskanie informacji nie jest już tak banalnie proste. Jednak bezpieczeństwo komputerowe bazuje na kompromisach. Obfuscatory kodujące łańcuchy tekstowe zawsze umieszczają w programie wynikowym funkcję, która pozwoli na późniejsze odkodowanie znaków. Funkcja ta jest umieszczana na stosie wywołań przed pierwszym wystąpieniem zakodowanych łańcuchów. Nic nie stoi więc na przeszkodzie aby przy odrobienie wysiłku dostać się do wrażliwych danych.

Jednak odpowiedni styl programowania, dobre umiejscowienie danych, szyfrowanie i użycie obfuscatorów powinno skutecznie odstraszyć leniwego włamywacza.

Szyfrowanie plików web.config

Web.config jest plikiem używanym podczas tworzenia stron opartych o technologię ASP.NET. W pliku tym znajdują się informacje konfiguracyjne sterujące pracą programu, łańcuchy tekstowe umożliwiające połączenie z bazą danych oraz szereg innych ważnych danych. Pliki web.config przechowywane są w głównym katalogu danej aplikacji, ale mogą także występować w katalogach podrzędnych (przesłaniając wtedy ustawienia głównego pliku konfiguracyjnego). Uzyskanie dostępu do tego pliku, w wyniku włamania lub błędu serwera, przez osobę niepowołaną praktycznie kompromituje całą aplikację. Wiedza zgromadzona przez włamywacza byłaby na tyle duża, że pozwoliłaby na przejęcie kontroli nad programem. Warto więc poświęcić czas na zaznajomienie się z techniką uniemożliwiającą efektywne wykorzystanie zdobytych informacji. Rozwiązaniem tym jest szyfrowanie newralgicznych sekcji pliku web.config. Przyjrzyjmy się najważniejszym miejscom  tego pliku:

<?xml version="1.0"?>

<configuration>

      <configSections>

      …

      </configSections>

      <appSettings>

      </appSettings>

      <connectionStrings>

      …

      </connectionStrings>

      <system.net>

      …

      </system.net>

      <system.web>

      …

      </system.web>

      <system.webServer>

      </system.webServer>

      …

</configuration>

 

Oczywiście jest to tylko skrócona wersja pliku Web.config, nie zawierająca żadnych danych. Warto jednak zwrócić uwagę na znaczniki <appSettings>, <connectionStrings> oraz  <system.net>. To właśnie w tych sekcjach przechowywane są informacje przydatne z punktu widzenia włamywacza. Mogą to być np. informacje wymagane do nawiązania połączenia z bazą danych:

<connectionStrings>

    <add name="example" connectionString="Data Source=localhost;Initial Catalog=exampledb;User ID=root;Password=toor" providerName="System.Data.SqlClient"/>

</connectionStrings>

 

ustawienia przechowywane w web.config i wykorzystywane w kodzie poprzez odpowiednie odwołania:

<appSettings>

    <add key="From" value="root@localhost.com"/>

    <add key="Subject" value="Email schema!"/>

    <add key="SmtpServer" value="localhost"/>

    <add key="MailUser" value="example"/>

    <add key="MailPassword" value="example"/>

    <add key="MailPort" value="25"/>

    <add key="EmailFormat" value="Text"/>

</appSettings>

 

lub też ustawienia zewnętrznych serwerów (np. SMTP):

 

<system.net>

            <mailSettings>

                  <smtp from=”root@localhost.com”>

<network host="localhost" password="example" userName="example"/>

                  </smtp>

            </mailSettings>

</system.net>

 

Każdy z tych przykładów przedstawia informacje, do których nie powinny mieć dostępu osoby nieuprawnione. Aby utrudnić odczyt danych (które przechowywane są w formie jawnej) należy zastosować szyfrowanie za pomocą narzędzi oferowanych przez .NET. Użyjemy programu aspnet_regiis znajdującego się w katalogu domowym Frameworka (domyślnie jest to C:\Windows\Microsoft.NET\Framework\v2.0.50727). Posiada on opcję szyfrowania wybranych sekcji pliki Web.config. Składnia polecenia wygląda następująco:

aspnet_regiis –pef „nazwa_sekcji” „pełna ścieżka do katalogu, w którym znajduje się plik Web.config”

bezpieczenstwonet3

Po uruchomieniu naszego programu dla sekcji connesctionStrings, plik Web.config będzie zawierał następujący wpis:

<connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider">

    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"

      xmlns="http://www.w3.org/2001/04/xmlenc#">

      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />

      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">

          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />

          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

            <KeyName>Rsa Key</KeyName>

          </KeyInfo>

          <CipherData>

<CipherValue>E+cdLBak7iXnNSez6EvKToVIv+QB9+4WnTQwCRf05P9SA6U4pje9EOyGAlQvuMCSCioXX1tfHE4ldVHoE2i3DAmUuF3HUL6nMsCvl6bPRnGxMrBieLT0oWeYX9gTEAV+zDJS67HQgOcn0FK1ZI6CRma+2CXtSGGhfd8WxhPxkpY=</CipherValue>

          </CipherData>

        </EncryptedKey>

      </KeyInfo>

      <CipherData>

<CipherValue>rQzgfu1CufYM46+DUp8kzO88mXyOvZ3sNU+6Fs/QBOH8FeQUiWq6T0m3qTMFAf7XUkS1BpjLN6PuoTqDqIi7r1d40AG3a4vCbGJ9SJWv45hbGj8Z7Dge05SW2p5qtloMq33TKfuDK6MG2IqDy6R8w4USOghMOCdEqkmpVihWVJG54/Kg9yonM7xgQF+zI1D8jFhV3JZ1hcvwE28Z/LT79bk+EOlAAH++ILXVB9YUcvJkr9gM6psDNcixg/MO0Z9GozNhgsWwb30K5MerR72o0JCLHDNijFjKtCBr7OOZArmHLU1h4hjcGWhlR6tZtkZW</CipherValue>

      </CipherData>

    </EncryptedData>

</connectionStrings>

Narzędzie aspnet_regiis standardowo używa RsaProtectedConfigurationProvider do szyfrowania danych. Możemy wtedy z łatwością wyeksportować klucz prywatny RSA i użyć go na innych komputerach (np. na farmie połączonych ze sobą maszyn). Jeżeli nasza aplikacja ma pracować na pojedynczym serwerze możemy użyć DataProtectionConfigurationProvider. Aby tego dokonać wystarczy dodać parametr „-prov” do wywołania programu aspnet_regiis:

aspnet_regiis –pef „nazwa_s” „ścieżka” –prov DataProtectionConfigurationProvider

Szyfrowanie plików konfiguracyjnych znacznie zwiększy bezpieczeństwo naszej aplikacji. Uzyskanie dostępu do takiego pliku przestanie być równoznaczne z uzyskaniem dostępu do poufnych danych przechowywanych np. w bazie danych.

SQL Injection

Termin „SQL Injection” jest już dobrze znany przez większość programistów i specjalistów ds. bezpieczeństwa komputerowego. Napisano o nim wiele i każdy może na własną rękę wyszukać potrzebne informacje. Niestety, pomimo tak dużej popularności błędów polegających na wstrzykiwaniu własnego kodu SQL, w .NET nie zaimplementowano natywnej ochrony przed ich występowaniem. Przestrzeń nazw SQL.Data.SqlClient, która udostępnia szereg klas odpowiedzialnych za nawiązywanie połączeń i pobieranie danych z bazy posiada pewne mechanizmy zapobiegające atakom, ale ich poprawne wykorzystanie leży w rękach programisty. W związku z tym warto przedstawić kilka przykładów obrazujących, w których fragmentach kodu mogą wystąpić podatności. Dobrym przykładem kodu podatnego na SQL Injection jest poniższy fragment:

string userID = Console.ReadLine();

string conString = WebConfigurationManager.ConnectionStrings["ExampleDB"].ConnectionString;

SqlConnection DBConn = new SqlConnection(conString);

String sqlString = "SELECT * FROM Users WHERE id=’" + userID + “’”;

SqlCommand sqlCmd = new SqlCommand(sqlString, DBConn);

sqlCmd.CommandType = CommandType.Text;

DBConn.Open();

SqlDataReader reader = sqlCmd.ExecuteReader(CommandBehavior.CloseConnection);

 

Listing ten jest prosty, ale idealnie obrazuje problem. Pomimo tego, że powyższy fragment jest tylko abstrakcyjnym modelem podatnego kodu to występują w nim elementy, na które warto zwrócić uwagę przy audycie aplikacji pisanej pod .NET. Prześledźmy źródło w poszukiwaniu błędów.

Po pierwsze, id przyjmowane jest bezpośrednio od użytkownika. Następnie, pobierany jest ciąg odpowiedzialny za połączenie z bazą danych. Oczywiście, ten element powinien być już zabezpieczony zgodnie ze wskazówkami z poprzedniego rozdziału. Następnie tworzony jest obiekt klasy SqlConnection. Kolejna linijka kodu przedstawia zapytanie SQL. Właśnie to miejsce narażone jest na atak. Zapytanie tworzone jest poprzez łączenie ciągów znakowych bez użycia walidacji. Włamywacz może przekazać do userID ciąg znaków rozpoczynający się od pojedynczego apostrofu (‘) a następnie wykonać dowolną operację na naszej bazie. Jest to dość popularny błąd. Kolejne linijki obrazują ustawienie typu zapytania (w naszym wypadku jest to tradycyjne zapytanie SQL) oraz wykonanie zapytania i przekazanie wyników do instancji klasy SqlDataReader.

Jak można zapobiegać błędom tego typu? Możemy zastosować walidację danych wejściowych (jest to zalecane) i np. podwajać przekazywane apostrofy:

 userID = userID.Replace(“’”, “’’”);

Jest to jednak rozwiązanie niewystarczające. Oczywiście zapewnia podstawową ochronę, ale istnieje duże prawdopodobieństwo złego dopasowania autorskich walidatorów i w wyniku tego umożliwienie ataku. Aby zapewnić dodatkową ochronę należy użyć sparametryzowanych zapytań. Pozwalają one rozgraniczyć dane przekazywane przez użytkownika od wartości używanych przy dokonywaniu zapytania SQL oraz są automatycznie kodowane (w celu zniwelowania niebezpiecznych znaków). Po modyfikacji kodu, otrzymamy:

string userID = Console.ReadLine();

string conString = WebConfigurationManager.ConnectionStrings["ExampleDB"].ConnectionString;

SqlConnection DBConn = new SqlConnection(conString);

String sqlString = "SELECT * FROM Users WHERE id=@userID”;

SqlCommand sqlCmd = new SqlCommand(sqlString, DBConn);

sqlCmd.Parameters.AddWithValue(“@userID”, userID);

sqlCmd.CommandType = CommandType.Text;

DBConn.Open();

SqlDataReader reader = sqlCmd.ExecuteReader(CommandBehavior.CloseConnection);

 

Drobna zmiana kodu pozwoliła nam uniknąć wielu nieprzyjemności związanych z możliwością występowania SQL Injection. Parametry, charakteryzujące się znakiem „@” przed nazwą przyjmują wartości poprzez funkcję AddWithValue(). Dzięki temu unikamy procesu konkatenacji łańcuchów znakowych, będącego przyczyną błędów.

Nie jest to jedyna metoda unikania luk bezpieczeństwa. Możemy użyć także procedur zapisanych bezpośrednio w bazie danych. Dobrze napisana procedura powinna skutecznie chronić przed atakami typu SQL Injection. Wielu programistów uważa, że procedury zawsze będą chronić naszą aplikację. Niestety jest to myślenie błędne. Dlaczego? Zapraszam do zapoznania się z artykułem pod adresem http://www.webservice.pl/pl/nowosci/security-blog/sql-injection-mit-bezpiecznych-procedur.

Niebezpieczny ViewState

ViewState jest mechanizmem wykorzystywanym do przechowywania stanu aplikacji. Dzięki niemu możemy np. zapamiętywać stan kontrolki (np. zaznaczone elementy na liście) pomiędzy kolejnymi przeładowaniami witryny. ViewState wykorzystywany jest także do przechowywania „zwykłych” danych. Aby zapamiętać wartość danej zmiennej w ViewState wystarczy napisać krótki kawałek kodu:

int test = 1;

ViewState["Test"] = test;

 

Następnie, aby odzyskać wartość zmiennej:

int test = (int)ViewState["Test"];

 

Jak rozpoznać czy dana strona korzysta z ViewState? Wystarczy podejrzeć źródło strony i wyszukać elementy, których struktura przypomina:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"

value="bBw3NDg2KTR5MDg56z5=" />

Ciąg znaków przechowywanych w value określa wartość przechowywanego elementu. Jak można zauważyć jest to wartość zakodowana. Niestety, jej odczytanie w formie jawnego tekstu jest banalnie proste. Ciąg umieszczony w value używa algorytmu Base64, który jest łatwy do odszyfrowania. Istnieją także narzędzia, które automatyzują ten proces (np. ViewStateDecoder).

bezpieczenstwonet1 ViewStateDecoder w akcji

 

Z uwagi na brak jakichkolwiek zabezpieczeń w domyślnej konfiguracji ViewState, nie powinno przetrzymywać się w nim danych mogących mieć wpływ na pracę programu i jego logikę oraz danych związanych z autoryzacją. W chwili gdy do ViewState zapiszemy informacje poufne, odczytanie ich zajmie kilkanaście sekund.

ASP.NET zapewnia możliwość szyfrowania danych znajdujących się w ViewState, ale nie jest to popularna praktyka wśród programistów. Wykonywanie operacji szyfrowania i deszyfrowania przy każdym przeładowaniu strony jest procesem obciążającym serwer. Jednak, jeżeli zdecydowaliśmy się przechowywać wrażliwe informacje (co jest niezalecane) to należy w pliku konfiguracyjnym aplikacji dodać następujący wpis:

<configuration>

     <system.web>

            <pages viewStateEncryptionMode = „Always” />

      </system.web>

</configuration>

Domyślnie, szyfrowanie danych uzależnione jest od opcji ustawionych dla danej kontrolki. Ustawienie viewStateEncryptionMode na „Always” pozwala nam zapomnieć o ustawianiu parametrów każdej kontrolki osobno, co mogłoby doprowadzić do błędu.

Omówiliśmy możliwość odczytywania danych z ViewState, ale czy istnieje możliwość ich modyfikacji i ponownego przesłania do aplikacji? Teoretycznie tak. W najnowszej wersji ASP do zakodowanego w Base64 ciągu znaków dołączany jest także hash stworzony na podstawie klucza przechowywanego na komputerze. Jednak wiele osób decyduje się wyłączyć tą funkcję, mając na uwadze możliwość uruchomienia programu na farmie serwerów i uniknięcia problemów związanych z różnymi kluczami na serwerach. Jeżeli w pliku konfiguracyjnym znajdziemy wpis:

<configuration>

         <system.web>

            <pages enableViewStateMac = „False” />

      </system.web>

</configuration>

to możemy być pewni, że aplikacja pozwoli nam na podmianę danych przechowywanych w ViewState. Jest to sytuacja wielce niebezpieczna, będąca punktem wyjściowym do przeprowadzenia ataku (np. Cross Site Request Forgery).

W trakcie pisania aplikacji webowej należy pamiętać, że funkcjonalność takich elementów jak ViewState nie zawsze idzie w parze z bezpieczeństwem. Programista musi sam zadbać o odpowiedni poziom bezpieczeństwa i skonfigurować ją w należyty sposób (nie polegając bezgranicznie na wartościach domyślnych).

 Domyślne strony błędów

W razie wystąpienia błędu w czasie wykonywania się aplikacji pisanej w ASP.NET wyświetlana jest strona zawierająca informacje o typie błędu, ślad stosu wywołań oraz fragmenty kodu. Na poniższym zdjęciu przedstawiona została strona przykładowego błędu (dzielenie przez zero).

 Server Error

Strony błędów domyślnie wyświetlane są jedynie w chwili uruchomienia aplikacji na lokalnym komputerze, jednak programiści czasami aktywują  taką możliwość na serwerach produkcyjnych. Jest to wysoce niebezpieczne i przy sprzyjających warunkach (występowanie błędu w odpowiednim miejscu) może doprowadzić do wycieku danych. Aby temu zaradzić należy zawsze ustawiać domyślne strony błędu poprzez wpis w pliku Web.config. Wystarczy dodać jedną linijkę, która uniemożliwi wyświetlanie stron z kodem źródłowym witryny:

<configuration>

       <system.web>

            <customErrors mode=”On” defaultRedirect=”MyErrorPage.htm” />

      </system.web>

</configuration>

XPath Injection

Ostatnim typem błędów jaki opiszę w tym artykule będzie XPath Injection. Jest to podatność w dużym stopniu zbliżona do SQL Injection, ale odnosząca się do plików XML (w SQL Injection atak skierowany był w „tradycyjne” bazy danych).

Pliki XML mogą składować dane podobnie jak normalne bazy danych. Aby ułatwić pobieranie informacji z plików XML wymyślono język XPath, w którym wykorzystujemy odpowiednie zapytania – analogicznie jak w SQL. Przypuśćmy, że posiadamy plik XML zawierający loginy użytkowników, hasła i prywatne  dane:

<?xml version="1.0" encoding="utf-8" ?>

<users>

  <user1>

    <login>geek</login>

    <passwd>keeg</passwd>

    <data>My data</data>

  </user1>

  <user2>

    <login>root</login>

    <passwd>toor</passwd>

    <data>Very important data</data>

  </user2>

</users>

W celu pobrania np. nazwy pierwszego użytkownika za pomocą XPath musimy napisać odpowiedni kawałek kodu:

XmlDocument doc = new XmlDocument();

doc.Load("<ścieżka_do_pliku>");

XmlNodeList nodesList = doc.SelectNodes("/users/user1/login/text()");

foreach(XmlNode node in nodesList)

{

Console.WriteLine(node.InnerText);

}

Program powinien zwrócić nam oczekiwany wynik:

 bezpieczenstwonet5

Teraz rozważmy przypadek, w którym użytkownik podaje login i hasło i na tej podstawie zwracane są poufne dane przekazywane w pliku XML:

Console.WriteLine("Input your login and pass");

string login = Console.ReadLine();

string pass = Console.ReadLine();

XmlDocument doc = new XmlDocument();

            doc.Load("C:\\Users\\hellsource\\Documents\\Visual Studio 2008\\Projects\\ConsoleApplication2\\ConsoleApplication2\\XMLFile1.xml");

XmlNodeList nodesList = doc.SelectNodes("//users/*[login/text()='" + login + "' and passwd/text()='" + pass + "']");

foreach(XmlNode node in nodesList)

{

        if(node.HasChildNodes)

        {

           foreach(XmlNode nod in node)

           {

                  Console.WriteLine(nod.InnerText);

           }

        }

}

 

bezpieczenstwonet6

W tym przypadku użytkownik może wpisać praktycznie dowolne dane na wejście. Wiąże się to z krytyczną luką w bezpieczeństwie aplikacji. Włamywacz może dowolnie manipulować ciągiem wejściowym i np. odczytać dane zapisane przez root’a. Wystarczy skorzystać z odpowiednio spreparowanego ciągu. W XPath nie ma jednak odpowiednika ‘—‘ z SQL.  Wprowadźmy następujący ciąg znaków jako login:

’ or 1=1 or ‘a’=’a

a jako hasło podajmy dowolny wyraz. W przypadku naszego programu otrzymamy dość zaskakujący wynik. Mianowicie, zostaną zwrócone wszystkie dane umieszone w pliku XML! Czemu? Dzięki spreparowaniu danych wejściowych zostało podmienione zapytanie do pliku XML. Sam proces preparowania zapytań jest analogiczny do tego wykorzystywanego w przypadku SQL  Injection. Zmieniła się jedynie składnia języka i przez to musimy przyjąć pewne poprawki.

Jak możemy zaradzić tego typu błędom? Identycznie (znowu…) jak w przypadku zapobiegania SQL Injection – poprzez odpowiednią walidację i parametryzowanie zapytań. Gorąco zachęcam do zgłębiania tajników XPath Injection na własną rękę. W Internecie jest mnóstwo materiałów na ten temat i myślę, że nie będzie problemów ze znalezieniem potrzebnych informacji.

Podsumowanie

Żadna platforma programistyczna nie jest pozbawiona luk w bezpieczeństwie. Dotyczy to również .NET. W tym krótkim artykule starałem się nieco przybliżyć najczęstsze błędy spotykane w aplikacjach pisanych pod .NET i zobrazować zagrożenia z nich wynikające. Nie są to oczywiście wszystkie podatności – nie wspomniałem o XSS i XSRF oraz wielu innych ciekawych technikach przełamywania zabezpieczeń programów. Mam nadzieję, że druga część tego artykułu pozwoli na jeszcze lepsze poznanie sekretów bezpieczeństwa .NET.

--Piotr Łaskawiec

 

 

xterm


Produkty
  • Portal Absolwent oraz Badania Losów Zawodowych Absolwentów
    (PDF 1,06 MB)

    System portalowy wspierający działalność Biur Karier, przeznaczony do usprawnienia komunikacji pomiędzy Uczelniami, Pracodawcami oraz Studentami i Absolwentami.
  • more:portal
    (PDF 0,5 MB)

    Kompetencje oraz doświadczenia WebService w zakresie wdrażania portali korporacyjnych.
  • more:arena
    (PDF 1,7 MB)

    Aplikacja do prowadzenia zaawansowanych negocjacji z klientem oraz do planowania sprzedaży.
  • eProcurement
    (PDF 438 kb)

    Elektroniczny Obieg Wniosków Zakupowych