Narzędzia osobiste
Start > Aktualności > Security blog

Security blog

Kolejny audyt bezpieczeństwa dla BRE Banku

Tym razem wykonaliśmy audyt bezpieczeństwa aplikacji desktopowej Windows.

Dodatkowo przeprowadzony został audyt infrastruktury: systemu operacyjnego oraz bazy danych.

Całość prac potwierdzona została listem referencyjnym, uznającym wysoki poziom naszej wiedzy audytorskiej oraz profesjonalizm wykonanych przez nas prac.

xterm

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

Blind SQL Injection

21.04
W kategorii: ,

Ogólne rozważania o technice, czyli jak wykonać testy na Blind SQL Injection.

 
Ostatnim razem zająłem się mitem bezpiecznych procedur. Tym razem chciałbym się zająć kolejnym mitem, czy może lepiej, niebezpiecznym przeświadczeniem, które równie często osłabia naszą czujność.  Być może czytając poprzedni tekst na temat niebezpiecznych procedur SQLi- mit bezpiecznych procedur zadałeś/łaś sobie pytanie: „No i co z tego, że aplikacja jest podatna na SQL Injection, skoro włamywacz nie będzie znał struktury tabel, kolumn itp. Jeżeli aplikacja nie będzie wysyłała błędów składniowych bazy danych na output aplikacji, to nigdy nie wymyśli on zapytania, którym mógłby zaszkodzić moim danym”. I znów to tylko niebezpieczne pozory.

Tak jak człowiekowi niewidomemu trudniej dotrzeć do celu niż człowiekowi ze sprawnym wzrokiem, tak włamywaczowi, który nie będzie miał drogowskazów będzie trudniej namieszać w naszej bazie danych. W obu przypadkach „trudniej”, nie znaczy „niemożliwe”. Dlatego technika wykrywania podatności na SQL Injection w sytuacji, kiedy błędy serwera są ukrywane przed klientem, została nazwana Blind SQL Injection.

