Dane oryginału

Original edition copyright © Copyright © 2018, 2013, 2012, 2008 John Wiley & Sons, Inc. All rights reserved. Title of English-language original: Operating System Concepts, 10th Edition by Abraham Silberschatz, Greg Gagne, Peter B. Galvin. Polish edition copyright 2021 by Polish Scientific Publishers PWN Wydawnictwo Naukowe PWN Spółka Akcyjna. All rights reserved.

All Rights Reserved. This translation published under license with the original publisher John Wiley & Sons, Inc.

Przekład Zdzisław Płoski

Projekt okładki polskiego wydania Joanna Andryjowicz

Wydawca Wioleta Szczygielska-Dybciak

Redaktor prowadzący Jolanta Kowalczuk

Redaktor Irena Puchalska

Koordynator produkcji Anna Bączkowska

Skład wersji elektronicznej na zlecenie Wydawnictwo Naukowe PWN
Michał Latusek

Zastrzeżonych nazw firm i produktów użyto w książce wyłącznie w celu identyfikacji.

Copyright © for the Polish edition by Wydawnictwo Naukowe PWN SA

Warszawa 2021

ISBN 978-83-01-21639-9

eBook został przygotowany na podstawie wydania papierowego z 2021r. (Wydanie X (I w WN PWN))

Warszawa 2021

Wydawnictwo Naukowe PWN SA

02-460 Warszawa, ul. Gottlieba Daimlera 2

tel. 22 69 54 321, faks 22 69 54 288

infolinia 801 33 33 88

e-mail: pwn@pwn.com.pl, reklama@pwn.pl
www.pwn.pl

Bezpłatny fragment

Część 7. Bezpieczeństwo i ochrona

Rozdział 16. Bezpieczeństwo

16.1. Zagadnienie bezpieczeństwa

16.2. Zagrożenia programowe

16.3. Zagrożenia systemowe i sieciowe

16.4. Kryptografia jako narzędzie bezpieczeństwa

16.5. Uwierzytelnianie użytkownika

16.6. Realizacja obrony bezpieczeństwa

16.7. Przykład – Windows 10

16.8. Podsumowanie

Dalsze lektury

Bibliografia

Rozdział 17. Ochrona

17.1. Cele ochrony

17.2. Podstawy ochrony

17.3. Pierścienie ochrony

17.4. Domena ochrony

17.5. Macierz dostępów

17.6. Implementacja macierzy dostępów

17.7. Cofanie praw dostępu

17.8. Kontrolowanie dostępu według ról

17.9. Obligatoryjne kontrolowanie dostępu (MAC)

17.10. Systemy oparte na uprawnieniach

17.11. Inne metody ulepszania ochrony

17.12. Ochrona na poziomie języka

17.13. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 8. Tematy zaawansowane

Rozdział 18. Maszyny wirtualne

18.1. Przegląd

18.2. Rys historyczny

18.3. Korzyści i własności

18.4. Elementy konstrukcyjne

18.5. Rodzaje maszyn wirtualnych i ich implementacje

18.6. Wirtualizacja a komponenty systemu operacyjnego

18.7. Przykłady

18.8. Badania wirtualizacji

18.9. Podsumowanie

Dalsze lektury

Bibliografia

Rozdział 19. Sieci i systemy rozproszone

19.1. Zalety systemów rozproszonych

19.2. Struktura sieci

19.3. Struktura komunikacyjna

19.4. Sieciowe i rozproszone systemy operacyjne

19.5. Zagadnienia projektowe w systemach rozproszonych

19.6. Rozproszone systemy plików (DFS)

19.7. Nazewnictwo i przezroczystość DFS

19.8. Zdalny dostęp do plików

19.9. Końcowe przemyślenia dotyczące rozproszonych systemów plików

19.10. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 9. Przykłady konkretnych systemów

Rozdział 20. System Linux

20.1. Historia Linuxa

20.2. Podstawy projektu

20.3. Moduły jądra

20.4. Zarządzanie procesami

20.5. Planowanie

20.6. Zarządzanie pamięcią

20.7. Systemy plików

20.8. Wejście i wyjście

20.9. Komunikacja międzyprocesowa

20.10. Struktura sieci

20.11. Bezpieczeństwo

20.12. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 21. Windows 10

21.1. Historia

21.2. Podstawy projektu

21.3. Elementy systemu

21.4. Usługi terminalowe i szybkie przełączanie użytkowników

21.5. System plików

21.6. Praca sieciowa

21.7. Interfejs programisty

21.8. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 10. Dodatki

Dodatek A. Wpływowe systemy operacyjne

A.1. Wędrówka cech

A.2. Wczesne systemy

A.3. Atlas

A.4. XDS-940

A.5. THE

A.6. RC 4000

A.7. CTSS

A.8. MULTICS

A.9. IBM OS/360

A.10. TOPS-20

A.11. CP/M i MS-DOS

A.12. Systemy operacyjne Macintosh i Windows

A.13. Mach

A.14. Systemy oparte na uprawnieniach: Hydra i CAP

A.15. Inne systemy

Dalsze lektury

Bibliografia

Dodatek B. Windows 7

B.1. Historia

B.2. Podstawy projektu

B.3. Elementy systemu

B.4. Usługi terminalowe i szybkie przełączanie użytkowników

B.5. System plików

B.6. Praca sieciowa

B.7. Interfejs programisty

B.8. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Dodatek C. BSD UNIX

C.1. Historia UNIX-a

C.2. Podstawy projektu

C.3. Interfejs programisty

C.4. Interfejs użytkownika

C.5. Zarządzanie procesami

C.6. Zarządzanie pamięcią

C.7. System plików

C.8. System wejścia-wyjścia

C.9. Komunikacja międzyprocesowa

C.10. Podsumowanie

Dalsze lektury

Bibliografia

Dodatek D. System Mach

D.1. Historia systemu Mach

D.2. Podstawy projektu

D.3. Elementy systemu

D.4. Zarządzanie procesami

D.5. Komunikacja międzyprocesowa

D.6. Zarządzanie pamięcią

D.7. Interfejs programisty

D.8. Podsumowanie

Dalsze lektury

Bibliografia

Ćwiczenia

Ćwiczenia do rozdziału 16

Ćwiczenia do rozdziału 17

Ćwiczenia do rozdziału 18

Ćwiczenia do rozdziału 19

Ćwiczenia do rozdziału 20

Ćwiczenia do rozdziału 21

Źródła i prawa

O Autorach

Przypisy

Część 7

Bezpieczeństwo i ochrona

W ramach organizacji bezpieczeństwa są zapewniane środki uwierzytelniania (sprawdzania tożsamości) użytkowników systemu w celu ochrony integralności przechowywanych w nim informacji (zarówno danych, jak i kodu) oraz zasobów fizycznych systemu komputerowego. System bezpieczeństwa zapobiega nieupoważnionemu dostępowi, złośliwemu uszkadzaniu lub zmienianiu danych oraz przypadkowemu wprowadzaniu niespójności.

Mechanizmy ochrony kontrolują dostęp do systemu przez ograniczanie rodzajów dostępu do plików udzielanych użytkownikom. Ponadto ochrona ma zapewnić, że z segmentów pamięci, jednostki centralnej i innych zasobów będą mogły korzystać tylko te procesy, które otrzymały odpowiednie upoważnienia od systemu operacyjnego.

Ochrona jest realizowana za pomocą mechanizmu, który nadzoruje dostęp programów, procesów lub użytkowników do zasobów zdefiniowanych w systemie komputerowym. Mechanizm ten powinien umożliwiać określanie zasad nadzoru, jak również pozwalać na ich egzekwowanie.

ROZDZIAŁ 16

Bezpieczeństwo

Zarówno ochrona, jak i bezpieczeństwo są niezwykle istotne w odniesieniu do systemów komputerowych. Rozróżniamy te dwa pojęcia następująco. Bezpieczeństwo jest miarą zaufania, że system i jego dane pozostaną nienaruszone. Ochrona jest zbiorem mechanizmów służących do kontrolowania dostępu procesów i użytkowników do zasobów zdefiniowanych przez system komputerowy. W tym rozdziale skupiamy się na bezpieczeństwie, a ochroną zajmujemy się w rozdziale 17.

Bezpieczeństwo obejmuje strzeżenie zasobów komputerowych przed nieupoważnionym dostępem, złośliwym uszkodzeniem lub zmianą oraz przypadkowym wprowadzeniem niespójności. Do zasobów komputerowych należą informacje przechowywane w systemie (zarówno dane, jak i kod), a także CPU, pamięć główna, pamięć drugorzędna, pamięć trzeciorzędna oraz usieciowienie będące na wyposażeniu komputera. W tym rozdziale zaczynamy od przeanalizowania sytuacji, w których może dochodzić do przypadkowych lub umyślnych nadużyć zasobów. Potem koncentrujemy się na zasadniczym czynniku stanowiącym o bezpieczeństwie – kryptografii. Na koniec przyglądamy się mechanizmom odpierania lub wykrywania ataków.

CELE ROZDZIAŁU

• Omówienie zagrożeń bezpieczeństwa i związanych z nimi ataków.

• Wyjaśnienie zasad szyfrowania, uwierzytelniania i haszowania.

• Przeanalizowanie zastosowań kryptografii w technice komputerowej.

• Opisanie różnych środków przeciwdziałania atakom na bezpieczeństwo.

16.1. Zagadnienie bezpieczeństwa

W wielu zastosowaniach znaczny wysiłek mający na celu zapewnienie bezpieczeństwa jest wart zachodu. Duże systemy komercyjne zawierające listy płac lub inne dane finansowe są zachęcającym celem dla złodziei. Systemami zawierającymi dane dotyczące działalności firmy może być zainteresowana pozbawiona skrupułów konkurencja. Co więcej, utracenie takich danych – czy to przypadkowe, czy wskutek defraudacji – może poważnie osłabić zdolność przedsiębiorstwa do funkcjonowania. Nawet surowe zasoby komputerowe są atrakcyjne dla atakujących do pozyskiwania bitcoinów, wysyłania spamu[1] oraz jako punkt wyjścia do przypuszczania anonimowych ataków na inne systemy.

W rozdziale 17 omawiamy mechanizmy, które mogą być dostarczane przez system operacyjny (z odpowiednią pomocą ze strony sprzętu), aby użytkownicy mogli chronić swoje zasoby, w tym programy i dane. Mechanizmy te działają poprawnie tylko wówczas, gdy użytkownicy przestrzegają przewidzianego sposobu dostępu do tych zasobów i ich wykorzystania.

Mówimy, że system jest bezpieczny (secure), jeśli w każdych okolicznościach dostęp do jego zasobów i sposób ich wykorzystania jest zgodny z założonym. Niestety, osiągnięcie całkowitego bezpieczeństwa nie jest możliwe. Niemniej jednak musimy mieć jakieś mechanizmy zapewniające, że naruszenia bezpieczeństwa będą zjawiskami rzadkimi i nie staną się normą.

Naruszenia bezpieczeństwa (czyli nadużycia) systemu można podzielić na rozmyślne (złośliwe) i przypadkowe. Łatwiej jest chronić się przed nadużyciami przypadkowymi niż przed złośliwymi. Mechanizmy ochrony w większości stanowią podstawę unikania wypadków. Lista, którą podajemy niżej, zawiera kilka rodzajów przypadkowych i złośliwych naruszeń bezpieczeństwa. Zwracamy uwagę, że omawiając problemy bezpieczeństwa, do określenia osobników usiłujących naruszyć bezpieczeństwo używamy terminów intruz (intruder), haker (hacker) lub atakujący (napastnik; attacker). Oprócz tego zagrożenie (threat) oznacza potencjalną możliwość naruszenia bezpieczeństwa, na przykład odkrycie jakiejś słabości w systemie, natomiast przez atak (attack) rozumie się próbę złamania bezpieczeństwa.

Naruszenie poufności (breach of confidentiality) polega na nieupoważnionym czytaniu danych (lub kradzieży informacji). Intruz stawia sobie zwykle za cel naruszenie poufności. Przechwycenie utajnionych danych z systemu lub ze strumienia danych, na przykład informacji dotyczących karty kredytowej lub tożsamości (dokonane w celu kradzieży tożsamości) albo przedpremierowych wydań filmów czy podręczników, może oznaczać bezpośrednie korzyści majątkowe dla intruza i kłopoty dla zhakowanej instytucji.

Naruszenie integralności (pogwałcenie nienaruszalności; breach of integrity) oznacza nieupoważnioną modyfikację danych. Tego rodzaju atak może na przykład spowodować obciążenie odpowiedzialnością kogoś niewinnego lub zmianę kodu źródłowego ważnej aplikacji komercyjnej, względnie aplikacji o otwartym źródle.

Naruszenie dostępności (breach of availability) obejmuje nieuprawnione zniszczenie danych. Niektóre ataki mają charakter pustoszący i są powodowane chęcią „pokazania się” (są przejawem chełpliwości), a nie żądzą zysku. Oszpecanie (lub niszczenie) stron internetowych jest typowym przykładem tego typu naruszeń bezpieczeństwa.

Kradzież usług (theft of service) polega na nieupoważnionym wykorzystywaniu zasobów. Intruz (lub program włamywacza) mógłby na przykład zainstalować demona w systemie działającym jako serwer plików.

Odmowa świadczenia usług (denial of service – DoS) powoduje uniemożliwienie użytkowania systemu przez upoważnione podmioty. Program pomyślany pierwotnie jako robak internetowy przeobraził się w atak DoS, gdy z powodu błędu nie zdołał opanować swojego gwałtownego rozprzestrzeniania. Atakami DoS zajmiemy się w punkcie 16.3.2.

Dążąc do naruszenia bezpieczeństwa, atakujący używają kilku klasycznych metod. Najpopularniejszą jest maskarada (udawanie; masquerading), w której pewien uczestnik komunikacji udaje kogoś innego (inny host lub inną osobę). Udający napastnicy pokonują procedury uwierzytelniania (authentication), czyli poprawne sprawdzanie tożsamości; mogą w ten sposób uzyskać dostęp w normalnych warunkach im nienależny. Innym typowym atakiem jest powtórzenie przechwyconej wymiany danych. Atak powtórkowy (powtórka, atak przez ponowienie; replay attack) polega na złośliwym lub oszukańczym powtarzaniu transmisji dozwolonych danych. Czasami cały atak składa się tylko z powtórki, na przykład w przypadku powtórzenia zlecenia przelewu pieniędzy. Często jednak powtórce towarzyszy zmiana komunikatu (message modification), kiedy to atakujący zmienia przesyłane dane bez wiedzy nadawcy. Pomyślmy, do jakich szkód mogłoby dojść, gdyby na żądanie uwierzytelnienia dane legalnego użytkownika zostały zastąpione danymi użytkownika nieupoważnionego. Jeszcze innym rodzajem jest atak „człowiek pośrodku” (man-in-the-middle attack), w którym atakujący znajduje się na drodze przepływu komunikowanych danych, udając nadawcę przed odbiorcą i na odwrót. W komunikacji sieciowej atak „człowiek pośrodku” może być poprzedzony uprowadzeniem sesji (session hijacking), podczas którego dochodzi do przechwycenia aktywnej sesji łączności.

Celem innej szerokiej klasy ataków jest eskalacja przywilejów (wzrost przywilejów; privilege escalation). Każdy system wyposaża użytkowników w przywileje – nawet wówczas, gdy istnieje tylko jeden użytkownik, będący zarazem jego administratorem. System na ogół zawiera kilka zbiorów przywilejów: jeden dla konta każdego użytkownika i parę dla systemu. Przywileje są często nadawane również osobom nie będącym użytkownikami systemu (np. użytkownikom z Internetu odwiedzającym stronę w Sieci bez logowania lub anonimowym użytkownikom takich usług, jak przesyłanie plików). Nawet nadawcę e-maila (listelu) do zdalnego systemu można uważać za posiadającego przywileje, a mianowicie – przywilej wysyłania poczty elektronicznej do odbiorcy w tamtym systemie. Eskalacja przywilejów powoduje, że atakujący dostają więcej przywilejów niż im przysługuje. Na przykład przesyłka elektroniczna zawierająca skrypt lub makrodefinicję („makro”), które zostają wykonane, przekracza przywileje pocztowe nadawcy. Do eskalacji przywilejów często służą wspomniane maskarada i zmiana. Przykładów jest znacznie więcej, gdyż jest to bardzo popularny typ ataku. Wykrywanie i zapobieganie rozmaitym atakom tego rodzaju jest naprawdę bardzo trudne.

Jak już zauważyliśmy, całkowita ochrona systemu przed złośliwymi nadużyciami nie jest możliwa, lecz ich koszty dla potencjalnego sprawcy można uczynić na tyle wysokimi, aby stały się wystarczającą zaporą dla większości intruzów. W pewnych przypadkach, takich jak atak polegający na odmowie świadczenia usług, jest wskazane zapobieganie atakowi, lecz wystarczy go wykryć, aby móc przedsięwziąć środki zaradcze (np. filtrowanie w górę strumienia lub dołożenie zasobów w takiej ilości, by atak nie spowodował odmowy usług uprawnionym użytkownikom).

Aby chronić system, musimy w dokonywaniu zabezpieczeń uwzględnić cztery poziomy:

1. Fizyczny. Miejsce lub miejsca rozlokowania systemów komputerowych muszą być zabezpieczone fizycznie przed wtargnięciem intruzów. Należy zabezpieczyć hale maszyn, a także terminale i komputery mające dostęp do docelowych maszyn, na przykład ograniczając dostęp do budynku, w którym się znajdują, lub przytwierdzając je do biurek, na których stoją.

2. Sieciowy. Większość obecnych systemów komputerowych – od serwerów, poprzez urządzenia mobilne, aż po urządzenia internetu rzeczy (Internet od Things – IoT) – jest podłączonych do sieci. Praca w sieci umożliwia systemowi dostęp do zewnętrznych zasobów, lecz również jest potencjalnym źródłem nieupoważnionych dostępów do niego samego.

