IoT i bezpieczeństwo: część pierwsza

IoT i bezpieczeństwo: część pierwsza
Dmitry Andrianov, Senior Software Architect w DataArt, postanowił opowiedzieć o zagrożeniach związanych z nieprzemyślanym wypuszczaniem na rynek nowych produktów. Opisuje również absurdalne błędy popełniane przez developerów, które mogą wysadzić każdy system i wyjaśnia, dlaczego producenci instalują moduły wi-fi w czajnikach elektrycznych.

Zaniedbywane ryzyko

Jednym z najpoważniejszych wyzwań, z jakimi zmaga się dzisiejsze IT jest dość oczywisty fakt, że wielu specjalistów nie jest wystarczająco wyedukowanych w dziedzinie bezpieczeństwa informacji.

Odnosi się to nie tylko do głębokiej i profesjonalnej, ale także najbardziej podstawowej wiedzy.

Powiedzmy, że programiści mają za zadanie stworzyć i wykonać świetne oprogramowanie.

Jednocześnie mogą łatwo przeoczyć możliwe problemy w kodzie, które mogą wiązać się z ryzykiem ataków – nie są po prostu w stanie stwierdzić, dlaczego ich kod może przyciągać potencjalne zagrożenia.

Najprostsze wyjaśnienie tej sytuacji wiąże się z obszarem tzw. ogólnej kultury biznesowej.

Jedna z podstawowych zasad bezpieczeństwa mówi, że zabezpieczenia nie mogą być „doklejone” do gotowego produktu. Na przykład – niedopuszczalnym jest tworzenie słabo zabezpieczonego systemu tylko dlatego, że jest to system wewnętrzny dostępny jedynie za pośrednictwem Intranetu (w miejscu teoretycznie wolnym od większych zagrożeń).

Isaak Asimov opisał 3 prawa robotyki, z których pierwsze brzmi:

„Robot nie może skrzywdzić człowieka, ani przez zaniechanie działania dopuścić, aby człowiek doznał krzywdy.”

Jednocześnie, sam sobie zaprzeczył w swojej powieści „The Naked Sun”, gdzie opisał niedoskonałość tych praw. Nie można uniknąć niezamierzonej krzywdy – jeśli jeden robot przygotowuje truciznę i odstawia ją w określone miejsce (bez złych zamiarów), a drugi przynosi swojemu właścicielowi szklankę trującego napoju (nie wiedząc, że był zatruty), popełniają wspólnie morderstwo. Pomimo tego, że każdemu z nich zabroniono popełniania przestępstw przeciwko człowiekowi.

Klasycznym przykładem tego typu okoliczności w kontekście IT jest wypuszczenie niedostatecznie zabezpieczonej usługi do publicznego Internetu.

Developer, który stworzył te usługę rozumie, że zabezpieczenia są raczej prymitywne. Ale wie również, że rozwiązanie będzie przeznaczone do użytku wewnętrznego w bezpiecznej sieci, więc będzie niedostępne poza lokalną siecią.

Załóżmy, że developer zaniedbał przekazanie pozostałym informacji dotyczących potencjalnego ryzyka związanego z bezpieczeństwem w kontekście tej usługi, a po jakimś czasie pojawia się w zespole nowa osoba odpowiedzialna za dalszy rozwój i utrzymanie rozwiązania. Następnie firma, która jest właścicielem usługi zaczyna zlecać niektóre zadania na zewnątrz i zewnętrzni programiści otrzymują dostęp do projektu.

W tym momencie serwer ma styczność z setkami skryptów, które przeszukują go pod kątem słabych punktów. Wybieranie haseł „na siłę” jest również jednym z zagrożeń. Prawdopodobnie hasła używane w ramach niewielkiej wewnętrznej sieci są bardzo proste, konta testowe pozostają nietknięte, a dodatkowe zabezpieczenia na serwerze SSH nie były instalowane od ostatnich 2 lat.

Kto jest winien?

Zdrowy rozsądek podpowiada nam, że przede wszystkim powinniśmy winić osobę, która była odpowiedzialna za wypuszczenie systemu do sieci publicznej – nie udało się jej bowiem zawrzeć w  aplikacji właściwych zabezpieczeń.

Ale ta osoba mogła nie wiedzieć, że było tam konto administratora zabezpieczone absurdalnie prostym hasłem, albo, że developer pozostawił stronę diagnostyczną dostępną za pomocą prostego URLa, który nie był dodatkowo zabezpieczony hasłem. Jeśli pierwszy programista poświęciłby uwagę bezpieczeństwu i upewnił się, że zabezpieczenia są na maksymalnym poziomie, możnaby całkowicie uniknąć tego typu problemów.

