Bezpieczeństwo web, Zapobieganie SQL Injection

W dzisiejszym poście chciałbym podsumować poprzednie rozważania za pomocą wskazówek jak bronić się przed SQL Injection.

1. Unikanie dynamicznych zapytań
Podstawowa rzecz to oczywiście pisanie zapytań w taki sposób, aby niemożliwe było zmanipulowanie parametrów. Poniższy kod jest skrajnie złą sytuacją:

    var sqlCommand=new SqlCommand(string.Format("Select * From Articles where Name='{0}'",articleName));

Bezpiecznym sposobem jest użycie sparametryzowanych zapytań tzn.:

var sqlCommand=new SqlCommand("Select * From Articles where Name='@name'");
sqlCommand.Parameters["@name"].Value=articleName;

Każdy sterownik baz danych dostarcza analogiczne API do powyższego. Nie powinniśmy sami próbować kodować znaków, aby zapobiec SQL Injection. Istnieje wiele sposobów na wstrzyknięcie kodu i bardzo ciężko samemu oczyścić parametry wejściowe.

Z powyższych parametrów należy ZAWSZE korzystać. Jeśli dane pochodzą z naszej bazy danych to również nie należy przyjmować, że są one bezpieczne i można samemu skonstruować zapytanie. Należy traktować każdy parametr jako potencjalne źródło ataku.

Nawet jeśli spodziewamy się liczby czy innej teoretycznie bezpiecznej wartości, wtedy również nie ma wyjątku od powyższej reguły – żaden parametr nie może zostać wbudowany ręcznie w zapytanie.

Problem w tym, że duża część programistów uważa, że skoro używają sparametryzowane zapytania to nie muszą obawiać się ataków. Wiele “sławnych” wpadek pokazuje, że zdecydowanie nie jest to prawda i wciąż jest to jeden z częściej spotykanych ataków. O ile w prostych aplikacjach (klient-serwer lub po prostu stronach internetowych) trudno popełnić błąd, to w systemach Enterprise nie jest już to takie trudne.

2. Użytkownik w Connection String

Stosunkowo często, ta sama baza danych jest wykorzystywana przez kilka aplikacji. Szczególnie, gdy firmy przenoszą swój monolityczny system do SOA, zwłaszcza w pierwszych etapach refaktoryzacji, ta sama baza danych jest współdzielona przez różne usługi.

Co za tym idzie, nie mamy już takiej kontroli nad dostępem. Nawet jeśli nasza aplikacja używa
sparametryzowanych zapytań, możliwe jest, że któraś z wielu usług zawiera błąd. Za pomocą eskalacji opisanej w poprzednim poście, atakujący może przejąć kontrolę nawet nad całą infrastrukturą.

Z tego względu, zawsze należy tworzyć użytkowników o jak najniższych przywilejach. Jeśli aplikacja A, korzysta wyłącznie z tabel B,C, wtedy użytkownik w ConnectionString powinien mieć dostęp wyłącznie do nich. Często spotykam connection string, gdzie użytkownik ma dostęp do całej bazy danych, włącznie z możliwością zapisu. W skrajnych sytuacjach, aplikacje webowe korzystają nawet z kont, które mogą tworzyć i usuwać tabele…

Nigdy nie popadajmy w złudzenie, że skoro dla mnie oczywiste są sparametryzowane zapytania czy ORM, to wszyscy i zawsze będą taki kod pisać. Systemy ewoluują, część z nich jest odziedziczona albo outsourcowanana. Im bardziej skomplikowany system, tym trudniej mieć na tym kontrolę. Niektóre systemy działają od np. 20 lat i ciężko mieć tutaj pewność. Oczywiście sytuacja jest luksowana, gdy piszemy system od zera. Wtedy powyższe problemy  pojawią się dopiero za 10 lat, o ile wcześniej z różnych innych względów takowy projekt nie upadnie.

3. Aktualizacja oprogramowania, bibliotek i innych zależności

Nawet jeśli mamy pewność, że nasz system spełnia punkt pierwszy, to nigdy nie możemy powiedzieć tego samego o wszelkich zależnościach. W przypadku stron internetowych może być to prostu CMS. Inne przykłady to pluginy, Nuget, ORM, sterowniki do baz danych czy nawet całe serwisy

Im bardziej skomplikowany system, tym więcej komponentów utrzymywanych przez inne firmy. Mogą być to nawet całe usługi, nierzadko generujące ogromne ilości danych. Eskalacja SQL Injection może polegać na tym, że przez złamanie usługi utrzymywanej przez zewnętrzną firmę, możliwe stanie się wykradnięcie danych z naszych usług, które myśleliśmy wcześniej, że są bezpieczne ponieważ korzystamy z ORM\SP\sparametryzowanych zapytań.

4. Zapytania, które nie mogą być sparametryzowane

Niestety, nie wszystkie typy zapytań mogą być w łatwy sposób sparametryzowane. Widziałem przypadki, gdzie “ORDER BY DESC” był ręcznie dodawany, w zależności od typu sortowania wybranego przez użytkownika.
Tak jak wspomniałem, unikajmy pisania własnego kodowania takich wartości bo bardzo trudno pokryć wszystkie przypadki. Najrozsądniejszym sposobem jest użycie białych list, czyli dopuszczenie wyłącznie dozwolonych wartości, np. DESC, ASC i nic po za tym.
Nie wszystkie zapytania to pojedyncze SELECT z kilkoma filtrami. Czasami filtry dodawane są dynamicznie i wtedy mogą pojawić się wyzwania, ale to już zależy od konkretnego typu bazy.

5. Walidacja danych

Dobrą praktyką (nie tylko z punktu widzenia bezpieczeństwa) jest szczegółowa walidacja danych. Jeśli pominiemy walidację danych, to nawet, gdy nie wywoła one bezpośrednio problemu (poprzez parametryzację), to nigdy nie wiadomo, czy nie zostanie wykorzystane jakoś we wtórnych atakach, potencjalnie bazujących na zaufaniu do danych pochodzących z bazy danych.

Leave a Reply

Your email address will not be published.