Na czym polega ta technika? Mówiąc najprościej, polega ona na takim spreparowaniu zapytań, które da nam PEWNOŚĆ, że kod SQL dołączony do przekazanego parametru się wykonuje i zwraca rezultaty, jakich oczekujemy. (http://www.owasp.org/index.php/Blind_SQL_Injection , http://www.cgisecurity.com/questions/blindsql.shtml, http://www.spidynamics.com/assets/documents/Blind_SQLInjection.pdf)

Przedstawię to na przykładzie. Wyobraźmy sobie, że mamy aplikację, która przekazuje parametr „ID” w url-u i na podstawie tego parametru wyświetlane są rozszerzone informacje o użytkowniku, np.:

http://app-server/userInfo.aspx?id=25

Mając wiedzę o SQL Injection, możliwe jest sprawdzenie, czy aplikacja jest podatna na tego typu atak. Jeżeli jednak aplikacja ma prawidłowo zaimplementowaną obsługę błędów, to w sytuacji, kiedy w url-u zostanie przekazana wartość id: „25;select * from jakastabela”, zapewne wyświetlona zostanie strona z komunikatem: „Przykro nam, ale wystąpił błąd”. Można sobie zadać pytanie, czy błąd został wygenerowany na serwerze bazy danych, czy w aplikacji? W tej sytuacji komunikaty o błędach niewiele pomogą.

Jeżeli jednak potencjalny intruz wie, jak korzystać z techniki Blind SQL Injection, łatwo stwierdzi, czy serwer jest podatny na atak. Można to wykonać na kilka sposobów, jednak  cel jest jeden: takie przygotowanie zapytania SQL, żeby rekord zwracany przez aplikację był taki sam, jak rekord, który zostanie wyświetlony po zaaplikowaniu „zatrutej strzały”.

Oto kilka metod wykonywania testów na Blind SQL Injection:

  1. Metoda „na działanie arytmetyczne”. Polega ona na przekazaniu takiego ciągu znaków, który w wyniku przetworzenia przez serwer bazy danych wykona działanie arytmetyczne, które w wyniku da wartość przekazywanego identyfikatora, a więc w naszym przypadku może to być: id=24+1, Id=26-1 lub dla prawdziwych twardzieli ;-) Id=5*5.
  2. Metoda „na oczywistość”. Polega ona na wprowadzeniu do zapytania elementu, które zawsze zwraca „true”, wskutek czego warunki filtrowania danych nie ulegną zmianie. W naszym przypadku może to być: Id=25 AND 1=1, lub Id=25 AND ‘biale’=’biale’ AND ‘czarne’=’czarne’
  3. Metoda „czekajcie, a będzie wam dane”. Polega ona na wysłaniu do serwera takiego zapytania, żeby serwer bazy danych sam zasygnalizował, że zapytanie zostało wykonane. Najprościej zrobić to kombinując z wywoływaniem funkcji opóźniających. Przykładowo w SQL Serverze do naszego parametru można dodać Id=25 WAITFOR DELAY '0:0:5'. Jeżeli strona, która wyświetlona była bez dodatkowych „wspomagaczy” wyświetlała się o 5 sekund krócej, niż po ich zastosowaniu, oznacza to, że dodatkowe parametry zapytania zostały przyjęte bez zastrzeżeń, a więc  istnieje możliwość wykonania kodu SQL dołączonego do zapytań.
  4. Metoda „Kto tam?”. Służy ona odpytaniu bazy danych, jakiego serwera bazodanowego (chodzi o rozpoznanie czy to MySQL, SQL Server, Postgresql, Oracle itp.) używa aplikacja. Metoda ta polega na dołączeniu do zapytania kodu SQL specyficznego dla wybranych serwerów. Jeżeli dołączony kod wygeneruje błąd, wówczas można wykluczyć tę wersję serwera. Jest to bardzo istotny element Blind SQL, ponieważ wydobywanie informacji na temat obiektów systemowych jest inne dla każdej bazy danych.  W większości tekstów poświęconych bezpieczeństwu nazywana jest ona fingerprinting. (http://bernardodamele.blogspot.com/2007/07/more-on-database-management-system.html).
  5. Metoda „Jeżeli”. Metoda służy do sprawdzenia, czy zapytanie wykonuje się w sytuacji, kiedy nie da się w prosty sposób stwierdzić, że zapytanie się wykonało i zwróciło oczekiwane rezultaty, ponieważ wynik zapytania SQL nie jest wyświetlany na ekran. Np. w sytuacji, kiedy użytkownik się loguje na stronę, a po poprawnym wykonaniu zapytania jest weryfikowany i przekierowywany do innego zasobu. Jeżeli intruz chce sprawdzić, czy zapytanie SQL przekazane w parametrze się wykonuje, wówczas wystarczy „wstrzyknąć” zapytanie, które w zależności od spełnienia warunku wykona jakąś czynność, łatwą do zweryfikowania, np.  opóźnienie. A zatem kawałek kodu dodany do zapytania przeznaczonego do SQL Servera wyglądałby następująco:

if ((select user) = 'dbo') WAITFOR DELAY '0:0:5'

Jeżeli po przesłaniu zapytania do bazy danych chwila oczekiwania na wykonanie akcji będzie o 5 sekund dłuższa niż normalnie, oznacza to, że baza danych to SQL Server, a ponadto użytkownik, z którego prawami wykonywane jest zapytanie to „dbo”.

Powyższe przykłady stanowią tylko część możliwości. Więcej można poczytać pod adresem http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/. Pod tym adresem można znaleźć bardzo dużo wskazówek na temat SQL Injection.

Po stwierdzeniu faktu występowania podatności na włamanie, po raz kolejny można sobie zadać pytanie, do czego włamywacz może ją wykorzystać, skoro nigdzie nie może wyświetlić rezultatów zapytania (co nie zawsze jest prawdą, ponieważ przy pomocy odpowiedniego spreparowania instrukcji wyciągnięcie danych nie stanowi problemu, ale o tym innym razem)? Otóż nawet nie mogąc wyświetlić informacji na output można mozolnie, ale skutecznie przepytać serwer i wyciągnąć od niego wiele cennych informacji.

Z samej podatności serwera bowiem jeszcze nic nie wynika. Cały czas mamy w tyle głowy pytanie: „I co nam może zrobić włamywacz, skoro nie zna struktury bazy danych”. Zwłaszcza, jeżeli projektant przewidując ewentualne próby włamań i zgodnie z zasadą dobrych praktyk unikał standardowych nazw obiektów bazy danych, ich właściwości itp. Zamiast nazywać obiekty standardowo, np. tabela „Users”, z polami „username”, „password”, „id”, pododawał prefiksy, dzięki którym dużo trudniej wymyślić zapytanie. Tabeli nadał nazwę „AppUsers”, a polom w tabeli „AUName”, „AUPass”, „AUId”. Zabezpieczenie takie jest o tyle dobre, że od razu odfiltruje nam wszystkich scripts kiddies. Jednak jeżeli osoba próbująca się dostać do naszego serwera zna trochę zaawansowane funkcjonalności bazy danych, wówczas nawet mimo najbardziej wyrafinowanych nazw włamanie nie będzie stanowiło problemu właśnie z powodu Blind SQL Injection.

Zanim pójdziemy dalej, żeby przekonać się jak to jest możliwe, spróbujmy się przyjrzeć regułom formułowania zapytań tego typu. Przeanalizujmy sobie jak wygląda nasze zapytanie SQL przed i po zaaplikowaniu dodatkowego kodu SQL.

Jako przykład znów użyjemy naszą tabelę z użytkownikami użytą w poprzednim wpisie[link]. W parametrze GET przekazywana jest wartość ID i po jej otrzymaniu od serwera wykonujemy następujący kod SQL:

string sql = "select Id, Username, LastLogon from AppUsers where Id=" + Request.Params["id"];

Jeżeli więc jako parametr ID przekażemy

1 AND 1=1

Wówczas na serwerze bazodanowym wykona się zapytanie

select Id, Username, LastLogon from AppUsers where Id=1 AND 1=1

Przeanalizujmy sobie nasze zapytanie i zastanówmy się, jak podzielone jest to zapytanie. Pierwsza część jest standardowym zapytaniem, które zwraca oczekiwany rekord. Natomiast druga część mówi, czy warunek umieszczony po „AND” jest prawdziwy. Żeby się upewnić, że druga część zapytania nie jest ignorowana, ale na pewno się wykonuje, trzeba dodać warunek, który zawsze jest fałszywy, czyli np.:

AND 1=2

W tej sytuacji aplikacja zwróci błąd mówiący, że nie znaleziono podanego użytkownika.

Jak widać pierwsza część zapytania jest niezmienna i jest ona gwarantem, że zapytanie umieszczone w drugiej części zawsze zwraca „True”. Co można wyciągnąć z takiej wiedzy? Nadspodziewanie dużo, a jeżeli użytkownik, z którego prawami jest uruchamiana aplikacja kliencka, ma odpowiednie uprawnienia, to jeszcze więcej.

Włamywacz ma więc do dyspozycji tak tylko drugą część zapytania i do dyspozycji bardzo lakoniczne odpowiedzi serwera (TAK/NIE). Co dalej?

Przypomnijmy sobie zabawę z dzieciństwa w 25 pytań, kiedy jedna osoba wymyślała jakiś przedmiot, a druga musiała odgadnąć jaki to przedmiot, mając do dyspozycji 25 pytań i odpowiedzi tej pierwszej tylko „TAK” lub „NIE”. Podobnie włamywacz może odpytać serwer podatny na atak typu Blind SQL Injection.

Zastanówmy się, jakie pytania może on zadać serwerowi, żeby mogli dowiedzieć się, jaką strukturę ma tabela, do której kierowane są zapytania ze skryptu.

Może zadać następujące pytania (oczywiście po przeformułowaniu ich na język SQL):

  1. czy użytkownik, który wywołuje zapytanie jest „dbo”
  2. czy liczba tabel w bazie danych jest większa od [10,5,7,8]
  3. czy pierwsza litera nazwy pierwszej tabeli  przedstawiona jako mała litera to [m,g,d,b]
  4. czy druga litera nazwy pierwszej tabeli …
  5. czy liczba kolumn w wybranej tabeli jest większa od…
  6. czy pierwsza litera nazwy pierwszej kolumny wybranej tabeli to…

Wszystkie te informacje są możliwe do wyciągnięcia, jeżeli odpowiednio zostaną spreparowane odpowiednie zapytania do tabel systemowych, wywołania wbudowanych funkcji systemowych itp. Właściwie w ilości danych, jakie można wyciągnąć z niezabezpieczonej aplikacji intruza ogranicza tylko czas, wyobraźnia i potrzeby. Z takich szczątkowych informacji, jest w stanie zbudować sobie potężną bazę wiedzy o tabelach bazy danych używanych przez aplikację. Oczywiście opisane przeze mnie kroki, nie muszą być wykonywane ręcznie. Przygotowanie skryptu, który wykona za niego całą pracę nie zajmie mu wiele czasu (tym łatwiej, jeżeli będzie miał już rozpoznaną wersję serwera bazy danych), poza tym w sieci można ściągnąć wiele aplikacji wykonujących te zadania.

Ze względu na to, że celem tego artykułu nie jest przygotowanie instrukcji, jak wyciągać poufne dane z serwerów, a jedynie ostrzeżenie przed niebezpieczeństwem, nie będę tutaj pisał tutoriala, jakie kolejne kroki należy wykonać żeby je wyciągnąć z serwera bazodanowego, natomiast żeby udowodnić, że zagrożenie rzeczywiście istnieje, przytoczę kilka przykładów, które zachęcą, mam nadzieję, do lepszego zabezpieczania aplikacji przed SQL Injection we wszystkich postaciach.

Wszystkie przykłady są odpowiednie dla MS SQL Server.

Odp. 1: Czy user jest dbo.

 AND USER_NAME()='dbo'

Odp 2.

 AND (select count(name) from sysobjects where type='U')>10

Odp. 3.

 AND ascii(lower(substring((SELECT TOP 1 name FROM sysobjects WHERE xtype='U'),1,1)))>96

Mam nadzieję, że zachęciłem do zwrócenia uwagi, na to, czy mimowolnie nie dodaliśmy do naszej aplikacji funkcjonalności konsoli SQL dla zaawansowanych użytkowników. Warto o tym pomyśleć, kiedy piszemy warstwę obsługi dostępu do danych w naszych aplikacjach.

rusk

Kolejny audyt bezpieczeństwa

16.04
W kategorii:

Wykonaliśmy audyt bezpieczeństwa dla firmy: BRE ubezpieczenia sp. z o.o.

Tym razem testowaliśmy aplikację webową, opartą o technologię .NET

Prace zostały potwierdzone listem referencyjnym, uznającym profesjonalne wykonanie oraz bardzo wysoki poziom wiedzy inżynierskiej. Referencje są do wglądu dla naszych klientów.

xterm

SQLi- mit bezpiecznych procedur

Czy Ty jesteś bezpieczny? Przed czym należy się bronić?

O SQL Injection można znaleźć w Internecie wiele materiałów, zakładam więc, że czytelnik ma już podstawową wiedzę z tego zakresu, a mianowicie wie:

- co znaczy zwrot SQL Injection

- jak manipulować danymi w bazie danych za pomocą zapytań SQL przemycanych w przekazywanych danych wejściowych.

Jeżeli terminologia nie jest jeszcze znana, polecam zapoznanie się z tematyką: http://www.owasp.org/index.php/SQL_injection, http://www.owasp.org/index.php/Guide_to_SQL_Injection, http://www.governmentsecurity.org/articles/SQLinjectionBasicTutorial.php).
Szczególnie polecam strony OWASP, ponieważ nie powielają one mitu, o którym będę pisał poniżej, a który jest powielany  na wielu forach, blogach itp.

Mit, którym chciałbym się zająć, jest o tyle niebezpieczny, że daje pozorne poczucie bezpieczeństwa, a jednocześnie jest ukrytą miną, na którą ktoś wcześniej, czy później trafi. Tyle, że w tym przypadku ofiarą będzie umieszczający tę minę.

Mit ten brzmi: „korzystanie ze Stored Procedures zabezpiecza przed SQL Injection”.

Dla wielu programistów jest oczywiste, że najlepszym zabezpieczeniem przed SQL Injection jest skorzystanie z używanych w większości serwerów bazodanowych procedur przechowywanych w bazie danych, wywoływanych z parametrami, przekazywanymi przez klienta wywołującego procedurę. Jest to prawda w 50% (a więc nie jest to prawda), ponieważ błędnie napisana procedura może się okazać równie niebezpieczna, co konkatenowany skrypt SQL zaszyty w kodzie aplikacji. Może nawet bardziej niebezpieczna, ponieważ zapytanie generowane dynamicznie w kodzie, w przeciwieństwie do procedury, nie sprawia wrażenia bezpiecznego.

W przykładzie posłużę się procedurami składowanymi (stored procedures) oferowanymi przez SQL Server.

Załóżmy, że mamy w bazie danych tabelę AppUsers:

 

 

 

Tabela przechowuje dane użytkowników naszej aplikacji. Jedno z pól (pole IsAdmin) informuje, czy użytkownik jest administratorem. Na podstawie flagi ustawionej w tym polu aplikacja daje dostęp do panelu administracyjnego, w którym zalogowany użytkownik może zarządzać aplikacją, użytkownikami, uprawnieniami itp.

W części publicznej aplikacji mamy jakąś listę użytkowników, którą można przeszukiwać. Dla uproszczenia przykładu przyjmijmy, że będzie to wyszukiwanie według loginu użytkownika. W sytuacji, kiedy w filtrze wyszukiwania nie zostaną wprowadzone żadne dane, wówczas prezentowana jest lista wszystkich użytkowników. W przeciwnym razie w bazie będą wyszukiwani użytkownicy, których login zawiera litery podane w zapytaniu. Programista w tej sytuacji postanawia skorzystać z procedury SQL, ponieważ jest przekonany, że to zabezpieczy go przed problemem SQL Injection. Umieszcza więc w bazie danych procedurę:

CREATE PROCEDURE [dbo].[USP_UsersSearchSafe]

@username varchar(400) = NULL

AS

DECLARE @sql nvarchar(4000)

SET @sql = ' SELECT Id, Username, IsAdmin, LastLogon ' +

              ' FROM AppUsers '

IF @username IS NOT NULL

   SET @sql = @sql + ' Where Username LIKE '''+ @username +''''

Exec (@sql)


Wydaje się, że wszystko jest bezpieczne, ponieważ skorzystał z procedury, natomiast teraz wyobraźmy sobie, że w polu tekstowym użytkownik o loginie „user1” wprowadzi poniższy kod SQL:

';update AppUsers set IsAdmin=1 where Username='user1';--

Po przesłaniu powyższego zapytania do mechanizmu wyszukiwawczego, nasz mechanizm wyszukiwawczy „w promocji” dorzuca użytkownikowi „user1” uprawnienia administratora. Gdyby zapytanie było wykonywane 6 grudnia, można by zrezygnować z klauzuli „where” i wówczas wszyscy użytkownicy w prezencie mikołajkowym dostaliby administracyjne uprawnienia.

Nie trzeba wyrafinowanej analizy, żeby odkryć, co jest przyczyną podatności na SQL Injection, mimo zastosowania procedury SQL.

W powyższym przykładzie procedura została napisana dokładnie w taki sam sposób, w jaki byłaby ona zapisana, gdyby programista zaszył ją wewnątrz kodu aplikacji, tzn. zapytanie SQL jest konkatenowane i ewaluowane , natomiast sam przekazany parametr nie jest odseparowany od logiki zapytania. Zapytanie to nie różni się zbytnio od zapytań typu:

„Select * from products where Id=” + Input[„id”]

Przy korzystaniu z procedur SQL ważne jest, żeby uświadomić sobie, że przez zagrożeniami nie chroni sam fakt skorzystania z procedury. Przed wszelkiego rodzaju problemami chroni tylko DOBRZE NAPISANY KOD, niezależnie od tego, czy znajduje się on w aplikacji, czy też w bazie danych.

Przejdźmy więc do tego mitycznego stworzenia o nazwie DOBRZE NAPISANY KOD. Jak powinien wyglądać w naszym przykładzie, żeby wyeliminować podatność na SQL Injection.

CREATE PROCEDURE [dbo].[USP_UsersSearchReallySafe]

@username varchar(400) = NULL AS

DECLARE @sql nvarchar(4000)

SET @sql = ' SELECT Id, Username, IsAdmin, LastLogon ' +

              ' FROM AppUsers '

IF @username IS NOT NULL

   SET @sql = @sql + ' Where Username LIKE @username '

Exec sp_executesql @sql, N'@username varchar(400)',@username

 

Na czym więc polega różnica. Otóż w tym przypadku skorzystaliśmy możliwości oferowanych przez zapytania parametryzowane, dzięki czemu parametry przekazywane do zapytania SQL (dane wejściowe) zostały całkowicie odseparowane od składni zapytania. Jedyną niedogodnością jest, że w tym przypadku wywołanie przygotowanego zapytania SQL jest trochę bardziej skomplikowane, ponieważ musimy skorzystać ze specjalnej procedury sp_executesql, a także musimy jeszcze raz przekazać parametry do tej procedury. W tym przypadku jednak dużo bardziej przejrzyste jest przygotowanie samego zapytania SQL, dlatego w sumie wychodzimy na remis. Z tą różnicą, że w tym drugim przypadku mamy naprawdę bezpieczną procedurę.

Jaką konkluzję chciałbym więc zawrzeć w podsumowaniu? Chyba taką, że nawet „the best practice” może być przyczyną problemów, jeżeli będzie zastosowana bezrefleksyjnie...

I że ŹLE NAPISANY KOD zawsze jest zagrożeniem, niezależnie od tego, gdzie został umieszczony.

rusk

Multi sql injection w Phorum

15.02
W kategorii:

Używasz Phorum - Koniecznie musisz to przeczytać!

Podczas kontroli kodu źródłowego, nasz dział software security znalazł kilka błędów typu blind sql injection w oprogramowaniu Phorum3.

Phorum3 ma poważną wadę konstrukcyjną, sprawiającą iż oprogramowanie to działa w trybie register_globals=on niezależnie od ustawień w php.ini.

Winny jest plik: include/register_globals.php (includowany z commons.php), który w całości cytuję poniżej:

 

if (!defined("_COMMON_PHP")) return;
 if(isset($_SERVER)){
     $arrays=array(
                $_SERVER,
                $_ENV,
                $_GET,
                $_POST,
                $_COOKIE
    );
 } else {
     $arrays=array(
                $HTTP_SERVER_VARS,
                $HTTP_ENV_VARS,
                $HTTP_GET_VARS,
                $HTTP_POST_VARS,
                $HTTP_COOKIE_VARS
    );
 }

foreach($arrays as $array){
     foreach($array as $var=>$val){
        if(!isset($GLOBALS[$var])){
            $$var = $val;
        }
    }
}

Komentarz w temacie za co odpowiada ten plik wydaje się być zbędny. 

Pierwszy SQLi:

http://phorum/forum/search.php?f=1&search=test&...&globalsearch=1
&searchforums[0]=sqli

Przy tworzeniu zapytania SQL nie jest walidowana tablica $searchforums,
linijka 186, plik search.php:

http://phorum/forum/search.php?f=1&search=test&...&globalsearch=1
&searchforums[0]=sqli

search.php:

183.  if($globalsearch){
184.                  $SQL="Select id, name, table_name from $pho_main where";
185.                  if(isset($searchforums)){
186.                      $SQL.=" id in (".implode(",", $searchforums).")";
187.                  } else {
188.                      $SQL.=" (active=1 or id=$num)";
189.                  }
190.                  if(!isset($phorum_user['id'])){
191.                      $SQL.=" and security!=".SEC_ALL;
192.                  }
193.                  $q->query($DB, $SQL);
194.                  $row = $q->getrow();
195.              } else {
196.                  $row=array("id"=>$num, "name"=>$ForumName, "table_name"=>$ForumTableName);
197.              }

Drugi SQLi:

http://phorum/forum/search.php?f=1&search=test&...&fields[0]=sqli

search.php, problem jak w pierwszym SQL injection:

87.                reset($fields);
88.                unset($likeArray);
89.                while (list ($key, $val) = each ($fields)) {
90.                    $term=addslashes($term);
91.                    if(strstr($DB->type, "postgresql")){
92.                        $likeArray[]=" upper($val) $notmod"."~~ upper('%$term%') ";
93.                    } else {
94.                        $likeArray[]=" $val $notmod"."LIKE '%$term%' ";
95.                   }
96.                }

Trzeci SQLi:

http://phorum/forum/read.php?f=1&...&ids[0]=sqli

Z pliku  read.php wywolywana jest funkcja phorum_get_users()
bez walidacji parametru przekazywanego do funkcji.

Funkcja phorum_get_users() znajduje się w pliku include/userlogin.php, a interesujący fragment cytuję poniżej:

291.  function phorum_get_users($ids) {
292.    global $DB,$q,$PHORUM;
293.     // Get the user info.  I curse PG for not having Left Joins.
294.     $SQL="select id, username, email, signature from ".$PHORUM["auth_table"]." where id in (".implode(",", $ids).")";
295.     $q->query($DB, $SQL);
296.     $rec=$q->getrow();
297.     While(is_array($rec)){
298.        $users[$rec["id"]]=$rec;
299.    $rec=$q->getrow();
300.     }
301.     return $users;
302.  }

 

Podatności były testowane na wersji 3.4.8a phorum. Jest to wersja oficjalnie oznaczona na stronie producenta jako  niesupportowana. Vendor po zgłoszeniu przez nas błędu odmówił wypuszczenia patcha, właśnie powołując się na ww. fakt.

Z naszych doświadczeń wynika jednak, że phorum3 jest cały czas intensywnie wykorzystywane w Internecie, również w nowych projektach - informacja o znalezionych przez nas podatnościach może być więc przydatna.

 

 

xterm

Persistent XSS w Wordpress

07.02
W kategorii:

Krytyczne błędy w popularnym oprogramowaniu! Czy Twój blog stanowi zagrożenie?

W ramach rutynowej analizy kodu źródłowego, znaleźliśmy 2 błędy persistent XSS w jednym z najpopularniejszych oprogramowaniań blogowych - <a href="www.wordpress.org">WordPress</a>.

Proof of Concept (IE7)

wpisanie w komentarz ciągu:


aaa@"STYLE="behavior:url('#default#time2')"onBegin="alert('XSS')"

Aktywacja XSS-a następuje po odczytaniu komentarza przez uzytkownika blogu.
Aktywacja ma również miejsce w administracji aplikacji, przez co atakujący
może otrzymać dostęp do konta administratora (ataki typu: przejęcie sesji administratora bez znajomości hasła, CSRF).

Drugi błąd jest podobny, PoC:

javascript://%0a%0dalert%281%29

aktywacja po kliknięciu w wygenerowany link.

Przypominam, że XSS-y persistent są wyjątkowo zjadliwe (do uruchomienia ataku nie jest jest wymagane podrzucenie linku ofierze, wystarczy że będzie ona najzwyczajniej w świecie nawigować po portalu).

 Szczegóły

problem istnieje w pliku:

wp-includes/formatting.php, a dokładniej, w funkcji
make_clicable().

regexpy zamieniające wybrane kawałki tekstu  na wersje klikalne są zbyt mało restrykcyjne. Dokładniej, chodzi o 6 i 8 linijkę poniżej.



1. function make_clickable($ret) {
2.    $ret = ' ' . $ret;
3.    // in testing, using arrays here was found to be faster
4.    $ret = preg_replace(
5.        array(
6.            '#([\s>])([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)#is',
7.            '#([\s>])((www|ftp)\.[\w\#$%&~/.\-;:=,?@\[\]+]*)#is',
8.            '#([\s>])([a-z0-9\-_.]+)@([^,< \n\r]+)#i'),
9.        array(
10.            '$1<a href="$2" rel="nofollow">$2</a>',
11.            '$1<a href="http://$2" rel="nofollow">$2</a>',
12.            '$1<a href="mailto:$2@$3">$2@$3</a>'),$ret);
13.    // this one is not in an array because we need it to run last, for cleanup of accidental links within links
14.    $ret = preg_replace("#(<a( [^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i", "$1$3</a>", $ret);
15.    $ret = trim($ret);
16.    return $ret;
17.}

 

Podatne wersje wordpress-a

2.3.1, najprawdopodobniej również niższe.

Kroki zaradcze

Vendor został przez nas powiadomiony o problemie i wypuścił wersję 2.3.2 zawierającą m.in. patcha poprawiającego podatność.

Zalecamy więc upgrade do najnowszej wersji wordpress-a.

 

 

 

xterm

Błąd persistent XSS w Allegro

29.01
W kategorii: ,

Tym razem znaleźliśmy błąd typu persistent XSS.

PoC:

<P STYLE="xss:e\xpression(alert(1))"></P>

W przypadku błędów persistent XSS, odpowiednio spreparowany payload znajduje się na serwerze podlegającym atakowi (użytkownik nie musi kliknąć w spreparowany link, wystarczy że w normalnym trybie nawiguje po portalu), co zdecydowanie zwiększa drastyczność ataku.

Błędy tego typu umożliwiają w prosty sposób m.in. przejęcie sesji użytkownika i dostęp do jego konta bez znajomości hasła/nazwy użytkownika.

Dodam tez, że ww PoC jest klasycznym przykładem wykorzystania techniki filter evasion.

Nie muszę chyba pisać, czym tego rodzaju błąd mógłby skutkować w serwisie typu Allegro...

Dzięki sprawnej współpracy z działem bezpieczeństwa Allegro, błędy został w bardzo szybkim czasie poprawiony.

xterm

Błędy SQL Injection w Allegro

22.01
W kategorii: ,

W ramach rutynowych testów, wykryliśmy błędy typu SQL Injections w serwisie allegro.pl.

Błędów było kilka, poniżej prezentujemy jeden z nich (PoC):

http://allegro.pl/phorum/search.php?f=259&search=sprzedawca&globalsearch=0
&match=1&date=30&fldsubject=1&fldbody=1&fields[0]=Tutaj SQLi

Dzięki sprawnej współpracy z działem bezpieczeństwa Allegro, błędy zostały w bardzo szybkim czasie poprawione.

Dla niewtajemniczonych, przypominamy, że błędy typu SQL injections umożliwiają ataki dające atakującemu m.in:

  • nieograniczony dostęp do bazy danych w trybie do zapisu i odczytu (umożliwia to np. umieszczenie kompromitujących informacji w portalu czy odczytanie poufnych danych)
  • nieograniczony dostęp do administracji aplikacji
  • dostęp do shell-a w systemie operacyjnym i eskalacja ataku w głąb infrastruktury
  • przejęcie dostępu do kont użytkowników bez znajomości haseł
  • wykorzystanie serwera jako zombie host do ataków na inne serwery w Internecie


Nie mówimy, że tego typu konsekwencje występowały w serwisie allegro (nie sprawdzaliśmy tego), a wskazujemy jedynie na krytyczność tego typu luk.


 

xterm

Audyty bezpieczeństwa dla BRE

15.01
W kategorii:

Wykonaliśmy dwa audyty bezpieczeństwa dla BRE Banku.

Prace te obejmowały:
  • Audyt jednego z serwisów informacyjnych Banku
  • Wykonanie audytu aplikacji webowej

Oba audyty obejmowały część aplikacyjną oraz infrastrukturę.

Prace zostały potwierdzone listem referencyjnym - do wglądu dla naszych potencjalnych klientów.

Niestety ze względu na umowy o poufności nie możemy podać więcej szczegółów.

Witam na blogu security

10.01

W tym miejscu będziemy publikować rozmaite ciekawostki ze świata web application security.

Tematy będą przekrojowe: począwszy od opisów znalezionych przez nas luk w oprogramowaniu webowym, przez opis podatności w znanych polskich portalach, a kończąc na postach dotyczących aktualnie wykonywanych przez nas prac audytowych.

Zapraszam do częstych odwiedzin, postaram się aby większość
z wpisów opisywała sytuacje o wysokim stopniu krytyczności :-)

Michał Sajdak
Dyrektor IT, Kierownik działu software security
WebService

xterm


Ulotki
  • Audyty bezpieczeństwa systemów IT
    (PDF 1,7 MB)

    Kompetencje i doświadczenia WebService w zakresie wykonywania audytów bezpieczeństwa.
  • 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.
  • Portal Absolwent
    (PDF 1,87 MB)

    System dla Biur Karier, przeznaczony do komunikacji pomiędzy Uczelniami, Pracodawcami oraz Studentami i Absolwentami.