Co więcej, dane komputerowe w nowoczesnych systemach często podróżują prywatnymi, dzierżawionymi liniami, liniami dzielonymi (jak Internet), łączami bezprzewodowymi oraz liniami telefonicznymi. Przechwycenie tych danych może być równie bolesne jak włamanie do komputera, a przerwa w komunikacji może stanowić zdalny atak odmowy świadczenia usług, zmniejszając z punktu widzenia użytkownika przydatność systemu i zaufanie, którym go obdarzał.

3. Poziom systemu operacyjnego. System operacyjny wraz z wbudowanymi w niego aplikacjami i usługami stanowi olbrzymią bazę kodu, która może skrywać wiele słabych punktów. Niebezpieczne ustawienia domyślne, zła konfiguracja i błędy w zabezpieczeniach to tylko kilka potencjalnych problemów. Systemy operacyjne muszą więc być na bieżąco uaktualniane (przez ciągłe łatanie) i „hartowane”, tj. konfigurowane i modyfikowane tak, aby zmniejszyć powierzchnię ataku i uniknąć penetracji. Przez powierzchnię ataku (attack surface) rozumie się zbiór punktów, w których atakujący może próbować włamania do systemu[2].

4. Poziom aplikacji. Aplikacje pochodzące z rozmaitych źródeł również mogą stanowić ryzyko, zwłaszcza jeśli uzyskają znaczne przywileje. Niektóre aplikacje są z natury złośliwe (z założenia służą nikczemnym celom), lecz nawet pożyteczne aplikacje mogą być wadliwie zabezpieczone. Z powodu olbrzymiej liczby aplikacji pochodzących z różnych źródeł i najróżniejszych baz kodu, na których są oparte, praktycznie nie da się zagwarantować, żeby wszystkie te aplikacje były bezpieczne.

Ten czterowarstwowy model bezpieczeństwa jest pokazany na rysunku 16.1.

Rys. 16.1. Czterowarstwowy model bezpieczeństwa

Czterowarstwowy model bezpieczeństwa jest niczym łańcuch złożony z ogniw – słabość w którejkolwiek jego warstwie może prowadzić do całkowitego opanowania systemu. Pod tym względem stare powiedzenie, że bezpieczeństwo jest tylko tak mocne jak jego najsłabsze ogniwo, jest stale aktualne.

Innym elementem, którego nie wolno przeoczyć, jest czynnik ludzki. Z całą starannością należy dokonywać uwierzytelniania, aby zapewnić, że do systemu będą mieli dostęp tylko zaufani użytkownicy. Jednak nawet uwierzytelnieni użytkownicy mogą być nikczemni lub „zachęcani” do pozwalania innym na korzystanie z ich praw dostępu – dobrowolnie lub wskutek omotania metodami socjotechnicznymi (social engineering), w których wykorzystuje się podstęp, aby przekonać ludzi do udzielenia poufnych informacji. Jednym z rodzajów ataku metodą inżynierii socjotechnicznej jest wyłudzanie (phishing[3]; phishing); polega on na tym, że e-mail lub witryna sprawiające uczciwe wrażenie oszukańczo nakłaniają użytkownika do podania poufnych informacji. Czasami wystarczy kliknięcie odsyłacza („linka”) na stronie wyszukiwarki lub w e-mailu, aby nieopatrznie pobrać złośliwy ładunek (payload) narażający na szwank bezpieczeństwo systemu w komputerze użytkownika. Na ogół dany pecet nie jest ostatecznym celem, lecz chodzi tu o coś cenniejszego. Z opanowanego systemu rozpoczynają się ataki wymierzone przeciw innym systemom w sieci lokalnej lub innym użytkownikom.

Wiemy już zatem, że jeżeli bezpieczeństwo ma być zachowane, należy brać pod uwagę wszystkie cztery czynniki czterowarstwowego modelu oraz czynnik ludzki. System musi ponadto zapewniać ochronę (omówioną szczegółowo w rozdz. 17), aby umożliwiać realizację elementów bezpieczeństwa. Bez zdolności nadawania uprawnień użytkownikom i procesom, warunkujących kontrolowanie ich dostępu, i bez rejestrowania ich działań system operacyjny nie zdołałby realizować środków bezpieczeństwa ani funkcjonować bezpiecznie. Do całościowego urzeczywistniania schematu ochrony niezbędne jest wsparcie sprzętowe. Na przykład system bez ochrony pamięci nie może być bezpieczny. Nowe własności sprzętu umożliwiają zwiększanie bezpieczeństwa systemów, do czego jeszcze wrócimy.

Niestety, w dziedzinie bezpieczeństwa prawie nic nie jest proste. Skoro intruzi wyszukują i wykorzystują słabości, opracowuje się i wdraża środki zaradcze. To powoduje, że intruzi uciekają się do coraz bardziej wymyślnych ataków. Na przykład oprogramowanie szpiegujące (spyware) może spowodować, że w systemach, bez ich wiedzy, zostaną otwarte kanały przekazywania spamu (ten proceder omawiamy w podrozdz. 16.2), co z kolei może posłużyć do ataków wyłudzających, nakierowanych na inne cele. Ta zabawa w kotka i myszkę zdaje się nie mieć końca, co powoduje popyt na coraz więcej narzędzi bezpieczeństwa do powstrzymywania eskalacji technik i działalności intruzów.

W pozostałej części rozdziału zajmujemy się bezpieczeństwem na poziomie sieci i systemu operacyjnego. Bezpieczeństwo na poziomie aplikacji, fizycznym oraz ludzkim – choć ważne – w znacznej mierze wykracza poza zakres tego podręcznika. Bezpieczeństwo w systemie operacyjnym i między systemami operacyjnymi jest realizowane kilkoma metodami, poczynając od stosowania haseł do uwierzytelniania, poprzez zwalczanie wirusów, na wykrywaniu włamań kończąc. Zaczniemy od zbadania zagrożeń bezpieczeństwa.

16.2. Zagrożenia programowe

Procesy, w tym procesy jądrowe, są jedynymi środkami wykonywania prac na komputerze. Dlatego popularnym celem atakujących jest napisanie programu, który naruszy bezpieczeństwo lub spowoduje, że normalny proces zmieni zachowanie i spowoduje takie naruszenie. W rzeczywistości nawet większość nieprogramowych incydentów związanych z bezpieczeństwem służy do wywołania zagrożenia programowego. Na przykład, choć logowanie się w systemie bez upoważnienia jest przydatne, znacznie przydatniejsze jest pozostawienie w nim demona tylnych drzwi (back-door) lub narzędzia zdalnego dostępu („szczura”; Remote Access Tool – RAT), które będzie dostarczać informacji lub umożliwi łatwy dostęp nawet wówczas, gdy oryginalny eksploit (program-wyzyskiwacz; exploit) zostanie zablokowany. W tym podrozdziale opisujemy typowe metody, którymi programy naruszają bezpieczeństwo. Zauważmy, że do określania luk w bezpieczeństwie istnieje sporo różnych konwencji nazewniczych, korzystamy więc z najpopularniejszych lub najbardziej obrazowych terminów.

16.2.1. Malware

Malware, czyli szkodliwe (nikczemne, wrogie) oprogramowanie (malware) służy do wyzyskania, dezorganizacji lub uszkadzania systemów komputerowych. Jest wiele sposobów uprawiania takiego procederu, a my zajmiemy się w tym punkcie ich głównymi odmianami.

Wiele systemów ma mechanizmy, dzięki którym programy pisane przez jednych użytkowników mogą być wykonywane przez innych użytkowników. Jeżeli te programy są wykonywane w domenie praw dostępu[4] użytkownika-wykonawcy, to inni użytkownicy mogą nadużyć tych praw. Program, który działa potajemnie lub złośliwie, zamiast po prostu wykonywać przypisaną mu funkcję, nazywa się koniem trojańskim (trojanem; Trojan horse). Jeśli taki program działa w innej domenie, może eskalować (zwiększyć) swoje przywileje. Aby to zilustrować, pomyślmy o – dajmy na to – mobilnej apce latarce, na pozór zupełnie nieszkodliwej, która ukradkiem sięga do kontaktów lub esemesów użytkownika i szmugluje je na zdalny serwer.

Klasyczną odmianą konia trojańskiego jest „osioł trojański” („Trojan mule”), który emuluje (naśladuje) program logowania. Niczego niepodejrzewający użytkownik rozpoczyna sesję na terminalu, komputerze lub na stronie w Sieci i zauważa, że najwyraźniej się pomylił w pisaniu hasła. Próbuje jeszcze raz i wszystko jest w porządku. Tymczasem jego klucz uwierzytelniający i hasło zostały ukradzione przez emulator logowania pozostawiony na komputerze przez atakującego lub uruchomiony wskutek podania błędnego lokalizatora URL. Emulator wyprowadził hasło z systemu, wydrukował komunikat o błędzie logowania i zakończył działanie. Użytkownikowi przedstawiono następnie prawdziwe zaproszenie do logowania. Atak tego rodzaju można udaremnić, jeśli system operacyjny drukuje komunikat o sposobie użycia po zakończeniu sesji interakcyjnej, wymagając wykonania nieprzechwytywalnej operacji klawiszowej do uzyskania zaproszenia do logowania (np. naciśnięcia kombinacji klawiszy control-alt-delete używanej przez wszystkie współczesne systemy operacyjne Windows) lub przez upewnienie się użytkownika, że ma do czynienia z właściwym, poprawnym lokalizatorem URL.

Inną odmianą konia trojańskiego jest oprogramowanie szpiegujące (spyware). Towarzyszy ono czasem programowi, który użytkownik wybrał do zainstalowania. Najczęściej występuje w programach bezpłatnych lub rozpowszechnianych na licencji shareware, czasami jednak jest dołączane do oprogramowania komercyjnego. „Szpieg” może sprowadzać reklamy do wyświetlenia w systemie użytkownika, uruchamiać okna wyskakujące w przeglądarkach podczas odwiedzania pewnych witryn lub przechwytywać informacje z systemu użytkownika i przekazywać je do centrali. Zainstalowanie niewinnie wyglądającego programu w systemie Windows może spowodować załadowanie demona szpiegującego. Program szpiegujący może skontaktować się z centralnym stanowiskiem, odebrać komunikat i listę adresów odbiorców, po czym spamować do nich z maszyny z systemem Windows. Ten proces może trwać dopóty, dopóki użytkownik nie odkryje szpiega. A oprogramowanie takie często pozostaje niewykryte. W 2010 roku szacowano, że 90% spamu było dostarczane tą metodą. Taka kradzież usług w większości krajów nie jest nawet uważana za przestępstwo!

Dość nowym, niepożądanym przejawem „postępu” jest klasa szkodliwego oprogramowania, które nie kradnie informacji. Oprogramowanie zbójeckie (oprogramowanie szantażujące; ransomware) szyfruje część lub wszystkie dane w komputerze docelowym, czyniąc je niedostępnymi dla użytkownika. Same informacje nie mają wielkiego znaczenia dla atakującego, lecz stanowią wielką wartość dla właściciela. Chodzi tu o wymuszenie od właściciela zapłaty (okupu) za otrzymanie klucza deszyfrowania potrzebnego do odszyfrowania danych. Rzecz jasna, jak w przypadku innych kontaktów z kryminalistami, zapłacenie okupu nie gwarantuje zwrócenia dostępu.

Trojany i inny malware mają się szczególnie dobrze tam, gdzie nie jest przestrzegana zasada najmniejszych przywilejów (principle of least privilege). Zdarza się to powszechnie, gdy system operacyjny domyślnie nadaje więcej przywilejów niż potrzeba zwykłemu użytkownikowi lub gdy użytkownik działa domyślnie jako administrator (co było faktem we wszystkich systemach Windows aż do wersji Windows 7). W takich przypadkach własny system odpornościowy systemu operacyjnego – różnego rodzaju pozwolenia i ochrona – nie mogą „stawić oporu”, więc malware potrafi trwać i przetrwać kolejne rozruchy, a nawet szerzyć się zarówno lokalnie, jak i przez sieć.

Naruszenie zasady najmniejszych przywilejów jest przypadkiem kiepskich decyzji podejmowanych przy projektowaniu systemu operacyjnego. System operacyjny (a w istocie całe oprogramowanie) powinien umożliwiać drobnoziarniste kontrolowanie dostępu i bezpieczeństwa, aby wykonywanemu zadaniu były nadawane tylko niezbędne przywileje. Sprawowanie kontroli musi być łatwe i zrozumiałe. Niewygodne, nieadekwatne i źle pojmowane środki bezpieczeństwa są skazane na obchodzenie, co powoduje ogólne osłabienie bezpieczeństwa, które w założeniu miały urzeczywistniać.

W jeszcze innej odmianie szkodliwego oprogramowania osoba projektująca program lub system pozostawia w nim lukę, którą tylko ona potrafi wykorzystać. Ten rodzaj naruszenia bezpieczeństwa, zwany bocznymi lub tylnymi drzwiami (trap door lub back door) można było oglądać w filmie War Games (polski tytuł „Gry wojenne”). Na przykład kod mógłby zawierać sprawdzanie określonego identyfikatora lub hasła użytkownika i po otrzymaniu tego ID lub hasła mógłby obchodzić normalne procedury bezpieczeństwa. Programiści wykorzystywali boczne drzwi do oszukiwania banków przez umieszczanie w swoim kodzie błędnych zaokrągleń i okazjonalne półcentowe dopiski do swoich kont. Tak kredytowane konto potrafiło urosnąć do dużych sum, zważywszy liczbę transakcji wykonywanych przez duży bank.

ZASADA NAJMNIEJSZYCH PRZYWILEJÓW