Z tego powodu powinniśmy dbać o bezpieczeństwo systemów od pierwszego dnia ich rozwoju. Niewiele osób przywiązuje do tego dostateczną wagę. Nie dlatego, że są płytkie czy leniwe, ale dlatego, że większość z nich nie miała nigdy do czynienia z kwestiami bezpieczeństwa.

Załóżmy, że część rozwiązania została stworzona przez developera na poziomie junior, który tworzył, testował i dopracowywał aplikację przez 2 dni. Teraz jest pewien, że wszystko jest idealne. Z drugiej strony – senior developer zauważy błędy w kodzie na pierwszy rzut oka. Ale senior nie musi wszystkiego testować wszystkiego – z miejsca powie, w których miejscach są problemy.

„Sieć neuronowa” wykwalifikowanego programisty jest zdecydowanie lepiej wytrenowana w dziedzinie docelowej i jest on w stanie z łatwością przewidzieć wzorce zachowania systemu (od razu, bez przemyślenia). Jednocześnie, ten sam wykwalifikowany developer może przeoczyć ogromną lukę w zabezpieczeniach, którą przed sekundą sam stworzył. Ma wyczucie, ale w innych kwestiach niż te związane z bezpieczeństwem.

Jeśli ktoś rzuci jakąś wskazówkę związaną z zagrożeniem szybko zrozumie, o czym mowa. Ale często jest jednak w stanie coś przeoczyć pozostawiony sam sobie.

Istnieje osobna dyscyplina zwana Modelowaniem Zagrożeń, która pozwala na identyfikację potencjalnych zagrożeń i wektorów ataku. Determinuje również wartość zasobów, które mogą zostać zagubione w wyniku tych ataków. Można uczyć się tego nie tylko w związku z IT, ale programiści niewątpliwie powinni zrozumieć pewne metody z tego obszaru (co stanowi nieodzowną część podstawowej wiedzy w IT).

Równie niezbędną rzeczą w przypadku developerów jest znajomość typowych słabych punktów systemów. Powinni być świadomi możliwych błędów i tego, w jaki sposób mogą być one wykorzystane przez osoby z zewnątrz, by złamać te systemy.

Przypuszczam, że większość developerów wie, czym jest przepełnienie bufora i dlaczego, pisząc w C, powinniśmy unikać używania standardowych funkcji bibliotek, które wpisują dane w bufor bez ograniczenia rozmiaru. Odpowiedź jest prosta: w systemach z otwartą rejestracją, użytkownik może stworzyć login o długości, która przewyższy granice bufora. W rezultacie procesor może być zmuszony wykonywać jego komendy, nie twoje.

To dość „znoszony” przykład – ten problem jest znany od dziesięcioleci. Obecnie można jednak łatwo znaleźć na GitHubie projekt open-source z pierwszą linią obejmującą nielimitowany tekst i ograniczony bufor, w którym dane poza kontrolą aplikacji (dostarczane przez użytkowników) są wykorzystywane jako dane wejściowe.

Wydaje mi się, że branża już straciła nadzieję, że developerzy nauczą się zapobiegać temu zjawisku. W związku z tym, głównym sposobem radzenia sobie z tą sytuacją jest zabezpieczenie przeciw wykonaniu kodu ze stosu po stronie hardware albo wbudowane zabezpieczenie przeciwko dostępowi albo nadpisywaniu danych w jakiejkolwiek części pamięci w językach wysokopoziomowych.

Mimo, że język C stale traci popularność, wciąż jest dość szeroko używany oraz właściwie bezkonkurencyjny w przypadku urządzeń wbudowanych i IoT, ponieważ nic innego nie będzie współpracować ze słabym procesorem.

W Javie taki słaby punkt jak przepełnienie bufora nie istnieje. Ale niezależnie od tego, jakiego używasz języka, można w nim znaleźć trywialne słabości. Może to być serwer, który zwraca plik z katalogu upload o nazwie niemożliwej do dołączenia ../../.. do nazwy pliku i – w rezultacie – wychodzi poza granice katalogu upload.

Podobne problem były obserwowane przez wiele lat, możemy więc zasugerować, że w 2018 nie będzie aplikacji, w której będzie można osiągnąć stan idealny.

Ze względu na to uważam, że szkolenia w zakresie bezpieczeństwa są niesamowicie ważne w przypadku developerów. Przede wszystkim, powinni oni znać najpopularniejsze techniki łamania systemów. Tylko wtedy będą w stanie zidentyfikować słabe punkty własnego kodu.

Ciąg dalszy nastąpi…