„Zasada najmniejszych przywilejów. Każdy program i każdy uprzywilejowany użytkownik systemu powinien działać z możliwie najmniejszym zbiorem przywilejów niezbędnych do wykonania danego zadania. Zasada ta ma na celu zredukowanie liczby potencjalnych interakcji między uprzywilejowanymi programami do niezbędnego minimum potrzebnego do poprawnego działania, aby można było osiągnąć pewność, że nie dojdzie do niezamierzonego, niechcianego lub niewłaściwego użycia przywileju”. Tak to ujął Jerome H. Saltzer, opisując zasadę projektową systemu operacyjnego Multics w 1974 roku (zob. https://multicians.org/saltzer-pacisim.pdf.

Boczne drzwi mogą być ustawione na zadziałanie tylko przy zaistnieniu określonego zbioru warunków logicznych; nazywa się je wówczas bombą logiczną (logic bomb). Boczne drzwi tego typu są szczególnie trudne do wykrycia, ponieważ pozostają uśpione przez długi czas, być może latami, zanim zostaną ujawnione – zwykle już po dokonaniu spustoszeń. Pewien administrator na przykład dokonał niszczącej rekonfiguracji sieci swojej firmy, co nastąpiło wtedy, kiedy jego program wykrył, że ów administrator już w niej nie pracuje.

Bardzo sprytne boczne wejście można zainstalować w kompilatorze. Kompilator mógłby generować standardowy kod wynikowy oraz boczne wejście – niezależnie od kodu źródłowego, który kompiluje. Jest to działalność szczególnie perfidna, gdyż przeglądanie programu źródłowego[5] nie uwidoczni żadnych problemów. Takie boczne drzwi można wykryć tylko za pomocą wstecznej inżynierii kodu kompilatora. Ten typ ataku może również być wykonany po fakcie przez łatanie kompilatora lub bibliotek fazy kompilacji. I rzeczywiście, w 2015 roku malware wycelowany przeciw kompletowi kompilatora Apple (zwane „XCodeGhost”) dotknął wielu twórców oprogramowania, który używali zainfekowanych wersji XCode, niepobranych bezpośrednio od Apple’a.

Boczne drzwi stanowią trudny problem, ponieważ aby je wykryć, musimy przeanalizować cały kod źródłowy wszystkich komponentów systemu. Zważywszy, że oprogramowanie może liczyć miliony wierszy kodu, nieczęsto wykonuje się taką analizę, a często nie wykonuje się jej nigdy! Metodyką w procesie rozwojowym oprogramowania, mogącą pomóc w przeciwdziałaniu pojawianiu się tego typu luk w bezpieczeństwie, jest przegląd kodu (code review). W ramach przeglądu kodu budowniczy, który napisał kod, przekazuje go do bazy kodu, a jedna lub więcej osób z zespołu konstrukcyjnego przegląda ten kod i akceptuje go lub opatruje komentarzami. Gdy grupa przeglądających zaakceptuje kod (czasami po uwzględnieniu komentarzy, ponownym przedłożeniu kodu do przeglądu i ponownym jego przejrzeniu), kod jest przyjmowany do bazy kodu, po czym kompilowany, czyszczony z błędów i na koniec oddawany do użytku. Wielu solidnych twórców oprogramowania korzysta w pracy z systemów kontroli wersji, które dostarczają narzędzi do przeglądania kodu – na przykład takich jak git (https://github.com/git)[6]. Zauważmy też, że istnieją zautomatyzowane narzędzia przeglądania i analizowania kodu, zaprojektowane w celu znajdowania braków, w tym niedociągnięć w zakresie bezpieczeństwa, jednak dobrzy programiści są z reguły najlepszymi recenzentami kodu.

Dla niezaangażowanych w opracowywanie kodu przegląd kodu jest przydatny do znajdowania i raportowania uchybień (lub do ich znajdowania i wykorzystywania!). Kod źródłowy większości oprogramowania nie jest udostępniany, co znacznie utrudnia przeglądanie kodu osobom spoza kręgu jego budowniczych.

16.2.2. Wstrzykiwanie kodu

Większość oprogramowania jest nieszkodliwa, lecz mimo to stwarza poważne zagrożenie bezpieczeństwa ze względu na atak wstrzykiwania kodu (code-injection attack) polegający na dodawaniu lub zmienianiu kodu wykonywalnego. Nawet oprogramowanie skądinąd pożyteczne i łagodne może skrywać czułe miejsca, które w przypadku wykorzystania umożliwiają atakującemu opanowanie programu, podporządkowanie swoim celom istniejącej w nim luki lub przeprogramowanie go w całości przez zastąpienie nowym kodem.

Ataki polegające na wstrzykiwaniu kodu są prawie zawsze wynikiem słabych lub niebezpiecznych praktyk programistycznych, najczęściej w językach niskiego poziomu, takich jak C lub C++, które umożliwiają bezpośredni dostęp do pamięci za pomocą wskaźników. Ów bezpośredni dostęp do pamięci wraz z koniecznością starannego określania rozmiarów buforów pamięci i dbałości o ich nieprzekraczanie mogą w razie braku właściwego potraktowania prowadzić do uszkodzeń zawartości pamięci.

Jako przykład rozważmy najprostszego nosiciela wstrzykiwania kodu – przepełnienie bufora. Program na rysunku 16.2 ilustruje przepełnienie, do którego dochodzi wskutek nieograniczonej operacji kopiowania, wywoływanej przez strcpy(). Ta funkcja wykonuje kopiowanie, nie zważając na długość docelowego bufora; zatrzymuje się dopiero po wykryciu bajta NULL (\0). Jeśli taki bajt wystąpi przed końcem bufora (określonym przez ROZMIAR_BUFORA), to program będzie działał tak, jak przewidziano. Jednak kopiowanie może łatwo przekroczyć rozmiar bufora – i co wtedy?

#include <stdio.h>

#include <string.h>

#define ROZMIAR_BUFORA 0

int main(int argc, char *argv[])

{

int j = 0;

char bufor[ROZMIAR_BUFORA];

int k = 0;

if (argc < 2) {return -1;}

strcpy(bufor, argv[1]);

printf("K = %d, J = %d, bufor zawiera %s\n", j, k, bufor);

return 0;

}

Rys. 16.2. Program w języku C z sytuacją przepełnienia bufora

Odpowiedź jest taka, że skutek przepełnienia bufora w dużym stopniu zależy od długości przepełnienia i tego, co ono zawiera (rys. 16.3). Różni się również znacznie zależnie od kodu generowanego przez kompilator, którego optymalizacje mogą na niego oddziaływać – często polegają one na dostrajaniu rozmieszczenia danych w pamięci (zwykle jest to przestawianie lub dopełnianie długości zmiennych).

Rys. 16.3. Możliwe skutki przepełnienia bufora

1. Jeśli przepełnienie jest bardzo małe (odrobinę większe niż ROZMIAR_BUFORA), to jest szansa, że zostanie niezauważone. Wynika to z tego, że przydział ROZMIAR_BUFORA bajtów będzie często wyrównany (uzupełniony) do granicy określonej przez architekturę (zwykle do 8 lub 16 B). Uzupełnienie to jest nieużywaną pamięcią, dlatego trafiający do niej nadmiar, technicznie poza granicami, nie wywołuje żadnego efektu.

2. Jeśli przepełnienie przekracza wyrównanie, to następna zmienna automatyczna na stosie (lokalna) zostanie zapisana przepełniającą treścią. Skutek w tym przypadku zależy od dokładnego usytuowania zmiennej i od jej znaczenia (jeśli np. jest ona używana w warunku logicznym, to może być wykorzystana do spowodowania nadużycia). Takie niekontrolowane przepełnienie może doprowadzić do załamania programu, ponieważ nieoczekiwana wartość zmiennej może spowodować nieusuwalny błąd.

3. Jeśli przepełnienie znacznie przekracza wyrównanie, to zapisana zostaje cała ramka bieżącej funkcji na stosie. W ramce, u samej góry, jest adres powrotu z funkcji używany, gdy następuje wyjście z funkcji. Przepływ sterowania w programie zostaje podporządkowany atakującemu, który może tak zmienić adres powrotu, aby przejść w inne miejsce pamięci, w szczególności kontrolowane prze niego (może to być np. bufor wejściowy, stos lub sterta). Następuje wówczas wykonanie wstrzykniętego kodu, co umożliwia atakującemu wykonanie dowolnego kodu z efektywnym ID opanowanego procesu).

Zauważmy, że staranny programista mógłby sprawdzić ograniczenie rozmiaru, kontrolując parametr argv[1] za pomocą funkcji strncpy() zamiast strcpy(), czyli zastępując wiersz strcpy(bufor, argv[1]); wierszem strncpy(bufor, argv[1], sizeof(bufor)-1);. Niestety, porządne sprawdzanie granic jest wyjątkiem, a nie normą. Funkcja strcpy() jest jedną ze znanej klasy wrażliwych funkcji, do których należą też sprintf(), gets() i inne funkcje niekontrolujące długości bufora. Jednak nawet odmiany z kontrolą rozmiaru bufora mogą kryć zagrożenia, gdy zostaną połączone z operacjami arytmetycznymi na liczbach całkowitych skończonej długości[7], co może prowadzić do powstania nadmiaru całkowitego.

W tym miejscu niebezpieczeństwa związane z prostym niedopatrzeniem w obchodzeniu się z buforem powinny być już całkiem jasne. Brian Kernighan i Dennis Ritchie (w książce The C Programming Language) odwoływali się do możliwych skutków „nieokreślonych zachowań”, lecz i doskonale przewidywalne zachowanie może być nagięte przez atakującego, co po raz pierwszy zostało zademonstrowane przez Robaka Morrisa (i udokumentowane w raporcie RFC1135, https://tools.ietf.org/html/rfc1135). Jednak dopiero kilka lat później, w 49 numerze magazynu Phrack, ukazał się artykuł Smashing the Stack for Fun and Profit[8] (http://phrack.org/issues/49/14.html), który przybliżył techniki eksploatatorskie masowemu odbiorcy, rozpętując istny potop eksploitów.

Aby dokonać wstrzyknięcia kodu, najpierw musi powstać kod nadający się do wstrzyknięcia. Atakujący pisze więc krótki segment kodu w rodzaju następującego:

void func (void) {

execvp("/bin/sh", "/bin/sh", NULL); ;

}

Używając wywołania systemowego execvp() kod ten tworzy proces powłoki. Jeśli atakowany program działa z pozwoleniami superużytkownika („roota”), to nowo utworzona powłoka uzyska pełny dostęp do systemu. Taki segment kodu może oczywiście zrobić wszystko, na co pozwalają przywileje zaatakowanego procesu. Segment kodu jest następnie kompilowany do postaci binarnego kodu operacji asemblerowych, a potem przekształcany na strumień binarny. Ta skompilowana postać często nosi nazwę kodu powłokowego (shellcode) ze względu na to, że jej typową funkcją jest rodzenie powłoki, jednak z czasem zaczęto tym terminem określać kod dowolnego typu, również bardziej zaawansowany kod używany do dodawania nowych użytkowników do systemu, rozruchu systemu lub nawet do realizowania połączeń sieciowych i oczekiwania na zdalne polecenia (nazywany „odwróconą powłoką”). Eksploita kodu powłokowego pokazano na rysunku 16.4. Kod ten jest używany krótko, służy tylko do przekierowania wykonywania w inne miejsce niczym swoista odskocznia „odbijająca” przepływ sterowania w kodzie z jednego miejsca do drugiego.

Rys. 16.4. Odskocznia do wykonania kodu z wykorzystaniem przepełnienia bufora

Prawdę mówiąc, istnieją kompilatory kodu powłokowego (znakomitym przykładem jest projekt „MetaSploit”), które troszczą się również o takie szczegóły, jak zapewnienie, żeby kod był zwarty i nie zawierał bajtów NULL (znaków pustych), które w przypadku gdy eksploit ma postać łańcucha[9], symbolizują jego koniec. Taki kompilator może nawet maskować kod powłokowy pod postacią znaków alfanumerycznych.

Jeśli atakującemu uda się zmienić adres powrotu (lub jakiś wskaźnik funkcji, np. w VTable), to pozostaje już tylko (w prostym przypadku) przejść do dostarczonego kodu powłokowego, który zwykle jest ładowany jako część danych wejściowych użytkownika, za pośrednictwem zmiennej środowiskowej, z jakiegoś pliku albo z sieci. Zakładając, że nie ma żadnych środków zaradczych (o czym dalej), wystarczy to do wykonania kodu powłokowego, a więc i do udanego ataku hakera. Kwestie wyrównywania (odpowiedniego uzupełnienia) są często załatwiane przez poprzedzenie kodu powłokowego ciągiem rozkazów NOP („nic nie rób”). Uzyskiwany efekt nazywa się sankami NOP (NOP-sled), gdyż powoduje „zjazd” rozkazami NOP aż do napotkania ładunku (payload) i jego wykonania.

Ten przykład ataku z przepełnieniem bufora pokazuje, że do zrozumienia działania eksploatatorskiego kodu i jego wykorzystania trzeba sporej wiedzy i umiejętności programistycznych. Niestety, żeby przypuszczać ataki na bezpieczeństwo, nie trzeba być wielkim programistą. Haker wykrywa lukę i pisze eksploita. Natomiast każdy, kto ma elementarne umiejętności i dostęp do eksploita – osobnika takiego zwykło się nazywać skryptoszkrabem (script kiddie) – może wówczas przymierzyć się do ataku na obrane systemy[10].

Atak z przepełnieniem bufora jest szczególnie zgubny, gdyż można go wywoływać z jednego systemu na drugim i może się on przenosić kanałami komunikacyjnymi. Ataki tego rodzaju mogą występować w protokołach, o których się zakłada, że będą używane do komunikacji z docelową maszyną, dlatego ich wykrywanie i zapobieganie im może być trudne. Potrafią one nawet obchodzić zabezpieczenia dodawane przez zapory sieciowe (p. 16.6.6).

Podkreślmy, że przepełnienia buforów są tylko jedną z kilku dróg, którymi można dążyć do wstrzykiwania kodu. Można również wykorzystywać przepełnienia występujące na stercie. Używanie buforów pamięciowych po ich zwolnieniu oraz po superzwolnieniu (overfreeing, dwukrotne wywołanie funkcji free()) także może prowadzić do wstrzykiwania kodu.

16.2.3. Wirusy i robaki

Innego rodzaju zagrożeniem programowym jest wirus (virus). Wirus jest fragmentem kodu wbudowanym w legalny program. Wirusy potrafią się same namnażać i są pomyślane tak, aby „zarażały” inne programy. Mogą pustoszyć systemy, modyfikując lub niszcząc pliki i powodując załamania systemów lub złe działanie programów. Jak większość ataków penetrujących (bezpośrednie ataki na system), wirusy są mocno powiązane z architekturami, systemami operacyjnymi i aplikacjami. Wirusy są szczególnym problemem dla użytkowników PC. UNIX i inne systemy operacyjne z wieloma użytkownikami na ogół nie są narażone na wirusy, ponieważ programy są chronione przed zapisem przez system operacyjny. Jeśli nawet wirus zainfekuje taki program, jego możliwości są zwykle ograniczone z powodu ochrony innych aspektów systemu.

Wirusy są zwykle przenoszone spamem pocztowym lub w atakach wyłudzających. Mogą się także szerzyć, gdy użytkownicy pobierają zawirusowane programy za pomocą internetowych usług dzielenia się plikami lub przez wymianę zarażonych dysków. Można dokonać rozróżnienia między wirusami wymagającymi aktywności człowieka a robakami (worms), które używają sieci do replikowania się bez ludzkiej pomocy.

Aby dać przykład „zarażania” hosta przez wirusa, rozważmy pliki pakietu Microsoft Office. Mogą one zawierać „makra” (lub większe programy w języku Visual Basic), które będą automatycznie wykonywane w pakiecie Office (Word, PowerPoint i Excel). Ponieważ programy te działają na własnym koncie użytkownika, makrodefinicje takie mogą w większości działać bez ograniczeń (np. usuwając pliki użytkownika wedle jego woli). Następujący przykład kodu ukazuje, jak łatwo można napisać makrodefinicję w Visual Basicu, którą robak mógłby wykorzystać do sformatowania twardego dysku komputera z systemem Windows zaraz po otwarciu zawierającego ją pliku:

Sub AutoOpen()

Dim oFS

Set oFS = CreateObject("Scripting.FileSystemObject")

vs = Shell("c: command.com /k format c:",vbHide)

End Sub

Robak na ogół będzie również wysyłał siebie pocztą elektroniczną pod inne adresy znalezione na liście kontaktów użytkownika.

Jak działają wirusy? Gdy wirus osiągnie docelową maszynę, program zwany zakraplaczem wirusów (virus dropper)[11] wstawia wirusa do systemu. Zakraplacz wirusów jest zwykle koniem trojańskim wykonywanym z innych powodów, lecz jego głównym zadaniem jest zainstalowanie wirusa. Po zainstalowaniu wirus może wykonać dowolną z paru czynności. Istnieją dosłownie tysiące wirusów, lecz można je podzielić na kilka głównych kategorii. Dodajmy, że wiele wirusów należy do więcej niż jednej kategorii.

• Plikowy. Standardowy wirus plikowy zaraża system, dołączając się do pliku. Zmienia on początek programu tak, aby wykonywanie zaczęło się od skoku do jego kodu. Gdy skończy, zwraca sterowanie do programu, dlatego jego działanie pozostaje niezauważone. Wirusy plikowe są czasami określane jako wirusy pasożytnicze, gdyż nie pozostawiają po sobie całych plików i pozwalają na dalsze funkcjonowanie programu, w którym goszczą.

• Rozruchowy („bootsektorowy”). Wirus rozruchowy infekuje sektor rozruchowy systemu, działając przy każdym rozruchu systemu, przed załadowaniem systemu operacyjnego. Poszukuje innych nośników nadających się do rozruchu i je zaraża. Te wirusy są również nazywane wirusami pamięciowymi, gdyż nie występują w systemie plików. Na rysunku 16.5 pokazano, jak działa wirus rozruchowy. Wirusy rozruchowe są również przystosowane do infekowania oprogramowania układowego, takiego jak środowiska kart sieciowych PXE lub Extensible Firmware Interface (EFI).

Rys. 16.5. Komputerowy wirus sektora rozruchowego

• Makrowirus („makro”; macro). Większość wirusów jest pisanych w językach niskiego poziomu, w asemblerze lub w C. Makrowirusy są pisane w języku wysokiego poziomu, na przykład w Visual Basicu. Są one wyzwalane, gdy programowi wolno wykonać takie makro. Makrowirus mógłby na przykład być zawarty w pliku arkusza kalkulacyjnego.

• Rootkit (zestaw korzeniowy). Ten termin, pierwotnie ukuty do określania tylnych drzwi w systemach uniksowych, oznaczał umożliwianie łatwego dostępu do superużytkownika („roota”); z czasem jego zakresem objęto wirusy i szkodliwe oprogramowanie (malware) infiltrujące sam system operacyjny. Prowadzi to do całkowitego opanowania systemu – żadnego elementu systemu nie można uznać za godny zaufania. Gdy malware zaraża system operacyjny, może przejąć kontrolę nad wszystkimi funkcjami systemowymi, łącznie z tymi, które normalnie ułatwiałyby jego wykrywanie.

• Kod źródłowy. Wirus kodu źródłowego poszukuje kodu źródłowego i modyfikuje go tak, aby zawierał wirusa i pomagał w jego rozprzestrzenianiu.

• Polimorficzny. Wirus polimorficzny zmienia się przy każdym instalowaniu, aby uniknąć wykrycia przez oprogramowanie antywirusowe. Zmiany te nie dotyczą funkcjonowania wirusa, lecz tylko jego sygnatury. Sygnatura wirusa (virus signature) jest wzorcem, za pomocą którego można zidentyfikować wirusa, zazwyczaj ciągiem bajtów tworzących kod wirusa.

• Szyfrowany. Wirus szyfrowany zawiera kod deszyfrujący wraz z zaszyfrowanym wirusem, co również służy do uniknięcia wykrycia. Wirus najpierw się deszyfruje, a potem działa.

• Niewidzialny (stealth). Ten sprytny wirus stara się uniknąć wykrycia przez zmodyfikowanie części systemu, których można by użyć do jego identyfikacji. Mógłby na przykład tak zmodyfikować systemową funkcję czytania (read), żeby podczas czytania pliku, który zmodyfikował, była zwracana oryginalna postać kodu, a nie zainfekowana.

• Wieloczęściowy. Wirus tego rodzaju potrafi zarażać wiele części systemu, w tym sektory rozruchowe, pamięć i pliki. Utrudnia to jego wykrywanie i powstrzymywanie.

• Opancerzony. Wirus opancerzony jest mętny, tzn. tak napisany, aby tropicielom wirusów trudno było go rozwikłać i zrozumieć. Może być jeszcze skompresowany, aby uniknąć wykrycia i dezynfekcji. Ponadto zakraplacze wirusów i inne pełne pliki będące częścią plagi wirusowej są często ukrywane za pomocą (odpowiedniego ustawiania) atrybutów plikowych lub nieuwidacznianych nazw plików.

Ta olbrzymia różnorodność wirusów ciągle się zwiększa. Na przykład w 2004 roku wykryto szeroko rozpowszechnionego wirusa. Do działania wykorzystał on trzy osobne błędy. Zaczął od zainfekowania setek serwerów (w tym na wielu zaufanych stanowiskach) wykonujących microsoftowe oprogramowanie Internet Information Server (IIS). Odwiedzając te stanowiska, każda podatna przeglądarka sieciowa Microsoft Explorer zarażała się tym wirusem przy każdym pobraniu. Wirus przeglądarkowy instalował kilka programów z tylnymi drzwiami, w tym rejestrator naciskanych klawiszy (keystroke logger), który zapisywał wszystko, co było wprowadzane z klawiatury (w tym hasła i numery kart kredytowych). Instalował również demona umożliwiającego intruzowi nieograniczony zdalny dostęp i drugiego, który pozwalał intruzowi kierować spam przez zainfekowany pecet.

Tocząca się w społeczności komputerowej debata na temat bezpieczeństwa skupia się wokół istnienia monokultury, w której w wielu systemach działa ten sam sprzęt, system operacyjny i oprogramowanie użytkowe. Domniemywa się, że na ową monokulturę składają się wyroby Microsoftu. Powstaje pytanie, czy taka monokultura w ogóle dzisiaj istnieje. Drugie pytanie – zakładając jej istnienie – dotyczy tego, czy zwiększa ona zagrożenie uszkodzeniami powodowanymi przez wirusy i inne rodzaje naruszeń bezpieczeństwa. Informacje o podatnych, słabych punktach są kupowane i sprzedawane w takich miejscach jak ciemna Sieć (dark web, systemy w Sieci osiągalne za pośrednictwem nietypowych konfiguracji klientów lub nietypowymi metodami). Im więcej systemów może być dotkniętych atakiem, tym bardziej taki atak się opłaca.

16.3. Zagrożenia systemowe i sieciowe

Już same zagrożenia programowe poważnie narażają bezpieczeństwo. Lecz tworzone przez nie ryzyko wzrasta o rzędy wielkości, gdy system jest podłączony do sieci. Światowa łączność powoduje, że system staje się narażony na ataki z całego świata.

Im większe jest otwarcie systemu – im więcej ma on włączonych usług i im więcej umożliwia funkcji – tym większe jest prawdopodobieństwo, że jakiś błąd tylko czeka, aby z niego zrobić użytek. W coraz większym stopniu dąży się do tego, aby systemy operacyjne były domyślnie zabezpieczone (secure by default). Na przykład Solaris 10 przeszedł od modelu, w którym wiele usług (FTP, telnet i in.) było udostępnianych domyślnie w czasie instalowania systemu, do modelu, w którym podczas instalowania wyłącza się prawie wszystkie usługi i muszą być one indywidualnie włączane przez administratorów systemu. Takie zmiany zmniejszają powierzchnię ataku systemu.

Wszyscy hakerzy zostawiają po sobie ślad – czy to w postaci wzorców w ruchu sieciowym, czy nietypowych rodzajów pakietów lub w inny sposób. Z tego powodu hakerzy często atakują z systemów zombie (zombie systems), czyli opanowanych przez nich samodzielnych systemów lub urządzeń, które nadal służą swoim użytkownikom, choć jednocześnie bez wiedzy właścicieli są wykorzystywane do nikczemnych celów, w tym do ataków odmowy świadczenia usług lub do przekazywania spamu. Zombie sprawiają, że wykrycie hakerów staje się szczególnie trudne, ponieważ maskują oryginalne źródło ataku i tożsamość atakującego. Jest to jedna z wielu przyczyn zabezpieczania „mało znaczących” systemów, a nie tylko systemów zawierających „cenne” informacje lub usługi, żeby nie przekształciły się one w hakerskie twierdze.

Powszechność połączeń szerokopasmowych i sieci WiFi jeszcze bardziej pogłębiła trudności w śledzeniu hakerów – nawet prosta maszyna biurkowa, którą często łatwo przejąć przez malware, może stać się cenna, jeśli zostanie wykorzystana jej szerokość pasma lub dostęp do sieci. Bezprzewodowy Ethernet ułatwia atakującym przypuszczanie ataków przez anonimowe włączanie się do sieci publicznej lub „WarDriving”[12], tj. lokalizowania prywatnej, niechronionej sieci w drodze do celu.

16.3.1. Atakowanie ruchu sieciowego

Sieci są często atrakcyjnymi celami, a hakerzy mają wiele możliwości aranżowania ataków sieciowych. Jak pokazano na rysunku 16.6, atakujący może wybrać postawę pasywną i przechwytywać ruch sieciowy (atak powszechnie określany mianem węszenia; sniffing), niejednokrotnie uzyskując w ten sposób przydatne informacje o typach sesji prowadzonych między systemami lub ich treści. Atakujący może też przyjąć rolę aktywniejszą, udając którąś ze stron (co jest określane jako podszywanie się; spoffing)[13] albo stając się w pełni aktywnym „człowiekiem pośrodku” (man-in-the-middle), przechwytującym i być może modyfikującym transakcje między oboma partnerami.

Dalej opisujemy popularny typ ataku sieciowego – odmowę świadczenia usług (denial of service – DoS). Zwracamy uwagę, że przed atakami można się ustrzec za pomocą takich środków, jak szyfrowanie i uwierzytelnianie, które są omówione dalej w tym rozdziale. Jednak protokoły sieciowe nie zawierają domyślnie ani szyfrowania, ani uwierzytelniania.

Rys. 16.6. Typowe ataki na bezpieczeństwo[14]

16.3.2. Odmowa świadczenia usług

Jak już wiemy, ataki odmowy świadczenia usług nie mają na celu uzyskania informacji lub kradzieży zasobów, lecz chodzi w nich o zaburzenie uprawnionego (normalnego) korzystania z systemu lub infrastruktury. Większość takich ataków dotyczy systemów lub infrastruktury, do których atakujący nie przeniknął. Podejmowanie ataku uniemożliwiającego legalne użytkowanie jest często łatwiejsze niż włamywanie się do systemu lub infrastruktury.

Ataki odmowy świadczenia usług zwykle są oparte na sieci. Dzielą się na dwa rodzaje. W atakach pierwszego rodzaju używa się tak wielu zasobów infrastruktury, że praktycznie nie da się już wykonać żadnej pożytecznej pracy. Na przykład kliknięcie w jakiejś witrynie sieciowej mogłoby spowodować pobranie apletu Javy, który będzie zużywał cały dostępny czas CPU lub powodował nieustanne wyskakiwanie okien. Drugi rodzaj obejmuje uszkodzenie sieci obsługującej dane rozwiązanie. Odnotowano wiele udanych ataków odmowy świadczenia usług tego rodzaju skierowanych przeciwko ważnym witrynom sieciowym. Ataki takie, mogące trwać godzinami, a nawet dniami, spowodowały częściową lub całkowitą niemożność skorzystania z rozwiązań, które stały się ich celem. Ataki te są zwykle powstrzymywane na poziomie sieci do czasu, aż uda się zaktualizować systemy operacyjne, żeby zmniejszyć ich podatność.

Ogólnie biorąc, atakom odmowy świadczenia usług nie da się zapobiec. Wykorzystywane są w nich te same mechanizmy co w normalnych działaniach. Jeszcze trudniej jest zapobiegać i przeciwdziałać rozproszonym atakom odmowy świadczenia usług (Distributed Denial-of-Service – DDoS). Te ataki są kierowane z wielu stanowisk, zwykle przez komputery zombie przeciw wspólnemu celowi. Ataki DDoS stały się bardziej popularne i są niekiedy połączone z próbami szantażowania. Stanowisko zostaje zaatakowane, a atakujący proponują przerwanie ataku w zamian za pieniądze.

Czasami stanowisko w sieci nawet nie rozeznaje, że podlega atakowi. Trudno jest odróżnić, czy spowolnienie systemu jest skutkiem ataku, czy po prostu gwałtownym wzrostem zapotrzebowania na jego usługi. Zważmy, że udana kampania reklamowa, znacznie zwiększająca ruch w kierunku danego stanowiska, może być uznana za atak DDoS.

Z atakami DoS wiążą się inne ciekawe zagadnienia. Jeśli na przykład algorytm uwierzytelniania zamyka konto na pewien czas po kilku nieudanych próbach dostępu do konta, to atakujący mógłby spowodować zablokowanie całego uwierzytelniania, celowo powodując nieudane próby dostępu do wszystkich kont. Podobnie zapora sieciowa, która automatycznie blokuje pewne rodzaje ruchu, mogłaby zostać skłoniona do blokowania ruchu, którego blokować nie powinna. Te przykłady pokazują, że programiści i zarządcy systemów muszą dogłębnie rozumieć wdrażane przez siebie algorytmy i techniki. Dodajmy na koniec, że zajęcia z informatyki są notorycznie źródłami przypadkowych ataków DoS na system. Weźmy pierwsze ćwiczenia z programowania, na których student uczy się tworzyć podprocesy lub wątki. Typowy błąd polega na rodzeniu podprocesów w nieskończoność. Wolna pamięć systemu i zasoby CPU nie mają szans!

16.3.3. Skanowanie portów

Samo skanowanie portów (port scanning) nie jest atakiem, lecz środkiem, za pomocą którego haker wykrywa czułe miejsca w systemie, podatne na atak. (Personel odpowiedzialny za bezpieczeństwo też posługuje się skanowaniem portów, np. do wykrywania niepotrzebnych usług lub takich, których użycia się nie przewiduje). Skanowanie portów zwykle odbywa się automatycznie, z udziałem narzędzia, które próbuje utworzyć połączenie TCP/IP lub wysłać pakiet UDP do określonego portu lub grupy portów.

Skanowanie portów jest nieraz częścią techniki zwiadowczej określanej jako zdejmowanie odcisków palców, za pomocą której atakujący próbuje dociec typu używanego systemu operacyjnego i zbioru jego usług, aby zidentyfikować znane słabości. Wiele serwerów i klientów ułatwia to przez dokładne ujawnianie swoich numerów wersji w nagłówkach protokołu sieciowego (np. nagłówki „Server:” i „User-Agent:” w protokole HTTP). Szczegółowa analiza swoistych zachowań procedur obsługujących protokoły też może pomóc atakującemu wydedukować, jakiego systemu operacyjnego używa obrany cel – jest to krok niezbędny do udanej eksploracji.

Skanery słabych miejsc w sieci są sprzedawane jako wyroby handlowe. Istnieją też narzędzia wykonujące podzbiory funkcji kompletnego skanera. Na przykład nmap (z http://www.insecure.org/nmap/) jest dość uniwersalnym przyborem o otwartym źródle, służącym do eksploracji sieci i doglądania („audytowania”) bezpieczeństwa. Nakierowany na wybrany cel określi wykonywane usługi, w tym nazwy i wersje aplikacji. Potrafi zidentyfikować system operacyjny gospodarza. Potrafi też dostarczyć informacji o obronie, na przykład o rodzaju zapór sieciowych broniących cel. Nie wykorzystuje znanych błędów. Jednak inne narzędzia (takie jak Metasploit) wychodzą poza to, na czym poprzestają skanery portów, dostarczając rozwiązań konstrukcyjnych ładunku, którego można użyć do testowania podatności, lub takich, które można wykorzystać do utworzenia ładunku powodującego błąd.

Brzemienną w skutki pracę o skanowaniu portów można znaleźć pod http://phrack.org/issues/49/15.html. Techniki te podlegają nieustannej ewolucji, podobnie jak środki ich wykrywania (co stanowi podstawę omówionych dalej systemów wykrywania włamań sieciowych).

16.4. Kryptografia jako narzędzie bezpieczeństwa

Istnieje wiele sposobów obrony przed atakami komputerowymi, obejmujących całą gamę środków – od metodyk do technologii. Najpopularniejszym narzędziem dostępnym dla konstruktorów i użytkowników systemów jest kryptografia. W tym podrozdziale omawiamy kryptografię i jej zastosowanie w bezpieczeństwie komputerowym. Zwracamy uwagę, że podane tu ujęcie kryptografii zostało uproszone ze względów edukacyjnych; przestrzegamy Czytelników przed użyciem któregokolwiek z opisanych tu schematów w rzeczywistym świecie. Powszechnie dostępne są dobre biblioteki kryptograficzne i to one mogą stanowić dobrą podstawę do wytwarzania aplikacji.

W izolowanym komputerze system operacyjny może niezawodnie ustalić nadawcę i odbiorcę w całej komunikacji międzyprocesowej, ponieważ kontroluje wszystkie kanały komunikacyjne komputera. W sieci komputerów sytuacja jest zupełnie inna. Komputer sieciowy odbiera bity „z kabla” i nie ma tu natychmiastowego ani niezawodnego sposobu ustalenia, która maszyna lub aplikacja wysłała te bity. I podobnie, wysyłając bity siecią, komputer nie ma żadnej możliwości zorientowania się, kto może je ostatecznie odebrać. W dodatku zarówno przy wysyłce, jak i przy odbiorze system nie potrafi poznać, czy komunikowane treści nie były podsłuchiwane.

Do wnioskowania o potencjalnych nadawcach i odbiorcach komunikatów sieciowych używa się powszechnie adresów sieciowych. Pakiety sieciowe przybywają z adresem źródłowym, na przykład z adresem IP. Kiedy komputer wysyła komunikat, nazywa jego przewidywanego odbiorcę, określając adres przeznaczenia. Jednak w aplikacjach, w których duże znaczenie ma bezpieczeństwo, zakładanie, że adres źródłowy lub adres przeznaczenia pakietu niezawodnie określi, kto nadaje lub kto otrzymuje pakiet, jest szukaniem kłopotów. Komputer oszusta może wysłać komunikat z podrobionym adresem źródłowym, a zaadresowany pakiet może dotrzeć (i zwykle dociera) do wielu innych komputerów, nie tylko do podanego w adresie. Na przykład wszystkie rutery występujące na drodze do miejsca przeznaczenia również go otrzymają. Jak w takich warunkach system operacyjny ma rozstrzygnąć, czy udzielić zgody na jakieś zamówienie, jeśli nie może polegać na podanym źródle zamówienia? I na jakiej podstawie miałby oprzeć ochronę zamówienia lub danych, skoro nie potrafi określić, kto otrzyma odpowiedź lub zawartość komunikatu, którą posyła przez sieć?

Przyjmuje się ogólnie, że zbudowanie sieci o dowolnej skali, w której źródłowe i docelowe adresy pakietów byłyby godne zaufania w przedstawionym sensie, jest niemożliwe. Dlatego jedyną możliwością jest wyeliminowanie w jakiś sposób konieczności obdarzania sieci zaufaniem. To zadanie spoczywa na kryptografii. Ujmując abstrakcyjnie, kryptografia (cryptography) jest używana do ograniczania grona potencjalnych nadawców i (lub) odbiorców komunikatów. Nowoczesna kryptografia opiera się na tajemnicach zwanych kluczami (keys), które podlegają wybiórczemu upowszechnieniu między komputerami w sieci i są używane do przetwarzania komunikatów. Kryptografia umożliwia odbiorcy komunikatu sprawdzenie, czy komunikat utworzono na komputerze mającym pewien klucz. Podobnie nadawca może zaszyfrować swój komunikat, aby tylko komputer z pewnym kluczem mógł taki komunikat odszyfrować. W odróżnieniu jednak od adresów sieciowych klucze są konstruowane tak, aby nie dało się ich za pomocą obliczeń wyprowadzić z komunikatów, do wygenerowania których zostały użyte, i z żadnych innych jawnych informacji. Dostarczają one zatem znacznie pewniejszych środków ograniczania kręgu nadawców i odbiorców komunikatów.

Kryptografia jest silnym narzędziem, więc stosowanie kryptografii może być kwestią sporną. Niektóre kraje zakazują stosowania pewnych jej odmian lub ograniczają dopuszczalną długość klucza. W innych trwa debata, czy dostawcy technologii (np. dostawcy smartfonów) powinni zostawiać tylne drzwi (back door) do udostępnianej kryptografii, aby umożliwiać organom ścigania obchodzenie dostarczanej przez nią prywatności. Wielu obserwatorów uważa jednak, że tylne drzwi stanowią zamierzone osłabianie bezpieczeństwa, które mogłoby posłużyć atakującym lub nawet być nadużywane przez rządy.

Na koniec trzeba zauważyć, że kryptografia jest samodzielną dziedziną badań, pełną dużych i małych zawiłości i subtelności. Tutaj zajmiemy się najważniejszymi aspektami części kryptografii, która dotyczy systemów operacyjnych.

16.4.1. Szyfrowanie

Z uwagi na rozwiązywanie bardzo rozmaitych problemów bezpieczeństwa komunikacji szyfrowanie (encryption) znajduje wiele zastosowań w wielu zagadnieniach współczesnej komputeryzacji. Jest używane do bezpiecznego przesyłania komunikatów przez sieć, a także do ochrony danych w bazach, plików, a nawet całych dysków przed czytaniem ich zawartości przez nieupoważnione podmioty. Algorytm szyfrowania zapewnia nadawcy komunikatu, że tylko komputer mający pewien klucz będzie mógł ten komunikat przeczytać, lub gwarantuje, że zapisujący dane będzie jedynym ich czytelnikiem. Szyfrowanie wiadomości jest oczywiście praktyką sięgającą starożytności i od tamtych czasów powstało wiele algorytmów szyfrowania. W tym podrozdziale opisujemy ważne współczesne zasady i algorytmy szyfrowania.

W skład algorytmu szyfrowania wchodzą następujące elementy:

• Zbiór kluczy K.

• Zbiór komunikatów M.

• Zbiór tekstów zaszyfrowanych (szyfrogramów, szyfrówek; ciphertexts) C.

• Funkcja szyfrowania E: K → (M → C). To znaczy dla każdego k ∈ K, Ek jest funkcją generowania szyfrogramów z komunikatów. Zarówno E, jak i Ek dla dowolnego k powinny być funkcjami efektywnie obliczalnymi. Ogólnie biorąc, Ek jest losowym odwzorowaniem komunikatów na szyfrogramy.

• Funkcja deszyfrowania D: K → (C → M). To znaczy dla każdego k ∈ K, Dk jest funkcją generowania komunikatów z szyfrogramów. Zarówno D, jak i Dk dla dowolnego k powinny być funkcjami efektywnie obliczalnymi.

Algorytm szyfrowania musi mieć istotną własność – dla danego szyfrogramu c ∈ C komputer tylko wtedy potrafi obliczyć m, takie że Ek(m) = c, gdy ma k. Komputer dysponujący k może zatem deszyfrować teksty zaszyfrowane na teksty jawne, użyte do ich wytworzenia, natomiast komputer nie mający k nie może zdeszyfrować tekstu zaszyfrowanego. Ponieważ szyfrogramy są zwykle ujawniane (np. przesyła się je siecią), jest ważne, aby wyprowadzenie k na ich podstawie było niemożliwe.

Istnieją dwa główne typy algorytmów szyfrowania: symetryczny i asymetryczny. Omówimy je kolejno w następnych punktach.

16.4.1.1. Szyfrowanie symetryczne

W algorytmie szyfrowania symetrycznego (symmetric encryption algorithm) do szyfrowania i deszyfrowania używa się tego samego klucza. Dlatego k należy przechowywać w tajemnicy. Na rysunku 16.7 przedstawiono przykład dwóch użytkowników porozumiewających się bezpiecznie niezabezpieczonym kanałem z wykorzystaniem szyfrowania symetrycznego. Podkreślmy, że wymiana kluczy odbywa się tu bezpośrednio między obiema stronami lub z udziałem zaufanej strony trzeciej (czyli organu certyfikującego), co omówiono w podpunkcie 16.4.1.4.

Rys. 16.7. Bezpieczna komunikacja niezabezpieczonymi łączami[15]

W USA w minionych kilku dziesięcioleciach najczęściej używanym algorytmem szyfrowania symetrycznego do zastosowań cywilnych był standard szyfrowania danych (data encryption standard – DES) – szyfr przyjęty przez National Institute of Standards and Technology (NIST)[16]. DES działa z 64-bitową lub 56-bitową wartością klucza i wykonuje ciąg przekształceń opartych na operacjach podstawiania i przestawiania. Ponieważ DES działa porcjami na blokach bitów, określa się go jako szyfr blokowy (block cipher), a stosowane w nim przekształcenia są typowe dla szyfrów blokowych. Szyfry blokowe w przypadku używania tego samego klucza do dużej ilości danych stają się podatne na atak.

W wielu zastosowaniach DES uważa się dzisiaj za mało bezpieczny, ponieważ do znalezienia jego kluczy można posłużyć się przeszukiwaniem pełnym z użyciem umiarkowanej ilości zasobów obliczeniowych[17]. (Zauważmy jednak, że jest on wciąż w częstym użyciu). Zamiast zrezygnować z DES-a, NIST opracował jego modyfikację nazwaną potrójnym DES (triple DES), w której algorytm DES jest powtarzany trzykrotnie (dwa szyfrowania i jedno deszyfrowanie) na tym samym tekście jawnym z użyciem dwu lub trzech kluczy, na przykład c = Ek_3(Dk_2(Ek_1(m))). Gdy się używa trzech kluczy, efektywna długość klucza wynosi 168 bitów.

W 2001 roku NIST przyjął nowy szyfr blokowy, nazwany zaawansowanym standardem szyfrowania (advanced encryption standard – AES, znany również jako Rijndael[18]), ustandaryzowany w FIPS-197 (https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf). Można w nim używać kluczy długości 128, 192 lub 256 bitów, a szyfr działa na blokach 128-bitowych. Jest to, ogólnie biorąc, algorytm zwarty i wydajny.

Szyfry blokowe nie zawsze są bezpiecznymi schematami szyfrowania. W szczególności nie operują bezpośrednio na komunikatach dłuższych niż wymagane przez nie rozmiary bloków. Inną możliwością są szyfry strumieniowe, których można używać do bezpiecznego szyfrowania dłuższych wiadomości.

Szyfr strumieniowy (stream cipher) służy do szyfrowania i deszyfrowania strumienia bajtów lub bitów, a nie bloku. Jest to przydatne, gdy długość komunikacji spowodowałaby zbyt wolne działanie szyfru blokowego. Klucz jest podawany na wejściu generatora bitów pseudolosowych, który jest algorytmem starającym się losowo produkować bity. Po podaniu klucza, na wyjściu generatora pojawia się strumień kluczy. Strumień kluczy (potok kluczy; keystream) jest nieskończonym ciągiem bitów, którego można używać do szyfrowania strumienia tekstu jawnego za pomocą operacji XOR. (XOR, czyli wykluczające „lub”, jest operacją, która porównuje dwa bity na wejściu i generuje jeden bit wyniku. Jeśli bity są takie same, wynik wynosi 0. Jeśli bity są różne, wynik równa się 1). Zestawy szyfrów (cipher suites) oparte na AES-ie zawierają szyfry strumieniowe i są obecnie najpopularniejsze.

16.4.1.2. Szyfrowanie asymetryczne

W algorytmie szyfrowania asymetrycznego (asymmetric encryption algorithm) klucze szyfrowania i deszyfrowania są różne. Osoba przygotowująca się do odbierania zaszyfrowanych wiadomości tworzy dwa klucze i jeden z nich (zwany kluczem publicznym) czyni dostępnym dla każdego, kto go potrzebuje. Dowolny nadawca może posłużyć się tym kluczem do szyfrowania komunikacji, lecz tylko twórca klucza może ją odszyfrować. Ten schemat, znany jako szyfrowanie z kluczem jawnym (public-key encryption), okazał się przełomowy w kryptografii (po raz pierwszy opisany przez Diffiego i Hellmana, zob. https://ee.stanford.edu/~hellman/publications/24.pdf). Klucz nie musi już być trzymany w sekrecie[19] i dostarczany bezpiecznie. Zamiast tego każdy może zaszyfrować komunikat do odbiorcy i bez względu na to, kto tego jeszcze słucha, tylko odbiorca zdoła go odszyfrować.

Jako przykład działania szyfrowania z kluczem publicznym opiszemy algorytm RSA, nazywany tak od nazwisk jego wynalazców: Rivesta, Shamira i Adlemana. RSA jest najszerzej używanym algorytmem szyfrowania asymetrycznego. (Inna sprawa, że na popularności zyskują algorytmy oparte na krzywych eliptycznych, ponieważ ich klucze mogą być krótsze z zachowaniem tej samej siły kryptograficznej).

W RSA ke jest kluczem publicznym (public key), a kd jest kluczem prywatnym (private key). N jest iloczynem dwóch wielkich, losowo wybranych liczb pierwszych p i q (np. p i q mają po 2048 bitów). Wyprowadzenie kd,N z ke,N musi być obliczeniowo niewykonalne, aby ke nie trzeba utrzymywać w tajemnicy i można go było upowszechniać. Algorytm szyfrowania ma postać Eke,N (m) = mke mod N, gdzie ke spełnia równość kekd mod (p – 1) (q – 1) = 1. Algorytm deszyfrowania ma wtedy postać Dkd,N (c) = ckd mod N.

Przykład z użyciem małych wartości pokazujemy na rysunku 16.8. Przyjmujemy w nim p = 7 i q = 13. Następnie obliczamy N = 7 × 13 = 91 i (p – 1) (q – 1) = 72. Za ke obieramy dalej 5 jako względnie pierwsze z 72 i mniejsze niż 72. Na koniec obliczamy kd takie, że kekd mod 72 = 1, co daje 29. W ten sposób otrzymujemy klucze: publiczny ke,N = 〈5,91〉 i prywatny kd,N = 〈29,91〉. Szyfrowanie komunikatu 69 kluczem publicznym daje w wyniku komunikat 62, który odbiorca może zdeszyfrować za pomocą klucza prywatnego.

Rys. 16.8. Szyfrowanie i deszyfrowanie z użyciem kryptografii asymetrycznej RSA[20]

Korzystanie z szyfrowania asymetrycznego zaczyna się od upublicznienia klucza dotyczącego miejsca przeznaczenia. W przypadku komunikacji dwukierunkowej źródło musi również opublikować swój klucz publiczny. „Opublikowanie” może się odbyć po prostu przez przekazanie elektronicznej kopii klucza lub w sposób bardziej skomplikowany. Klucz prywatny (czyli „klucz tajny”) musi być pilnie strzeżony, ponieważ każdy, kto go posiada, może odszyfrować dowolny komunikat utworzony z użyciem pasującego do pary klucza publicznego.

Należy zauważyć, że pozornie mała różnica w użyciu kluczy kryptografii asymetrycznej i symetrycznej jest dość duża w praktyce. Kryptografia asymetryczna jest znacznie kosztowniejsza pod względem obliczeniowym. Komputer znacznie szybciej szyfruje i deszyfruje teksty za pomocą zwykłych algorytmów symetrycznych, niż używając algorytmów asymetrycznych. Dlaczego zatem mielibyśmy używać algorytmu asymetrycznego? Prawdę mówiąc, algorytmy te nie są stosowane do ogólnego szyfrowania dużych ilości danych. Jednak korzysta się z nich nie tylko do szyfrowania małych ilości danych, lecz także do uwierzytelniania, osiągania poufności i dystrybucji kluczy, co przedstawimy w dalszych podrozdziałach.

16.4.1.3. Uwierzytelnianie

Wiemy już, że szyfrowanie pozwala ograniczyć zbiór możliwych odbiorców komunikatu. Ograniczanie zbioru potencjalnych nadawców komunikatu nazywa się uwierzytelnianiem (authentication). Uwierzytelnianie jest zatem dopełniające względem szyfrowania. Uwierzytelnianie jest również przydatne do udowadniania, że komunikat nie został zmieniony. Dalej omawiamy uwierzytelnianie jako ograniczanie możliwych nadawców komunikatu. Zauważmy, że ten rodzaj uwierzytelniania przypomina uwierzytelnianie użytkownika omawiane w podrozdziale 16.5, lecz nie jest taki sam.

W skład algorytmu uwierzytelniania wchodzą następujące elementy:

• Zbiór kluczy K.

• Zbiór komunikatów M.

• Zbiór A uwierzytelniaczy (czynników uwierzytelniających; authenticators).

• Funkcja S : K → (M → A). To znaczy dla każdego k ∈ K, Sk jest funkcją generowania uwierzytelniaczy na podstawie komunikatów. Zarówno S, jak i Sk dla dowolnego k powinny być funkcjami efektywnie obliczalnymi.

• Funkcja V : K → (M × A → {true, false}). To znaczy dla każdego k ∈ K, Vk jest funkcją sprawdzania (weryfikowania) uwierzytelniaczy komunikatów. Zarówno V, jak i Vk dla dowolnego k powinny być funkcjami efektywnie obliczalnymi.

Krytyczna własność, którą musi mieć algorytm uwierzytelniania, wyraża się następująco – dla komunikatu m komputer tylko wtedy potrafi wygenerować uwierzytelniacz a ∈ A taki, że Vk(m, a) = true, gdy jest w posiadaniu k. A zatem komputer dysponujący k może generować uwierzytelniacze komunikatów, które dowolny inny komputer mający k będzie mógł zweryfikować. Natomiast komputer, który nie ma k, nie jest w stanie wytworzyć uwierzytelniaczy komunikatów, które można by sprawdzać za pomocą Vk. Ponieważ uwierzytelniacze są z reguły ujawniane (np. przesyła się je siecią wraz z komunikatami), wyprowadzenie k z uwierzytelniaczy powinno być niemożliwe. W praktyce, jeśli Vk(m, a) = true, to wiemy, że m nie został zmodyfikowany i że nadawca komunikatu ma k. Jeżeli dzielimy k tylko z jedną jednostką (osobą), to wiemy, że komunikat wywodzi się z k.

Analogicznie, do dwóch typów algorytmów szyfrowania istnieją dwie główne odmiany algorytmów uwierzytelniania. Pierwszym krokiem do zrozumienia tych algorytmów jest zaznajomienie się z funkcjami haszowania. Funkcja haszowania (funkcja mieszająca, funkcja skrótu; hash function) H(m) tworzy z komunikatu m mały, stałej długości blok danych określany jako skrót komunikatu (message digest) lub wartość haszowania (hash value). Funkcje haszowania pobierają komunikat, dzielą go na bloki i przetwarzają je, produkując n-bitowy skrót. H musi być odporna na kolizje, tzn. znalezienie m’ ≠ m takiego, że H(m) = H(m’) ma być niewykonalne. Jeśli więc mamy H(m) = H(m’), to wiemy, że m = m’, tzn. wiemy, że komunikat nie został zmodyfikowany. Do popularnych funkcji skrótu komunikatu należą MD5 (obecnie uważana za nie dość bezpieczną), wytwarzająca skrót 128-bitowy, i SHA-1, która wyprowadza skrót 160-bitowy. Skróty komunikatów przydają się do wykrywania zmienionych komunikatów, nie są natomiast przydatne jako uwierzytelniacze. Na przykład H(m) można wysłać wraz z komunikatem; jeżeli jednak H(m) jest znana, to ktoś mógłby zmienić m na m’ i wtórnie obliczyć H(m’), a wtedy zmiana treści komunikatu pozostałaby niewykryta. Dlatego musimy uwierzytelniać H(m).

W algorytmie uwierzytelniania pierwszego głównego typu używa się szyfrowania symetrycznego. W kodzie uwierzytelniającym komunikatu (message authentication code – MAC) jest generowana kryptograficzna suma kontrolna komunikatu z zastosowaniem klucza tajnego. Za pomocą MAC można bezpiecznie uwierzytelniać krótkie wartości. Jeżeli użyjemy go do uwierzytelnienia H(m), gdzie funkcja H jest odporna na kolizje, to otrzymamy sposób bezpiecznego uwierzytelniania długich komunikatów, wcześniej je haszując. Zauważmy, że k jest potrzebny zarówno do obliczania funkcji Sk, jak i Vk, więc każdy, kto potrafi obliczyć jedną, potrafi obliczyć drugą.

Drugi główny typ algorytmu uwierzytelniania to algorytm podpisu cyfrowego (digital signature algorithm), a wytwarzane w ten sposób uwierzytelniacze (poświadczenia) są nazywane podpisami cyfrowymi (digital signatures). Podpisy cyfrowe są bardzo przydatne, gdyż umożliwiają każdemu sprawdzenie autentyczności komunikatu. W algorytmie podpisu cyfrowego nie jest możliwe efektywne obliczeniowo wyprowadzenie ks z kv. Tak więc kv jest kluczem publicznym, a ks jest kluczem prywatnym.

Rozważmy jako przykład algorytm podpisu cyfrowego RSA. Jest on podobny do algorytmu szyfrowania RSA, lecz kluczy używa się tutaj odwrotnie. Podpis cyfrowy komunikatu jest wyprowadzany przez obliczenie mod N. Klucz ks znów jest parą 〈d, N〉, gdzie N jest iloczynem dwóch wielkich, losowo wybranych liczb pierwszych p i q. Algorytm weryfikacji przybiera wówczas postać gdzie kv spełnia równość kvks mod (p – 1) (q – 1) = 1. Podpisy cyfrowe (jak wiele innych aspektów kryptografii) można stosować nie tylko do komunikatów. Na przykład za pomocą podpisu cyfrowego twórcy programów mogą „podpisywać swój kod”, aby zaświadczać, że od czasu opublikowania, a przed zainstalowaniem na komputerze, nie uległ zmianie. Podpisywanie kodu (code signing) stało się bardzo popularną metodą poprawiania bezpieczeństwa w wielu systemach.

Zauważmy, że szyfrowanie i uwierzytelnianie mogą być używane razem lub osobno. Czasami na przykład potrzebujemy uwierzytelniania, lecz nie jest nam potrzebna poufność. Na przykład firma mogłaby dostarczać „podpisaną” poprawkę (łatę) oprogramowania, aby wykazać, że poprawka pochodzi od niej i nie została zmodyfikowana (po drodze).

Uwierzytelnianie jest elementem wielu aspektów bezpieczeństwa. Podpisy cyfrowe stanowią na przykład istotę niezaprzeczalności (nonrepudiation), tj. dostarczania dowodu, że dana jednostka wykonała pewną czynność. Typowym przykładem sytuacji, w której niezaprzeczalność jest nieodzowna, jest wypełnianie elektronicznych formularzy zamiast podpisywania umów papierowych. Niezaprzeczalność zapewnia, że osoba wypełniająca elektroniczny formularz nie zdoła zaprzeczyć, że tego nie zrobiła.

16.4.1.4. Rozpowszechnianie kluczy

Nie ulega wątpliwości, że większa część batalii toczonej między kryptografami (to ci, którzy wymyślają szyfry) a kryptoanalitykami (tymi, którzy próbują je łamać) dotyczy kluczy. W algorytmach symetrycznych obu stronom jest potrzebny klucz, którego nie powinien mieć nikt inny. Dostarczanie klucza symetrycznego jest poważnym problemem. Niekiedy odbywa się to poza pasmem (out-of-band). Gdyby na przykład Władek i Elwira chcieli komunikować się bezpiecznie, to mogliby wymienić klucz papierowym dokumentem lub podczas rozmowy, a potem komunikować się elektronicznie. Jednak takie metody nie skalują się dobrze. Rozważmy również zarządzanie kluczami. Przypuśmy, że Justyna chce się porozumiewać z N innymi użytkownikami z zachowaniem prywatności. Do komunikacji będzie potrzebowała N kluczy i dla większego bezpieczeństwa będzie musiała te klucze często zmieniać.

Są to główne przyczyny wysiłków zmierzających do tworzenia algorytmów z kluczem asymetrycznym. Takie klucze nie tylko można wymieniać jawnie, lecz danej użytkowniczce – powiedzmy Urszuli – będzie potrzebny tylko jeden klucz prywatny – niezależnie od tego, z iloma innymi osobami będzie chciała się komunikować. Problem uporania się z kluczem publicznym każdego odbiorcy komunikacji nadal pozostaje, ponieważ jednak klucze publiczne nie muszą być zabezpieczane, do utworzenia tego kółka kluczy (key ring) można będzie posłużyć się zwykłą pamięcią pomocniczą.

Niestety, nawet dystrybucja kluczy publicznych wymaga pewnego zachodu. Weźmy pod uwagę atak „człowiek pośrodku”, pokazany na rysunku 16.9. W takim przypadku osoba, która chce odebrać zaszyfrowany komunikat, wysyła swój klucz publiczny, lecz także ktoś atakujący wysyła swój „zły” klucz publiczny (pasujący do jego klucza prywatnego). Osoba chcąca wysłać zaszyfrowany komunikat nie wie niczego więcej, więc do zaszyfrowania komunikatu używa złego klucza. Atakujący z powodzeniem go odszyfrowuje.

Jest to problem z kategorii uwierzytelniania – chcielibyśmy mieć dowód tego, czyją własnością jest klucz publiczny. Jeden ze sposobów rozwiązania tego problemu polega na użyciu certyfikatów cyfrowych. Certyfikat cyfrowy (świadectwo cyfrowe; digital certificate) jest kluczem publicznym podpisanym przez organ zaufany. Organ zaufany otrzymuje dowód tożsamości od danej jednostki i zaświadcza, że dany klucz publiczny do niej należy. Na jakiej podstawie mamy jednak ufać poświadczającemu? Klucze publiczne owych organów certyfikujących (certificate authorities) są dołączane do przeglądarek sieciowych (i innych konsumentów certyfikatów) przed ich rozpowszechnianiem. Organy certyfikujące mogą wtedy poręczać za inne organy (podpisując cyfrowo klucze publiczne tych innych organów) itd., tworząc Sieć zaufania. Certyfikaty mogą być rozpowszechniane w standardowym formacie X.509 certyfikatu cyfrowego, z którego rozbiorem („parsowaniem”) komputer potrafi się uporać. Tego schematu używa się do bezpiecznej komunikacji w Sieci, co omawiamy w punkcie 16.4.3.

Rys. 16.9. Człowiek pośrodku – atak na kryptografię asymetryczną [21]

16.4.2. Implementacja kryptografii

Protokoły sieciowe są na ogół zorganizowane w warstwy (layers), przy czym każda warstwa działa jako klient warstwy położonej pod nią. Oznacza to, że gdy protokół generuje komunikat, który ma być wysłany do jego partnerskiego protokołu na drugiej maszynie, przekazuje go protokołowi usytuowanemu w stosie protokołów sieciowych poniżej niego do dostarczenia odpowiednikowi tamtego protokołu w drugiej maszynie. Na przykład w sieci IP protokół TCP (warstwy transportu) działa jak klient IP (protokołu warstwy sieciowej) – pakiety TCP są przekazywane w dół do protokołu IP w celu dostarczenia do partnera IP na drugim końcu połączenia. Protokół IP obudowuje pakiet TCP w pakiecie IP, który w podobny sposób jest przekazywany w dół, do warstwy łącza danych, aby można go było przetransmitować przez sieć do jej odpowiednika w komputerze docelowym. Ów partner IP dostarcza potem pakiet TCP w górę, do partnera TCP na maszynie odbiorczej. Model wzorcowy OSI, o którym wspominaliśmy wcześniej i który będzie opisany w punkcie 19.3.2, zawiera siedem takich warstw.

Kryptografię można wstawić prawie do każdej warstwy stosu protokołów sieciowych. Na przykład TLS (p. 16.4.3) zapewnia bezpieczeństwo w warstwie transportu. Bezpieczeństwo warstwy sieciowej zostało, ogólnie biorąc, ustandaryzowane w protokole IPSec określającym formaty pakietów IP, które umożliwiają wstawianie uwierzytelniaczy i szyfrowanie zawartości pakietów. IPSec używa szyfrowania symetrycznego, a do wymiany kluczy stosuje protokół internetowej wymiany kluczy (Internet Key Exchange – IKE). Protokół IKE jest oparty na szyfrowaniu kluczem publicznym. IPSec znalazł szerokie zastosowanie jako podstawa wirtualnych sieci prywatnych (virtual private networks – VPN), w których cały ruch między dwoma punktami końcowymi IPSec jest szyfrowany, co izoluje tę skądinąd publiczną sieć jako prywatną. Opracowano również liczne protokoły do używania w aplikacjach, na przykład PGP do szyfrowania poczty elektronicznej; w rozwiązaniach tego typu osiąganie bezpieczeństwa musi być zakodowane w samych aplikacjach.

Które miejsce w stosie protokołów jest najlepsze do umieszczenia ochrony kryptograficznej? W zasadzie nie ma tu definitywnej odpowiedzi. Z jednej strony w większości protokołów z korzyścią umieszczono ochronę w niższych warstwach stosu. Skoro na przykład pakiety IP obudowują pakiety TCP, to szyfrowanie pakietów IP (np. za pomocą IPsec) ukrywa także treść obudowanego pakietu TCP. Podobnie, uwierzytelniacze pakietów IP wykrywają zmianę informacji nagłówkowych TCP.

Z drugiej strony ochrona w niższych warstwach stosu protokołów może być niewystarczająca dla protokołów wyższych warstw. Na przykład serwer aplikacji, który akceptuje połączenia szyfrowane za pomocą IPSec, byłby zdolny uwierzytelniać komputery klientów, z których pochodzą zamówienia. Jednak w celu uwierzytelnienia użytkownika w komputerze klienta może wystąpić potrzeba użycia w serwerze protokołu poziomu aplikacji – użytkownik może zostać poproszony o wpisanie hasła. Albo weźmy pod uwagę problem poczty elektronicznej – przesyłka dostarczana protokołem standardu przemysłowego SMTP zanim dotrze na miejsce, jest zapamiętywana i przekazywana dalej – często wiele razy. Każda z tych transmisji może przebiegać siecią zabezpieczoną lub niezabezpieczoną. Aby listel (e-mail) był bezpieczny, trzeba go szyfrować, uniezależniając jego bezpieczeństwo od sposobu jego transportowania.

Niestety, jak wiele narzędzi, szyfrowanie może być użyte nie tylko w „godnych”, lecz również w „niecnych” zamiarach. Na szyfrowaniu opierają się na przykład opisane wcześniej ataki zbójeckie. Jak powiedziano, atakujący szyfrują informacje w upatrzonym systemie, co sprawia, że stają się one niedostępne dla właściciela. Chodzi o to, by zmusić właściciela do zapłacenia okupu za otrzymanie klucza potrzebnego do odszyfrowania danych. Zapobieganie takim atakom polega na polepszeniu bezpieczeństwa systemu i sieci oraz na sumiennie wykonywanym planie składowań, aby zawartość plików można było odtworzyć bez klucza.

16.4.3. Przykład – TLS

Mianem bezpieczeństwa warstwy transportu (Transport Layer Security – TLS) określa się protokół kryptograficzny, który umożliwia dwóm komputerom bezpieczną komunikację, to znaczy taką, dzięki której każdy z nich może ograniczyć krąg nadawców i odbiorców komunikatów. Niewykluczone, że jest to obecnie najczęściej używany protokół kryptograficzny w Internecie, gdyż jest protokołem standardowym, za pomocą którego przeglądarki sieciowe bezpiecznie komunikują się z serwerami Sieci. Dla pełnego obrazu powinniśmy dodać, że TLS wyewoluował z protokołu SSL (Secure Socket Layer, z ang. warstwa bezpiecznych gniazd) zaprojektowanego przez Netscape. Jest on opisany szczegółowo w dokumencie https://tools.ietf.org/html/rfc5246.

TLS jest złożonym protokołem, mającym wiele opcji. Tu prezentujemy tylko jedną jego odmianę i to w postaci bardzo uproszczonej i ogólnej, skupiając się na zastosowaniu w nim elementarnych operacji kryptograficznych. Naszym celem jest ukazanie skomplikowanego układu, w którym stosuje się kryptografię asymetryczną do ustanowienia przez klienta i serwer bezpiecznego klucza sesji (session key) symetrycznego szyfrowania sesji między nimi, a wszystko po to, żeby uniknąć ataków „człowiek pośrodku” i z odbicia. Dodatkowo siła tej kryptografii jest wzmacniana przez zapominanie kluczy sesji z chwilą jej zakończenia. Następny seans łączności między oboma stronami będzie wymagał wygenerowania nowych kluczy sesji.

Protokół TLS jest rozpoczynany przez klienta c w celu bezpiecznej komunikacji z serwerem. Zakłada się, że przed użyciem protokołu serwer s otrzymał certyfikat – oznaczamy go certs – od organu certyfikującego CA. Ten certyfikat (świadectwo) jest strukturą zawierającą:

• rozmaite atrybuty (attrs) serwera, takie jak jego niepowtarzalna nazwa wyróżniona (distinguished) i jego nazwa potoczna (DNS);

• identyfikację asymetrycznego algorytmu szyfrowania E() serwera;

• klucz publiczny ke tego serwera;

• okres ważności (interval), w czasie którego certyfikat powinien być uznawany za ważny;

• podpis cyfrowy a odniesiony do powyższych informacji, wytworzony przez CA, tzn. a = SkCA(〈attrs, Eke, interval〉).

Dodatkowo zakłada się, że przed użyciem protokołu klient otrzymał jawny algorytm VkCA weryfikacji CA. W przypadku Sieci dostawca przeglądarki przesyła ją użytkownikowi zaopatrzoną w algorytmy weryfikacji i klucze publiczne pewnych organów certyfikujących. Użytkownik może je usunąć lub dodać inne.

Łącząc się z serwerem s, klient c wysyła mu 28-bajtową, losową wartość nc , na co s odpowiada własną wartością losową ns wraz z certyfikatem certs. Klient sprawdza, czy VkCA(〈attrs, Eke , interval〉, a) = true oraz czy bieżący czas mieści się w okresie ważności interval. Po przejściu obu tych testów tożsamość serwera jest udowodniona. Klient wytwarza wtedy 46-bajtową tajemnicę poprzedzającą tajemnicę główną (premaster secret, pms) i wysyła serwerowi cpms = Eke(pms). Serwer wydobywa pms = Dkd(cpms). Teraz już zarówno klient, jak i serwer mają nc , ns i pms, więc każdy może obliczyć wspólną 48-bitową tajemnicę główną (master secret) ms = H(nc , ns , pms). Tylko klient i serwer potrafią obliczyć ms, ponieważ tylko im jest znana tajemnica pms. Ponadto zależność ms od nc i ns zapewnia, że ms jest świeżą wartością, tzn. kluczem sesji, który nie był używany w poprzedniej komunikacji. Osiągnąwszy ten punkt, klient i serwer obliczają na podstawie ms następujące klucze:

• klucz szyfrowania symetrycznego do szyfrowania komunikatów przekazywanych od klienta do serwera,

• klucz szyfrowania symetrycznego do szyfrowania komunikatów przekazywanych od serwera do klienta,

• klucz generacji MAC do generowania uwierzytelniaczy komunikatów od klienta do serwera,

• klucz generacji MAC do generowania uwierzytelniaczy komunikatów od serwera do klienta.

Aby przekazać serwerowi komunikat m, klient wysyła

Po odebraniu c serwer wydobywa

i akceptuje m, jeśli Podobnie, aby wysłać komunikat m klientowi, serwer nadaje

a klient dokonuje odtworzenia

i akceptuje m, jeśli

Ten protokół umożliwia serwerowi zawężenie kręgu odbiorców jego komunikatów do klienta, który wygenerował pms i ograniczenie nadawców akceptowanych przez niego komunikatów do tego samego klienta. Analogicznie, klient może ograniczyć krąg odbiorców wysyłanych przez siebie komunikatów i nadawców komunikatów, które akceptuje, do tego, kto zna kd (tzn. jest stroną, która potrafi zdeszyfrować cpms). W wielu aplikacjach, na przykład w transakcjach internetowych, klient musi sprawdzić, kim jest ten, który zna kd. Jest to jedno z zastosowań certyfikatu certs. W szczególności pole attrs zawiera dane, które klient może wykorzystać do ustalenia tożsamości serwera, z którym nawiązuje łączność – na przykład poznać nazwę jego domeny. Na okoliczność zastosowań, w których serwer również potrzebuje informacji o kliencie, protokół TLS zawiera opcję, z użyciem której klient może wysłać serwerowi certyfikat.

Oprócz zastosowania w Internecie TLS jest używany w najróżniejszych zadaniach. Napomknęliśmy na przykład, że protokół IPSec jest powszechnie używany jako podstawa do organizacji wirtualnych sieci prywatnych, czyli VPN-ów. Sieci IPSec VPN mają obecnie konkurenta w postaci sieci TLS VPN. IPSec nadaje się do szyfrowania ruchu od punktu do punktu, powiedzmy – między dwoma biurami firmy. Sieci TLS VPN są elastyczniejsze, lecz mniej wydajne, można więc ich używać do łączności między indywidualnym pracownikiem działającym w terenie a biurem przedsiębiorstwa.

16.5. Uwierzytelnianie użytkownika

W naszym dotychczasowym omówieniu uwierzytelniania występowały komunikaty i sesje. A co z użytkownikami? Jeśli system nie potrafi sprawdzić tożsamości użytkownika, to uwierzytelnianie komunikatu pochodzącego od tego użytkownika jest bezcelowe. Dlatego głównym problemem bezpieczeństwa w systemach operacyjnych jest uwierzytelnianie użytkownika (user authentication). System ochrony zależy od zdolności identyfikowania programów i aktualnie wykonywanych procesów, co z kolei zależy od zdolności identyfikacji każdego użytkownika systemu. Użytkownicy zwykle sami się przedstawiają, jak jednak ustalić, czy tożsamość użytkownika jest autentyczna? Ogólnie biorąc, uwierzytelnianie użytkownika jest oparte na jednej lub kilku z trzech rzeczy: tym, co użytkownik posiada (klucz lub karta), co wie (identyfikator użytkownika i hasło) lub na jakimś znaku szczególnym użytkownika (odcisk palca, wzorzec siatkówki lub podpis).

16.5.1. Hasła

Najpopularniejszą metodą sprawdzania tożsamości użytkowników jest operowanie hasłami. Gdy użytkownik się przedstawia, podając swój identyfikator lub nazwę konta, wówczas jest proszony o hasło (password). Jeśli podane przez użytkownika hasło jest zgodne z hasłem zapamiętanym w systemie, to system przyjmuje, że konto jest odwiedzane przez pełnoprawnego właściciela.

Hasła są często używane do ochrony obiektów w systemie komputerowym w razie braku bardziej złożonych schematów ochrony. Można je uważać za specjalny przypadek kluczy lub uprawnień. Hasło można na przykład skojarzyć z każdym zasobem (takim jak plik). Każdorazowe zamówienie użycia tego zasobu musi być opatrzone hasłem. Jeśli hasło jest poprawne, to zezwala się na dostęp. Z różnymi prawami dostępu mogą być skojarzone różne hasła. Na przykład do czytania plików, dopisywania do plików i uaktualniania plików mogą być używane różne hasła.

W praktycy większość systemów wymaga tylko jednego hasła od użytkownika, aby uzyskał on pełne prawa. Choć więcej haseł dawałoby teoretycznie większe bezpieczeństwo, systemów takich raczej się nie buduje ze względu na klasyczny kompromis między bezpieczeństwem a wygodą. Jeśli bezpieczeństwo powoduje pewną niewygodę, to jest często pomijane lub obchodzone w inny sposób.

16.5.2. Słabości haseł

Hasła są niezwykle popularne, gdyż łatwo je zrozumieć i używać. Niestety, hasła niejednokrotnie mogą być odgadnięte, ujawnione przypadkowo, wywęszone (przeczytane przez podsłuchiwacza) lub nielegalnie przekazane stronie nieuprawnionej przez użytkownika uprawnionego, o czym powiemy dalej.

Istnieją trzy pospolite metody odgadywania hasła. Jedna polega na tym, że intruz (człowiek lub program) zna użytkownika lub posiada o nim pewne informacje. Nazbyt często ludzie używają oczywistych informacji (takich jak imiona ich kotów lub współmałżonków) w charakterze haseł. Drugi sposób polega na użyciu metody siłowej (brute force), czyli wypróbowaniu po kolei wszystkich znaków dopuszczalnych w haśle (kombinacji liter, cyfr i znaków przestankowych w niektórych systemach) – aż do znalezienia hasła. Krótkie hasła są szczególnie podatne na tę metodę. Na przykład dla hasła złożonego z czterech cyfr dziesiętnych istnieje tylko 10 000 kombinacji. Średnio trzeba zatem wykonać tylko 5000 prób, aby je odgadnąć. Jeśliby napisać program sprawdzający kolejne hasło w ciągu 1 ms, to odgadnięcie czterocyfrowego hasła zajęłoby ok. 5 s. Wyliczanie jest mniej skuteczne przy znajdowaniu haseł w systemach, które umożliwiają używanie dłuższych haseł, zawierających litery małe i duże oraz cyfry i wszystkie znaki przestankowe. Rzecz oczywista – użytkownicy powinni korzystać z zalet wielkiej przestrzeni haseł i nie powinni stosować na przykład tylko małych liter. Trzecią popularną metodą jest atak słownikowy (dictionary attack), w którym wypróbowuje się wszystkie słowa i ich formy oraz popularne hasła.

Do ujawnienia hasła może dojść nie tylko w drodze odgadnięcia, lecz również wskutek podglądania – zwykłego lub elektronicznego. Intruz może patrzeć użytkownikowi przez ramię (podglądanie zza pleców; shoulder surfing) i w chwili gdy użytkownik się loguje, przechwycić hasło bez trudu, obserwując klawiaturę. Z kolei każdy, kto ma dostęp do sieci, w której znajduje się dany komputer, może niepostrzeżenie zamontować w niej monitor sieci i śledzić wszystkie dane przekazywane przez sieć (węszenie; sniffing), w tym identyfikatory użytkowników i hasła. Szyfrowanie strumienia danych zawierającego hasło rozwiązuje ten problem. Niemniej nawet w takim systemie może dojść do kradzieży haseł. Jeśli na przykład do przechowywania haseł służy plik, to mógłby on zostać skopiowany do analizy poza systemem. Albo weźmy zainstalowany w systemie program z koniem trojańskim, przechwytujący kod każdego naciśniętego klawisza przed wysłaniem go do aplikacji. Inną popularną metodą przechwytywania haseł, zwłaszcza hasłokodów (kodów dostępu; passcodes) kart debetowych jest instalowanie fizycznych urządzeń w miejscach, w których te kody są używane, i zapisywanie tego, co użytkownik robi; może to być np. „zbieracz” („skimmer”) zamontowany w bankomacie lub urządzenie umieszczone między klawiaturą a komputerem.

Ryzyko ujawnienia jest szczególnie poważne, jeśli hasło jest zanotowane, ponieważ może być ono przeczytane lub zgubione. Niektóre systemy zmuszają użytkowników do wybierania haseł trudnych do zapamiętania lub długich albo do częstego zmieniania haseł, co może doprowadzić do tego, że użytkownik zapisze hasło lub użyje go ponownie. W rezultacie bezpieczeństwo w takim systemie będzie zdecydowanie mniejsze niż w systemach, które pozwalają użytkownikom wybierać łatwe hasła!

Ostatnia metoda zagrażająca hasłom – nielegalne przekazanie – wynika z właściwości natury ludzkiej. W większości instalacji komputerowych obowiązuje zasada zakazująca użytkownikom posiadania wspólnych kont. Zasadę tę wprowadza się czasem w celach księgowych, lecz często w celu poprawy bezpieczeństwa. Gdyby na przykład identyfikator pewnego użytkownika był dzielony przez kilku użytkowników i doszło do naruszenia bezpieczeństwa konta o tym identyfikatorze, to nie można by było poznać, kto posługiwał się identyfikatorem tego użytkownika podczas włamania ani nawet sprawdzić, czy była to osoba uprawniona. Powiązanie z jednym identyfikatorem tylko jednego użytkownika umożliwia bezpośredni wgląd w sposób korzystania z jego konta; ponadto użytkownik jest w stanie zauważyć, że coś dziwnego dzieje się z jego kontem i wykryć włamanie. Niekiedy użytkownicy naruszają zasadę rozdzielności kont, aby pomóc znajomym lub ominąć księgowanie. Taka praktyka może spowodować przenikanie do systemu osób nieupoważnionych, być może szkodliwych.

Hasła mogą być generowane przez system lub wybierane przez użytkownika. Hasła wygenerowane przez system mogą być trudniejsze do zapamiętania, dlatego użytkownicy mogą mieć ochotę je zapisywać. Z kolei, o czym już wspomniano, hasła wybierane przez użytkowników są często łatwe do odgadnięcia (np. imię użytkownika lub ulubiona marka samochodu). Niektóre systemy będą sprawdzać proponowane hasło pod kątem łatwości odgadnięcia lub złamania, zanim je zaakceptują. Pewne systemy stosują również postarzanie (aging) haseł, zmuszając użytkowników do ich zmieniania w regularnych odstępach (np. co trzy miesiące). Metoda ta nie jest jednak w pełni skuteczna, gdyż użytkownik może łatwo posługiwać się dwoma hasłami naprzemiennie. Jako rozwiązanie tej trudności w niektórych systemach stosuje się zapisywanie używanych haseł każdego użytkownika. System może na przykład pamiętać N ostatnich haseł i nie pozwolić na ich ponowne użycie.

Można stosować kilka odmian tych prostych schematów ustalania haseł. Można na przykład częściej zmieniać hasło. W skrajnym przypadku hasło zmienia się podczas każdej sesji. Na końcu każdej sesji jest wybierane (przez system lub użytkownika) nowe hasło, którego należy użyć do następnej sesji. W takim przypadku, jeśli nawet dochodzi do użycia hasła przez nieuprawnioną osobę, może to się zdarzyć tylko raz. Gdy uprawniony użytkownik spróbuje posłużyć się już nieważnym hasłem na następnej sesji, odkryje naruszenie bezpieczeństwa. Można więc będzie przystąpić do naprawiania złamanego zabezpieczenia.

16.5.3. Zabezpieczanie haseł

Wadą wszystkich tych metod są kłopoty z sekretnym utrzymywaniem haseł w komputerze. W jaki sposób system może przechowywać hasło bezpiecznie, pozwalając jednocześnie na korzystanie z niego przy uwierzytelnianiu użytkownika podającego swoje hasło? W systemie UNIX stosuje się bezpieczne haszowanie w celu uniknięcia konieczności utrzymywania spisu haseł w tajemnicy. Ponieważ hasło jest haszowane, a nie szyfrowane, system nie może zdeszyfrować przechowanej wartości i odkryć oryginalnego hasła.

HASŁA SILNE I ŁATWE DO ZAPAMIĘTANIA

Stosowanie mocnych haseł (trudnych do odgadnięcia i podpatrzenia zza pleców) jest niezwykle ważne w systemach krytycznych, np. przy obsłudze kont bankowych. Istotne jest również, aby nie używać tego samego hasła w wielu systemach, gdyż zhakowanie hasła w którymś z mniej ważnych systemów byłoby równoznaczne z odkryciem hasła, którego używasz w ważniejszych systemach. Dobry sposób polega na utworzeniu hasła z pierwszych liter wyrazów łatwej do zapamiętania frazy, z użyciem zarówno liter małych, jak i dużych i dokładając jeszcze jakąś liczbę lub znak przestankowy. Na przykład z frazy „Mojej sympatii jest na imię Katarzyna” można otrzymać hasło „MsjniK!”. Hasło takie jest trudne do złamania, ale konkretny użytkownik może je łatwo zapamiętać. Bezpieczniejszy system mógłby pozwalać na więcej znaków w hasłach. W gruncie rzeczy system mógłby nawet dopuszczać występowanie w hasłach spacji, aby użytkownik mógł utworzyć hasłofrazę (frazę dostępu; passphrase) łatwą do zapamiętania, lecz trudną do złamania.

Funkcje haszowania łatwo jest obliczać, lecz ich odwracanie jest niezwykle trudne (jeśli wręcz nie niemożliwe)[22]. Mając daną wartość x, łatwo jest obliczyć wartość funkcji f(x), natomiast dla danej wartości f(x) obliczenie x jest niemożliwe. Funkcja ta służy do kodowania wszystkich haseł. Przechowuje się tylko hasła zakodowane. Gdy użytkownik podaje hasło, wówczas zostaje ono zakodowane i porównane z zapamiętanym, zakodowanym hasłem. Nawet jeśli przechowywane hasło zakodowane zostanie podpatrzone, nie da się go zdekodować, a więc nie można go ustalić. Plik z hasłami nie musi być zatem przechowywany w tajemnicy.

Słabością tej metody jest brak kontroli systemu nad hasłami. Chociaż hasła są haszowane, każdy, kto ma kopię pliku z hasłami, może zastosować do niego szybkie procedury haszowania, haszując na przykład każde słowo ze słownika i porównując wyniki z hasłami. Jeśli użytkownik wybrał hasło będące słowem ze słownika, to dojdzie do złamania hasła. Na wystarczająco szybkich komputerach, lub nawet z pomocą grup wolniejszych komputerów, takie porównanie może trwać zaledwie kilka godzin. Co więcej, ponieważ w systemach stosuje się dobrze znane algorytmy haszowania, atakujący mógłby przechowywać podręcznie hasła, które zostały uprzednio złamane.

Z tego powodu systemy zawierają w algorytmie haszowania „sól”, czyli zapisaną liczbę losową. Wartość soli jest dodawana do hasła, aby zapewnić, że jeśli dwa hasła w postaci jawnej są takie same, w wyniku haszowania dadzą różne wartości. Ponadto wartość soli sprawia, że haszowanie słownika staje się nieefektywne, gdyż w celu porównywania z zapamiętanymi hasłami każdy termin ze słownika trzeba by połączyć z każdą wartością soli. W nowszych wersjach UNIX-a wpisy zaszyfrowanych haseł są przechowywane w pliku udostępnianym do czytania tylko superużytkownikowi. Programy, które porównują przedłożone hasło z hasłem zapamiętanym, wykonują zmianę identyfikatora użytkownika (funkcja setuid) na root, dzięki czemu mogą czytać plik haseł, niedostępny dla innych użytkowników.

16.5.4. Hasła jednorazowe

Aby uniknąć kłopotów z wywąchiwaniem haseł i zaglądaniem przez ramię, można zastosować w systemie zbiór haseł dobranych parami (par haseł; paired passwords). Na początku sesji system wybiera losowo i przedstawia jedną część pary haseł; użytkownik musi podać jej drugą część. W takim systemie użytkownik otrzymuje wezwanie (challenge) i musi na nie dać poprawną odpowiedź (odzew; respond).

Podejście to można uogólnić, używając algorytmu jako hasła. W takim schemacie system i użytkownik dzielą hasło symetryczne. Hasło pw nigdy nie jest transmitowane nośnikiem, który może być ujawniony. Zamiast tego używa się go jako danych wejściowych funkcji H wraz z wezwaniem ch prezentowanym przez system. Użytkownik oblicza funkcję H(pw, ch). Wynik tej funkcji jest przesyłany do komputera jako uwierzytelniacz. Ponieważ komputer także zna pw i ch, potrafi wykonać to samo obliczenie. Jeśli oba wyniki są takie same, to użytkownik zostaje uwierzytelniony. Kiedy następnym razem użytkownik będzie wymagał uwierzytelnienia, generowane jest inne wezwanie ch, po czym następują te same kroki. Uwierzytelniacz będzie więc tym razem inny. Tego rodzaju hasłom algorytmicznym nie grozi ponowne użycie – nikt, kto by przechwycił pisane przez użytkownika hasło, nie zdoła posłużyć się nim ponownie. Ten system z hasłem jednorazowym (one-time password) jest jednym z nielicznych sposobów zapobiegania niewłaściwemu uwierzytelnieniu spowodowanemu ujawnianiem hasła.

Systemy haseł jednorazowych są implementowane różnymi sposobami. W implementacjach komercyjnych stosuje się kalkulatory sprzętowe z wyświetlaczem lub z wyświetlaczem i klawiaturą numeryczną. Kalkulatory takie na ogół mają kształt karty kredytowej, kółka z kluczem sprzętowym (key-chain dongle) lub urządzenia USB. Oprogramowanie działające w komputerach lub smartfonach zaopatruje użytkownika w funkcję H(pw, ch); hasło pw może być wprowadzone przez użytkownika lub wygenerowane przez kalkulator w synchronizacji z komputerem. Niekiedy pw jest po prostu osobistym numerem identyfikacyjnym (personal identification number – PIN). Na wyjściu dowolnego z takich systemów pojawia się jednorazowe hasło. Generator jednorazowego hasła, żądający od użytkownika podania danych, działa na zasadzie uwierzytelniania dwuskładnikowego (two-factor authentication). W tej metodzie są wymagane dwa komponenty, na przykład generator haseł jednorazowych, który wytworzy poprawną odpowiedź tylko wtedy, kiedy podano poprawny PIN. Uwierzytelnianie dwuskładnikowe daje znacznie lepszą ochronę sprawdzania tożsamości niż uwierzytelnianie jednoskładnikowe, ponieważ wymaga zarówno „czegoś, co masz”, jak i „czegoś, co wiesz”.

16.5.5. Biometria

W jeszcze innej odmianie stosowania haseł do uwierzytelniania robi się użytek ze środków biometrycznych. Do zabezpieczania dostępu fizycznego, na przykład do centrów danych, powszechnie używa się czytników dłoni lub ręki. Czytniki takie porównują zapamiętane parametry z tym, co jest czytane z ich podkładek analizujących rękę. Parametry mogą zawierać mapę temperaturową, a także długość palców, szerokość palców i rysunek linii dłoni. Urządzenia takie są obecnie zbyt duże i kosztowne, aby używać ich do zwykłego uwierzytelniania komputerowego.

Wygodne, także od strony ekonomicznej, okazały się czytniki odcisków palców. Urządzenia te czytają wzory linii papilarnych i zamieniają je na ciąg liczb. Z czasem mogą przechowywać zbiór ciągów w celu dostrajania się do położenia palca na podkładce czytającej i uwzględniania innych czynników. Oprogramowanie analizuje potem palec na podkładce i porównuje go z zapamiętanymi ciągami, żeby ustalić, czy palec na podkładce ma te same cechy co jego zapamiętany wzór. Rzecz jasna, można przechowywać profile wielu użytkowników, więc skaner potrafi je rozróżniać. Bardzo precyzyjny schemat uwierzytelniania dwuskładnikowego można otrzymać przez wymaganie hasła oraz nazwy użytkownika i analizowanie odcisku palca. Jeśli informacja ta na czas przekazywania zostanie zaszyfrowana, to system taki staje się bardzo odporny na podszywanie się i atak powtórkowy (przez ponawianie).

Uwierzytelnianie wieloskładnikowe (multifactor authentication) jest jeszcze lepsze. Zważmy, jak mocne uwierzytelnianie można uzyskać za pomocą urządzenia USB, które trzeba podłączyć do systemu, PIN-u i skanu odcisku palca. Pominąwszy konieczność położenia palca na podkładce i podłączenia USB do systemu, wygoda uwierzytelniania tą metodą jest nie mniejsza niż używanie zwykłych haseł. Przypomnijmy jednak, że samo silne uwierzytelnianie nie wystarczy do zagwarantowania ID użytkownika. Uwierzytelniona sesja jest wciąż narażona na uprowadzenie, jeśli nie jest szyfrowana.

16.6. Realizacja obrony bezpieczeństwa

Rozwiązań w dziedzinie bezpieczeństwa istnieje równie wiele co jego zagrożeń w odniesieniu do systemów i sieci. Spektrum rozwiązań rozciąga się od podnoszenia poziomu wyedukowania użytkowników, poprzez technologię, aż po pisanie lepszego oprogramowania. Większość specjalistów od bezpieczeństwa podpisuje się pod teorią głębokiej obrony (obrony w głąb; defense in depth), która głosi, że więcej warstw obronnych działa lepiej niż mniej warstw. Rzecz jasna ta teoria odnosi się do każdego rodzaju bezpieczeństwa. Pomyślmy o bezpieczeństwie domu bez zamka drzwiowego, mającego zamek oraz wyposażonego w zamek i alarm. W tym podrozdziale przyjrzymy się ważniejszym metodom, narzędziom i technikom, których można użyć do zwiększania odporności na zagrożenia. Zauważmy, że niektóre sposoby poprawiania bezpieczeństwa są w większym stopniu częścią zagadnień ochrony niż bezpieczeństwa i są omówione w rozdziale 17.

16.6.1. Polityka bezpieczeństwa

Pierwszy krok w kierunku poprawiania bezpieczeństwa informatycznego w dowolnym jego aspekcie polega na określeniu polityki bezpieczeństwa (zasad bezpieczeństwa; security policy). Jej zasady bywają różne, lecz na ogół precyzują, co ma być zabezpieczane. Na przykład polityka bezpieczeństwa może zakładać, że przed zainstalowaniem wszelkich aplikacji dostępnych z zewnątrz należy dokonać przeglądu ich kodu albo że użytkownikom nie wolno dzielić się hasłami, albo że we wszystkich punktach łączących firmę ze światem zewnętrznym musi być wykonywane skanowanie portów co sześć miesięcy. Bez przyjęcia jakiejś polityki ani użytkownicy, ani administratorzy nie są w stanie określić, co wolno, co jest wymagane, a co jest zabronione. Polityka jest ramowym, perspektywicznym planem („mapą drogową”) bezpieczeństwa i jeśli jakaś instalacja dąży do poprawienia bezpieczeństwa, musi dysponować planem, aby wiedzieć, jak osiągnąć cel.

Po określeniu polityki bezpieczeństwa osoby, których ona dotyczy, powinny ją dobrze poznać. Powinna ona służyć im za przewodnik. Zbiór zasad bezpieczeństwa musi być także dokumentem żywym (living document), okresowo przeglądanym i uaktualnianym, żeby zagwarantować, że jest on zawsze istotny i „na miejscu” oraz że jest przestrzegany.

16.6.2. Ocena słabości

W jaki sposób możemy określić, czy polityka bezpieczeństwa została poprawnie zrealizowana? Najlepszym sposobem jest ocena słabości. Ocena taka może być rozległa, poczynając od aspektów socjotechnicznych, poprzez ocenę ryzyka, aż po skanowanie portów. Na przykład ocena ryzyka (risk assessment) ma na celu określenie wartości aktywów badanej jednostki (programu, zespołu kierowniczego, systemu lub jakiegoś rozwiązania) i ustalenie prawdopodobieństwa, że incydent związany z bezpieczeństwem naruszy tę jednostkę i obniży jej wartość. Po poznaniu prawdopodobieństwa poniesienia strat i ich potencjalnego rozmiaru można dołożyć starań, aby zabezpieczyć daną jednostkę.

Zasadniczym elementem większości ocen słabości jest test penetracyjny (penetration test), w którym analizuje się znane słabości jednostki. Ponieważ ta książka jest poświęcona systemom operacyjnym i oprogramowaniu działającemu pod ich kontrolą, skoncentrujemy się na ocenie słabości aspektów z nimi związanych.

Poszukiwania słabości są zwykle prowadzone w czasie względnie małego wykorzystania komputera, aby minimalizować ich skutki. W uzasadnionych przypadkach robi się to w systemach testowych, a nie produkcyjnych, gdyż mogą one wywoływać niepożądane zachowanie badanych systemów lub urządzeń sieciowych.

Analiza danego systemu może obejmować różnorodne jego aspekty:

• krótkie lub łatwe do odgadnięcia hasła;

• nieuprawnione programy uprzywilejowane, na przykład manipulujące ustawianiem bitu użytkownika (setuid);

• nieupoważnione programy w katalogach systemowych;

• nieoczekiwanie długo wykonywane procesy;

• nieodpowiednią ochronę katalogów, zarówno należących do użytkowników, jak i systemowych;

• nieodpowiednią ochronę plików z danymi systemowymi, takimi jak plik haseł, pliki urządzeń lub samo jądro systemu operacyjnego;

• niebezpieczne wpisy na ścieżce wyszukiwania programów (np. koń trojański, omówiony w p. 16.2.1), takie jak bieżący katalog lub katalogi łatwo zapisywalne, na przykład /tmp;

• zmiany w programach systemowych wykryte za pomocą sum kontrolnych;

• nieoczekiwane lub poukrywane demony sieciowe.

Każde uchybienie wykryte wskutek analizy bezpieczeństwa może być skorygowane automatycznie lub zameldowane kierującym systemem.

Komputery pracujące w sieciach są znacznie bardziej zagrożone atakami na bezpieczeństwo niż systemy autonomiczne (wolno stojące). Zamiast ataków ze znanego zbioru punktów dostępu, w rodzaju bezpośrednio podłączonych terminali, mamy tu do czynienia z atakami pochodzącymi z nieznanego i wielkiego zbioru punktów dostępu – jest to potencjalnie bardzo poważny problem bezpieczeństwa. Bardziej narażone są również, choć w mniejszym stopniu, systemy przyłączone za pomocą modemów do linii telefonicznych.

Faktem jest, że agendy rządowe USA uważają systemy za bezpieczne tylko w takim stopniu, w jakim są bezpieczne ich najodleglejsze połączenia. Na przykład system ściśle tajny powinien być dostępny tylko z wnętrza budynku, który również jest uważany za ściśle tajny. System traci kwalifikację ściśle tajnego, jeśli dochodzi do jakiegokolwiek rodzaju komunikacji z otoczeniem zewnętrznym. W niektórych obiektach państwowych podejmuje się szczególne środki bezpieczeństwa. Gniazda służące do podłączania terminali w celu komunikacji z zabezpieczonym komputerem są na czas nieużywania terminalu zamykane w sejfie w pomieszczeniu biurowym. Osoba chcąca wejść do budynku i swojego biura musi mieć właściwy identyfikator, znać kombinację zamka fizycznego oraz informacje uwierzytelniające jej dostęp do samego komputera – jest to przykład uwierzytelniania wieloskładnikowego.

Niestety, administratorzy systemów i specjaliści od bezpieczeństwa komputerowego często nie mogą zamknąć maszyny w pomieszczeniu i zakazać do niej jakiegokolwiek zdalnego dostępu. Na przykład sieć Internet, łącząca obecnie miliardy komputerów i urządzeń, nabrała dla wielu przedsiębiorstw i osób fizycznych znaczenia kluczowego i stała się nieodzowna. Jeśli uważać Internet za klub, to jak w każdym klubie zrzeszającym miliony członków, pośród wielu przyzwoitych znajdą się również źli. Źli członkowie mają wiele narzędzi, za pomocą których mogą próbować dostępu do połączonych komputerów.

Poszukiwanie słabości można odnieść do sieci w celu rozwiązywania niektórych problemów bezpieczeństwa sieciowego. W sieci poszukuje się portów, które reagują na zamówienia. Jeśli są włączone usługi, których być nie powinno, dostęp do nich można zablokować lub można je wyłączyć. Na podstawie analizy ustala się potem szczegóły aplikacji prowadzącej nasłuch w danym porcie w celu określenia, czy ma ona jakieś znane czułe miejsca. Testowanie tych słabości może przyczynić się do wykrycia złej konfiguracji systemu lub braku niezbędnych poprawek (łat).

Rozważmy jednak na koniec, co się stanie, gdy skanery portów trafią do rąk atakującego, a nie kogoś, kto dąży do poprawy bezpieczeństwa. Narzędzie te mogłyby pomóc atakującym wynajdywać słabe punkty do ataku. (Na szczęście skanowanie portów można zidentyfikować w drodze wykrywania anomalii, co omówimy dalej). Ujmując ogólnie, w dziedzinie bezpieczeństwa trwa rywalizacja polegająca na tym, że tych samych narzędzi można używać w dobrych i złych celach. Niektórzy obstają w istocie przy bezpieczeństwie przez zaciemnianie (security through obscurity), uważając, że nie należy pisać żadnych narzędzi do testowania bezpieczeństwa, ponieważ takie narzędzia mogą być użyte do znajdowania (i wykorzystywania) luk w bezpieczeństwie. Inni są zdania, że takie podejście do bezpieczeństwa jest błędne, dowodząc na przykład, że atakujący mogą pisać własne narzędzia. Wydaje się rozsądne, że bezpieczeństwo przez zaciemnianie można uważać za jedną z warstw bezpieczeństwa tylko wtedy, kiedy nie stanowi ono jedynej warstwy. Na przykład przedsiębiorstwo mogłoby upublicznić całą konfigurację swojej sieci, lecz utrzymanie tych informacji w tajemnicy utrudnia intruzom zorientowanie się w tym, co atakować. Jednak nawet w takiej sytuacji firma, która zakłada, że takie informacje pozostają w sekrecie, wykazuje błędne poczucie bezpieczeństwa.

16.6.3. Zapobieganie włamaniom

Zabezpieczanie systemów i obiektów nieodłącznie wiąże się z wykrywaniem włamań i zapobieganiem im. Zapobieganie włamaniom (intrusion prevention), zgodnie z nazwą, polega na dokładaniu starań, aby usiłowane lub udane włamania do systemów komputerowych były wykrywane i aby odpowiednio na nie reagowano. Zapobieganie włamaniom obejmuje wiele sposobów idących w kilku kierunkach, spośród których można wymienić:

• Czas, w którym dochodzi do wykrycia. Wykrycie może być dokonane na bieżąco (w trakcie wystąpienia włamania) lub dopiero po fakcie.

• Rodzaje danych wejściowych sprawdzanych w celu wykrycia działalności naruszającej zasady. Można tu zaliczyć polecenia powłokowe użytkownika, wywołania systemowe procesu i nagłówki lub treści pakietów sieciowych. Pewne postaci włamań mogą być wykryte tylko przez powiązanie informacji z kilku takich źródeł.

• Zakres i środki reagowania. Do prostych form reagowania należą: zaalarmowanie administratora o potencjalnym włamaniu lub powstrzymanie w jakiś sposób działań zmierzających do włamania, na przykład zlikwidowanie procesu zaangażowanego w takie działania. Reagując w sposób wyrafinowany, system mógłby niepostrzeżenie skierować aktywność intruza w stronę pułapki zwanej miodownicą (honeypot) – fałszywego zasobu wystawionego na widok atakującego w celu przyglądnięcia się atakowi i zasięgnięcia o nim informacji; dla atakującego taki zasób wygląda jak prawdziwy.

Te stopnie swobody w obszarze projektowania wykrywania włamań w systemie doprowadziły do powstania wielkiej różnorodności rozwiązań znanych jako systemy zapobiegania włamaniom (intrusion-prevention systems – IPS). IPS-y działają na zasadzie samomodyfikujących się zapór sieciowych przekazujących ruch, jeżeli nie zostanie wykryte włamanie (w przeciwnym razie ruch zostaje zablokowany).

Co jednak stanowi o włamaniu? Zdefiniowanie odpowiedniej specyfikacji włamania okazuje się dość trudne, toteż dzisiejsze automatyczne IPS-y zazwyczaj zasadzają się na jednej lub dwóch mniej ambitnych metodach. W pierwszej, nazywanej wykrywaniem opartym na sygnaturach (signature-based detection), wejście systemu lub ruch w sieci bada się pod kątem występowania specyficznych wzorców zachowań (czyli sygnatur), o których wiadomo, że znamionują atak. Prostym przykładem wykrywania opartego na sygnaturach jest poszukiwanie napisu /etc/passwd w pakietach sieciowych kierowanych do systemu UNIX. Inny przykład stanowi oprogramowanie wykrywające wirusy, które analizuje pliki binarne lub pakiety sieciowe, poszukując znanych wirusów.

W drugiej metodzie, zazwyczaj nazywanej wykrywaniem anomalii (anomaly detection), za pomocą różnych technik podejmuje się próby wykrycia nienormalnego zachowania w obrębie systemów komputerowych. Rzecz jasna, nie wszystkie odbiegające od normy działania systemu wskazują na włamanie, lecz zakłada się, że włamania często wywołują w systemie nienormalne zachowanie. Przykładem wykrywania anomalii jest monitorowanie wywołań systemowych odnoszących się do procesu-demona, mające na celu zaobserwowanie, czy takie wywołania nie odbiegają od zwykłych schematów i czy na przykład nie wskazują na dążenie do przepełnienia bufora w obrębie demona w celu uszkodzenia jego działania. Innym przykładem jest nadzorowanie poleceń powłokowych w celu wykrywania poleceń nietypowych dla danego użytkownika lub nienormalnej pory otwierania przez niego sesji – jedno i drugie może wskazywać, że atakującemu udało się uzyskać dostęp do konta danego użytkownika.

Wykrywanie oparte na sygnaturach i wykrywanie anomalii można uważać za dwie strony tego samego medalu. W wykrywaniu opartym na sygnaturach dąży się do scharakteryzowania niebezpiecznych zachowań i tropienia występowania któregoś z nich, natomiast wykrywanie anomalii to próba scharakteryzowania normalnych (czyli nie tworzących zagrożenia) zachowań i odnotowywanie wystąpień zachowań innych niż one.

Te różne podejścia prowadzą jednak do systemów IPS o bardzo różnych własnościach. W szczególności wykrywanie anomalii może ujawniać uprzednio nieznane metody włamań (zwane atakami dnia zerowego; zero-day attacks). Wykrywanie oparte na sygnaturach będzie natomiast wykazywać tylko znane metody ataku, dające się skodyfikować w rozpoznawalny wzorzec. Tym samym nowe rodzaje ataku, które nie były jeszcze przeanalizowane, kiedy generowano sygnatury, pozostaną niewykryte na podstawie sygnatur. Jest to problem dobrze znany dostawcom oprogramowania wykrywającego wirusy; muszą oni publikować bardzo często nowe sygnatury, w ślad za ręcznym wykrywaniem nowych wirusów.

Wykrywanie anomalii niekoniecznie jednak góruje nad wykrywaniem opartym na sygnaturach. W istocie, poważnym wyzwaniem dla systemów podejmujących wykrywanie anomalii jest znalezienie właściwej miary „normalnego” zachowania systemu. Jeśli system w trakcie określania takiej miary jest już penetrowany, to działania intruza mogą zostać zaliczone do „normy”. Nawet podczas wymiarowania czystego systemu, w którym nie widać wpływu zachowań powodowanych działaniem intruza, miara wzorcowa musi sprawiedliwie i w pełni odzwierciedlać normalne zachowanie. W przeciwnym razie liczba wyników fałszywie dodatnich (false positives), czyli fałszywych alarmów, lub fałszywie ujemnych (false negatives), czyli przeoczonych włamań, będzie zbyt duża.

Aby zilustrować wpływ nawet nieznacznie zawyżonej częstości fałszywych alarmów, rozważmy instalację złożoną ze stu uniksowych stacji roboczych, z których zbiera się informacje o zdarzeniach istotnych dla bezpieczeństwa w celu wykrywania włamań. Mała instalacja, taka jak ta, mogłaby z łatwością generować milion zapisów kontrolnych dziennie. Ale tylko jeden, może dwa, mógłby być wart uwagi administratora. Jeśli założymy optymistycznie, że każdy faktyczny atak znajduje odbicie w dziesięciu zapisach kontrolnych, to możemy z grubsza obliczyć częstość występowania zapisów kontrolnych rzeczywiście odzwierciedlających aktywność napastniczą jako

Interpretując to jako „prawdopodobieństwo wystąpienia zapisów dotyczących włamania”, oznaczymy je jako P(I); a zatem I jest zdarzeniem wystąpienia zapisu odzwierciedlającego naprawdę zachowanie związane z włamaniem. Ponieważ P(I) = 0,00002, wiemy również, że P(¬I) = 1 – P(I) = 0,99998. Niech teraz A oznacza zdarzenie podniesienia alarmu przez system IDS[23]. Dokładny IDS powinien maksymalizować zarówno P(I|A), jak i P(¬I|¬A), czyli prawdopodobieństwa, że alarm znamionuje włamanie, a brak alarmu oznacza brak włamania. Skupiając się na chwilę na P(I|A), obliczamy to prawdopodobieństwo z twierdzenia Bayesa:

Rozważmy teraz wpływ częstości fałszywych alarmów P(A|¬I) na P(I|A). Nawet przy bardzo dobrym współczynniku prawdziwych alarmów P(A|I) = 0,8 wyglądający dobrze współczynnik fałszywych alarmów P(A|¬ I) = 0,0001 spowoduje, że P(I|A) ≈ 0,14. Tak więc mniej niż co siódmy alarm oznacza rzeczywiste włamanie! W systemach, w których administrator bezpieczeństwa bada każdy alarm, wysoki współczynnik fałszywych alarmów – zwany „efektem choinki” – jest zbyt marnotrawny i szybko nauczyłby administratora lekceważyć alarmy.

Ten przykład ilustruje ogólną zasadę systemów IPS – aby były użyteczne, muszą wykazywać skrajnie niski współczynnik fałszywych alarmów. Jak już wspomniano, osiągnięcie wystarczająco niskiego współczynnika fałszywych alarmów jest poważnym wyzwaniem w systemach wykrywania anomalii z powodu trudności w dokładnym określeniu, czym jest normalne zachowanie systemu. Niemniej trwają badania nad ulepszeniem technik wykrywania anomalii. Oprogramowanie wykrywania włamań rozwija się w kierunku implementowania sygnatur, algorytmów anomalii i innych algorytmów oraz łączenia tych wyników w celu osiągania dokładniejszych współczynników wykrywania anomalii.

16.7. Przykład – Windows 10

16.8. Podsumowanie

Dalsze lektury

Bibliografia

O Autorach

ABRAHAM SILBERSCHATZ

Od 2005 roku profesor informatyki na Uniwersytecie Yale. Przed dołączeniem do Yale był wiceprezesem Centrum Badań Informacji Naukowej w Bell Laboratories. Wcześniej przewodniczył profesurze na Wydziale Nauk Komputerowych Uniwersytetu Teksańskiego w Austin.

PETER BAER GALVIN

Principal Solutions Architect i GUI Product Manager w Starfish Storage i Chief Technologist w Cambridge Computer Services. Wcześniej m.in. CTO w firmie Corporate Technologies, Chief solutions architect w Pluribus Networks, DELL. Do 2012 roku był wykładowcą na Uniwersytecie w Bostonie, gdzie prowadził zajęcia z wprowadzenia do systemów operacyjnych.

GREG GAGNE

Kierownik Wydziału Informatyki w Westminster College w Salt Lake City, gdzie uczy od 1990 roku.

Przypisy

[1] Gwoli formalności przypominamy, że żargonowe określenie „spam” oznacza niechcianą, niezamawianą korespondencję elektroniczną, zaśmiecającą komputerowe skrzynki pocztowe na podobieństwo papierowych reklam upychanych do konwencjonalnych skrzynek na listy – przyp. tłum.

[2] Poszerzone objaśnienia tego i wielu innych specjalistycznych terminów z dziedziny bezpieczeństwa komputerowego można znaleźć w pierwszym tomie książki W. Stallingsa i L. Browna pt. Bezpieczeństwo systemów informatycznych. Zasady i praktyka. Helion, Gliwice 2019 – przyp. tłum.

[3] Ze względu na międzynarodowe upowszechnienie terminów z dziedziny bezpieczeństwa komputerowego oraz fakt, że często są to żargonowe neologizmy w samym języku angielskim, niektórym terminom nie przyporządkowujemy polskich określeń, ograniczając się do ich wyjaśniania – przyp. tłum.

[4] Domeny dostępu są omówione w rozdz. 17 – przyp. tłum.

[5] Dokładniej – kodu źródłowego kompilowanego programu – przyp. tłum.

[6] Zobacz też J. Loeliger, M. McCullough: Kontrola wersji z systemem Git. Narzędzia i techniki programistów. Helion, Gliwice 2014 – przy. tłum.

[7] Chodzi oczywiście o długość mierzoną liczbą bitów dwójkowej reprezentacji liczby w komputerze – przyp. tłum.

[8] Z angielskiego „Rozwalanie stosu dla zabawy i kasy” – przyp. tłum.

[9] Napisu w sensie języka C – przyp. tłum.

[10] Nikczemnym oprogramowaniem i zestawami do jego produkcji handluje się również w Sieci – przyp. tłum.

[11] Gdzie indziej nazywany wektorem zakażenia (infection vector) – przyp. tłum.

[12] W swobodnym przekładzie: „jazda na całego”, zob. jednak https://www.wikiwand.com/pl/Wardriving – przyp. tłum.

[13] I jest nim nie tylko z nazwy – przyp. tłum.

[14] Lorelyn Medina/Shutterstock.

[15] Lorelyn Medina/Shutterstock.

[16] Krajowy Instytut Standardów i Technologii amerykańskiego Ministerstwa Handlu – przyp. tłum.

[17] Klucz DES złamano po raz pierwszy już pod koniec lat 90. XX w. połączonymi siłami komputerów sieciowych – przyp. tłum.

[18] Nazwa ta jest kalamburem utworzonym od nazwisk dwóch wynalazców szyfru: Joana Daemena i Vincenta Rijmena – przyp. tłum.

[19] Jeśli pominąć drugi, prywatny klucz do pary w tym schemacie – przyp. tłum.

[20] Lorelyn Medina/Shutterstock.

[21] Lorelyn Medina/Shutterstock.

[22] Funkcje takie nazywa się również jednokierunkowymi (one-way functions) – przyp. tłum.

[23] IDS (intrusion detection system), system wykrywania włamań; od IPS-u różni się tym, że nie podejmuje prób zapobiegania włamaniom, ograniczając się do ich wykrywania – przyp. tłum.