R-12-01.doc

(341 KB) Pobierz
Szablon dla tlumaczy

12. Bezpieczne programowanie

Czym jest bezpieczne programowanie?

W prostym rozumieniu, bezpieczeństwo to zdolność do sprawowania nadzoru nad wykorzystywaniem przez innych naszych zasobów komputerowych, czyli zdolność do powiedzenia ludziom nie (lub tak) i umiejętność wsparcia tego odpowiednim działaniem.

W świecie komputerowym bezpieczeństwo obejmuje wiele pojęć. Na jednej płaszczyźnie, bezpieczeństwo utożsamia się z niezawodnością — bezpieczny system to taki, który pozostaje dostępny pomimo starań innych, aby uczynić go niedostępnym. Na innej płaszczyźnie, bezpieczeństwo uwzględnia pewne formy nadzoru dostępu — tylko niektórzy ludzie powinni mieć dostęp do systemu i to w ściśle określony sposób, a wszystko to powinno być dokładnie ustalone przez administratora systemu. Inne zadanie bezpieczeństwa jest związane z zapobieganiem wyciekom informacji — w sytuacji, kiedy prawowity użytkownik uzyskuje legalny dostęp do informacji, nikt inny nie może uzyskać dostępu do tej samej informacji.

Bezpieczne programowanie wymaga świadomości wszystkich tych pułapek. Programista musi określić, które zagrożenia bezpieczeństwa są ważne, a następnie chronić przed nimi. Bezpieczny program powinien reagować w określony sposób na ataki, rozpoznane dzięki starannemu prowadzeniu rejestrów zdarzeń, ostrzegając, czy nawet podejmując środki zaradcze, zapobiegające powtarzaniu się ataków.

Bezpieczne programowanie polega w takim samym stopniu na wiedzy o tym, czego nie robić, jak i na wiedzy o tym, co zrobić. Zatem, ten rozdział zawiera informacje o pomyłkach i pułapkach, których należy unikać, jak też przyjęte nowe metody i wskazówki. Nie jest to wyczerpujące ujęcie — taka książka nie mogłaby być napisana, ponieważ agresorzy nieustannie wymyślają nowe sposoby osiągania swoich celów. Rozdział ten jest raczej pomyślany jako wprowadzenie do praktyki bezpiecznego programowania oraz jako przewodnik po najbardziej powszechnych i użytecznych wskazówkach dotyczących bezpieczeństwa oraz pułapkach tak, aby od zaraz zacząć pisanie bezpieczniejszego kodu.

Dlaczego jest trudno bezpiecznie programować?

Jeśli istnieje uniwersalna prawda o bezpiecznym programowaniu to brzmi ona: Bezpieczne programowanie jest trudne.

Najróżniejsze wykazy i zasoby online poświęcone katalogowaniu i ujawnianiu słabości zabezpieczeń obfitują w dowody na powyższe stwierdzenie. Gdyby było to odrobinę łatwiejsze, większość programistów wolałaby uniknąć publicznej kompromitacji, która towarzyszy nadużyciu (exploit).

Jednak, pomimo tej oraz innych motywacji, zagadnienie bezpiecznego programowania wydaje się być zasypane niepowodzeniami niezliczonej liczby systemów, część spośród których została zaprojektowana przez najtęższe umysły z tej branży. Dlaczego wdrożenie zabezpieczeń do programu jest o wiele trudniejsze niż wdrożenie innych cech ?

Błędy utajone

Wiele systemów zabezpieczeń jest dobrze zaprojektowanych na papierze. Niestety, muszą zostać wdrożone zanim zostaną wykorzystane, a skłonni do pomyłek niedoskonali programiści wprowadzają błędy w czasie implementacji systemu. Błędy w zabezpieczeniach (ang. security bugs) nie są wyjątkowe pod tym względem. Jednakże, wyjątkowość błędów w zabezpieczeniach polega na tym, że są trudniejsze do wykrycia niż inne błędy w oprogramowaniu.

Jeśli nie zostanie zaalokowane wystarczająco dużo miejsca w formularzu dla etykiety, lub przypadkowo zostanie podstawiony jeden operator zamiast innego w obliczeniach, to błąd łatwo rzuca się w oczy. Etykieta jest obcięta, lub wynik obliczeń jest zły. Wytropienie źródła problemu może być trudne, ale samo występowanie problemu jest zawsze bezdyskusyjne.

Z drugiej strony, cechy bezpieczeństwa są często kodowane tak, aby były niewidoczne dla użytkownika. Są one często trudne do zobaczenia nawet dla samego programisty. Wiele spośród znalezionych nieoczywistych błędów w zabezpieczeniach pojawia się w postaci efektu ubocznego normalnego algorytmu wykonania, w wyniku którego następuje przeciek ważnej informacji. Inne błędy wiążą się z zaniedbaniem porządkowania, które nie wpływa na wykonywanie programu, ale które mogłoby zapobiec naruszeniom bezpieczeństwa. Oznacza to, że program może poprawnie przejść każdy test oraz wykazać pełną swoją funkcjonalność, ale być przy tym całkowicie pozbawiony zabezpieczeń.

Na przykład, długie hasło (ang. pass phrase) po wprowadzeniu przez użytkownika może zostać zmieszane (ang. hashed) w celu utworzenia klucza szyfrowania sesji (ang. encryption session key). Jednak logika usuwania z pamięci nie zmieszanego długiego hasła może zawierać błąd, który to uniemożliwia. Program kontynuuje poprawnie swoje działanie, ponieważ więcej już nie potrzebuje długiego hasła. We wszystkich jego operacjach wykorzystywany jest zmieszany klucz sesji (ang. hashed session key). Taki program może przejść wyśmienicie przez wszystkie testy. Jednakże, agresor po zauważeniu tego przeoczenia, może całkowicie obejść zabezpieczenia programu. Agresor powodując krach programu po wprowadzeniu zdania hasłowego, może być w stanie utworzyć plik zrzutu pamięci (ang. crash dump file), zawierający dane programu, z którego wyizoluje długie hasło.

Nie wszystkie błędy w zabezpieczeniach muszą być tak subtelne. Czasami, prosty fakt braku sprawdzenia zakresu bufora pamięci może być katalizatorem do całkowitego złamania zabezpieczeń. Przykładowo, wiele spośród ostatnich błędów w przeglądarce Internet Explorer firmy Microsoft jest spowodowanych przez translator URL. Szczególny adres URL, który może powodować złamanie zabezpieczeń może być zablokowany za pomocą jednego uaktualnienia zabezpieczenia. Jednakże, ponieważ sprawdzenia działają na danym adresie URL zanim nastąpi zdekodowanie symboli sterujących ze znakiem %, agresorzy zdołali wykorzystać te same błędy w oprogramowaniu poprzez zakodowanie części swojego adresu URL (na przykład, kodując wszystkie znaki A za pomocą %41). Wirusy używały latami, z różnym skutkiem, podobnych metod, by umknąć uwadze programów antywirusowych (ang. virus scanners).

Zaleta paranoi

Wielu programistów, widząc złamania zabezpieczeń w programach napisanych przez szanowanych programistów (niektórych z wieloletnim doświadczeniem w implementowaniu bezpiecznych systemów) pogrąża się w rozpaczy. Jeśli „eksperci” nie mogą napisać bezpiecznego kodu to jak może temu podołać przeciętny programista? W istocie wśród obserwatorów środowiska internetowego panuje przekonanie, że bezpieczeństwo jest celem niemożliwym do osiągnięcia — żadna twierdza kodu nie oprze się długo przemyślanym atakom ciekawskiego nastolatka.

Takie mniemanie jest, w pewnym stopniu, prawdziwe. Zawsze była to raczej kwestia stopnia zabezpieczenia, niż absolutnego rozwiązania problemu bezpieczeństwa. Stąd też, większość fizycznych systemów zabezpieczeń dążyła raczej do uczynienia ewentualnej penetracji zbyt kosztowną, aniżeli do jej całkowitego uniemożliwienia. To samo tyczy się bezpieczeństwa cyfrowego (ang. digital security). Przy projektowaniu bezpiecznego systemu, trzeba zawsze mieć na uwadze określone zagrożenia, którym system powinien się oprzeć, a następnie wdrożyć środki zaradcze w celu zmniejszenia do akceptowalnego poziomu ryzyka infiltracji. Niekiedy, złamanie zabezpieczeń może być zwyczajnie wynikiem działań zdeterminowanego agresora, który naruszy system zabezpieczeń ponad ich zaprojektowaną tolerancję, podobnie jak obciążony most może się zawalić w godzinach szczytu komunikacyjnego, jeśli obciążenie mostu przekroczy jego dopuszczalną nośność.

Jednocześnie jednak, programiści przyzwyczajeni do pisania bezpiecznych kodów mają tendencję do ulegania w dużym stopniu paranoi. Wyróżnikiem bezpiecznego programowania jest zarządzaniem zaufaniem — czy to zaufaniem do określonego systemu kodowania (ang. cryptosystem), czy to zaufaniem do tego, że jakiś fragment kodu działa poprawnie, czy do tego, że użytkownik poda poprawne dane wejściowe, albo też zaufaniem do systemu, który autoryzuje dany fragment kodu do wykonania określonego zadania. Zatem metoda ograniczonego zaufania, ograniczonego tak bardzo jak to możliwe bez poświęcenia niezbędnego zestawu funkcji, jest pewną drogą sukcesu dla programistów do zmniejszenia ilości błędy w zabezpieczeniach.

Czy można zaufać kompilatorowi?

W swoim wystąpieniu z okazji otrzymania Nagrody Turinga w 1984, Ken Thompson — jeden z twórców UNIX-a — opisał jak stworzył niewykrywalną, nawet z dostępnym kodem źródłowym, słabość w zabezpieczeniu w programie narzędziowym.

Thompson opisał jak zdołał zmodyfikować kompilator języka C we wczesnej wersji UNIX-a tak, aby wykrywał fakt kompilowania kodu dla login(1) i wstawiał kod, który akceptowałby zawsze pewne hasło. Umożliwiało to zarejestrować się komuś, kto znał to hasło jako dowolny użytkownik. Następnie zmodyfikował kod źródłowy kompilatora C tak, aby wykrywał, kiedy kompilował samego siebie (wówczas, tak jak i teraz, sam kompilator języka C był napisany w C) i wstawił ów kod (do wykrywania login i wprowadzania konia trojańskiego) do kompilatora języka C. Potem usunął zmiany jakich dokonał na źródle.

Od tego momentu, kompilator C w UNIX zawsze dołączał jego konia trojańskiego, ilekroć kompilował program login(1). Żadna inspekcja (ang. auditing) kodu źródłowego, dokonana dla login, lub dla kompilatora C, nie pozwoliłby na wykrycie jakiegokolwiek problemu.

Przykłady takie jak ten, pomagają zilustrować, jak dalece współczesny programista ufa współczesnemu środowisku komputerowemu. Pokłada się zaufanie w wielu składnikach systemu — kompilatorze, programie ładującym, dynamicznym konsolidatorze (ang. dynamic linker), a nawet w dekoderze mikrokodu w CPU — w nadziei, że robią dokładnie to, o co się je prosi. Każdy z tych składników — zwłaszcza używanych w trakcie wykonywania, takich jak program ładujący czy dynamiczny konsolidator — może być ze swej strony źródłem słabości w zabezpieczeniach.

Ograniczanie zaufania może być zrealizowane na wiele sposobów. Oto kilka przykładów:

Wybierz komponenty dla swojego systemu, które są dostarczane z pełnym kodem źródłowym (najlepiej z otwartym dostępem do kodu źródłowego — ang. open source, OS) włącznie ze wszystkimi modułami od dostawcy oprogramowania. Jeśli to możliwe, dokonaj inspekcji wszystkich takich modułów (lub zapłać jakiemuś profesjonaliście, aby to zrobił), lub poszukaj w Internecie wyników publicznej inspekcji (ang. public audit results) na serwerach WWW poświęconych bezpieczeństwu. To może okazać się trudne do zrobienia, a co więcej, nie jest zawsze w praktyce możliwe do zrealizowania w pełni. Niemniej jednak, biblioteki od dostawcy oprogramowania oraz komponenty z otwartym dostępem do kodu źródłowego mogą być płodnym źródłem problemów z bezpieczeństwem. Jeśli dostawca odmawia udostępnienia kodu źródłowego, to należy rozważyć jego zamianę na takiego dostawcę, który je udostępni. Warto przynajmniej przeprowadzić inspekcję w taki stopniu, w jakim to możliwe, poprzez poddanie próbie wytrzymałości, przykładowo poprzez wprowadzanie nieprawidłowych danych do biblioteki i obserwowanie w jaki sposób ona zawodzi.

Uruchamiaj z najmniejszymi możliwymi przywilejami. Oddziel kod, który wymaga specjalnych przywilejów (taki jak kod otwierający uprzywilejowany port sieciowy) od reszty programu. Jeśli to możliwe, zakończ wszystkie uprzywilejowane zadania przy starcie programu, a następnie zrezygnuj ze wszystkich przywilejów na zasadniczą część przebiegu programu. Jeśli specjalne przywileje są wymagane na bieżąco, to warto rozważyć rozdzielenie tego fragmentu kodu na oddzielny proces, który porozumiewa się z resztą programu za pośrednictwem jakiejś metody komunikacji międzyprocesowej IPC (chociaż patrz następny punkt — ta metoda niesie ze sobą dodatkowe ryzyko).

Nie ufaj danym pochodzącym spoza programu — nawet wziętych z własnego kodu. Napisz procedury sprawdzające, które zapewnią poprawność danych spoza programu. Należy pamiętać, że dane spoza programu zawierają dane wygenerowane przez system operacyjny lub biblioteki systemowe. One także mogą być manipulowane.

Bezpieczeństwo systemu plików

Bezpieczeństwo w systemach UNIX (oraz systemach zbliżonych do UNIX-a, takich jak Linux) zasadza się na dwóch fundamentalnych pojęciach: przywilejach użytkownika (ang. user privileges) oraz uprawnieniach w systemie plików (ang. file system permissions). Zagadnienia związane z uprawnieniami w systemie plików są zdecydowanie częściej spotykane w codziennej praktyce statystycznego programisty.

Standardowe uprawnienia

Większość użytkowników i programistów UNIX-a jest zaznajomiona ze standardową macierzą bezpieczeństwa, opisaną w poprzednim tomie i zilustrowaną za pomocą polecenia ls –l:

 

   user  group  world     (uzytkownik grupa inni)

     \     |      /

     rwx  rwx  rwx

     |     |     |

    read write execute    (odczyt   zapis   wykonanie)

 

Powyższe atrybuty są reprezentowane w polu bitowym, które jest zachowane we wpisie katalogowym. Dostęp do tego pola bitowego zapewniają rodziny chmod(2) i stat(2) wywołań systemowych. Najczęściej odwołuje się do tego pola bitowego w postaci notacji ósemkowej (o podstawie 8). Notacja ta jest szczególnie wygodna w tym przypadku, ponieważ każdy zestaw uprawnień (dla użytkownika, grupy oraz innych) może być przedstawiony za pomocą pojedynczej cyfry w zapisie ósemkowym. Same uprawnienia mogą być wyliczone poprzez dodanie do siebie wartości ósemkowych dla każdego typu uprawnienia.

 

Odczyt (Read)

4

Zapis (Write)

2

Wykonanie (Execute)

1

Bit lepki

Oprócz bitów standardowych uprawnień, większość systemów UNIX (w tym Linux) posiada bit zwany „lepkim” (ang. sticky bit). Może on być zmieniany poprzez ustawianie lub usuwanie bitu w polu uprawnień odpowiadającym wartości 1000 w zapisie ósemkowym, lub przy pomocy polecenia chmod wraz z parametrem +t lub –t. Bit lepki jest wskazywany w wydruku polecenia ls –l poprzez literę t w kolumnie odpowiadającej użytkownikowi w polu określającym uprawnienia innych (ang. world permissions place).

Historycznie rzecz biorąc, bit lepki został zaprojektowany dla wskazywania plików programów, które „ugrzęzły” w pamięci lub obszarze pliku wymiany po zakończeniu działania, jako metoda optymalizacji wydajności w wolniejszych systemach. W Linuksie, programy z ustawionym bitem lepkim są przechowywane w przestrzeni pliku wymiany nawet po zakończeniu wykonywania. Ta cecha jest utrzymana głównie dla zachowania kompatybilności — obecnie niewiele systemów jest tak wolna lub wrażliwa, aby wymagała podobnych sztuczek.

Bit lepki zastosowany do katalogów przyjmuje nową i bardziej interesującą rolę. Jeśli bit lepki jest ustawiony dla katalogu, to pliki w tym katalogu nie mogą być usunięte przez żadnego użytkownika, z wyjątkiem administratora, właściciela pliku lub właściciela katalogu. W szczególności, w katalogach z uprawnieniami do zapisu przez grupę oraz innych, które mają ustawiony bit lepki, dowolny użytkownik (lub dowolny użytkownik we właściwej grupie) może utworzyć nowe pliki. Nie może on jednak usunąć plików innego użytkownika, chyba że jest właścicielem katalogu.

Tej cechy można używać we wszystkich sytuacjach, w których istnieje potrzeba wzajemnego oddziaływania użytkowników ze sobą lub ze wspólną usługą. Na przykład program może pozwalać użytkownikom na pozostawianie plików w obszarze publicznym (ang. staging area) dla późniejszego ich wykorzystania przez proces cron lub jakiś inny demon. Aby uniemożliwić użytkownikom usunięcie procesów należących do innych użytkowników (z rozmysłem lub przypadkowo) można ustawić bit lepki dla katalogu publicznego.

Często również, z oczywistych powodów, wykorzystuje się bit lepki we współużytkowanych katalogach tymczasowych, takich jak /tmp i /var/tmp. Jeśli program tworzy dowolny katalog tymczasowy z dostępem dla wielu użytkowników, to można użyć bitu lepkiego dla tego katalogu.

Atrybuty setuid i setgid

W powszechnym użyciu znajdują się dodatkowe dwa zaawansowane atrybuty bezpieczeństwa plików: bity setuid (set user ID — ustaw identyfikator użytkownika) oraz setgid (set group ID — ustaw identyfikator grupy). Plik może być ustawiony setuid poprzez ustawienie lub usunięcie bitu reprezentowanego przez wartość 4000 w zapisie ósemkowym lub za pomocą symbolicznych argumentów u+s lub u-s dla polecenia chmod. Atrybut setgid może być ustawiony lub usunięty przy pomocy bitu dla wartości 2000 w zapisie ósemkowym, lub też przy pomocy symbolicznych argumentów g+s lub g-s. Można ustawić atrybuty setuid i setgid dla plików, bez ustawiania ich jako wykonywalne — w praktyce nie stosuje się tego prawie nigdy w odniesieniu do plików, zaś w przypadku katalogów spotyka się to niezmiernie rzadko.

Bity te mają dwojakie znaczenie, w zależności od tego, czy są użyte dla plików czy dla katalogów. Dla wykonywalnych plików, owe atrybuty zmieniają przywileje procesu, który nimi zarządza. Dla katalogów, powodują zmianę domyślnej własności nowo utworzonych plików.

Atrybuty setuid i setgid dla wykonywalnych plików

Kiedy wykonywalny plik, który ma ustawione atrybuty setuid i (lub) setgid, jest uruchomiony, to efektywne identyfikatory ID użytkownika i (lub) grupy są zmienione tak, aby pasowały do właściciela i (lub) grupy uruchomionego pliku. To daje aktualnemu użytkownikowi prawa tego właśnie właściciela i (lub) grupy w obrębie pojedynczego procesu — jednak inne procesy danego użytkownika nie zyskują dodatkowych przywilejów. Jeśli wykonywalny plik ma ustawiony atrybut setuid i jego właścicielem jest administrator (sytuacja powszechnie określana jako setuid root), to użytkownik wykonujący ten plik uzyska dla tego tylko procesu pełne przywileje superużytkownika.

Jest jeden wyjątek — atrybuty setuid i setgid są ignorowane w wielu systemach, włącznie z Linuksem, jeśli wykonywalny plik jest skryptem. Jest tak ponieważ, gdy pozwolić skryptom na uzyskanie atrybutu setuid lub setgid pojawia się problem bezpieczeństwa — zobacz wyjaśnienie w podrozdziale Ogólne wskazówki i techniki zabezpieczeń, dotyczące Warunku wyścigu.

Programiści mogą wykorzystać tę potężną zdolność do umożliwienia użytkownikom lub procesom zdobycia większych przywilejów niż te, które zwykle posiadają. Pozwala przy tym programiście na nadzór wykorzystywania tych przywilejów. Najczęściej realizuje się to za pomocą uprawnień plikowych (ang. file permissions). Polega to na nadaniu użytkownikowi tymczasowego prawa odczytu lub zapisu jakiegoś pliku lub katalogu, ale tylko poprzez określony program i w sposób przez ten program dozwolony.

Na przykład, programy zajmujące się pocztą elektroniczną mają często nadane atrybuty setgid dla grupy o nazwie mail (setgid mail), która jest określona jako właściciel plików z pocztą dla każdego użytkownika. Grupa ta uzyskuje również prawo do zapisu plików z pocztą. To gwarantuje, że żaden użytkownik nie może czytać poczty innego użytkownika w normalnej sytuacji, ale jednocześnie pozwala uruchomionemu przez użytkownika agentowi na dostarczenie poczty do innego użytkownika (poprzez dołączenie wiadomości do pliku pocztowego innego użytkownika).

Nieograniczona potęga superużytkownika może być zarządzana ściśle poprzez programy setuid root, pozwalając użytkownikom lub procesom na dostęp jedynie do pewnych funkcji superużytkownika, bez konieczności podawania im hasła administratora. Przykładowo, narzędzie su(1), spełnia swoją magiczną funkcję za pośrednictwem setuid root. W innym przykładzie, tylko superużytkownik może mieć bezpośredni dostęp do warstwy sprzętowej w systemie Linux, ale system X Window wymaga bezpośredniego dostępu do warstwy sprzętowej, aby móc w pełni zrealizować zakres funkcji karty graficznej (jak dla XFree86 3.x i Linux 2.2). Zatem, serwery X w dzisiejszych dystrybucjach Linuksa są bardzo często setuid root, co pozwala im właściwie działać. Serwer X jest także zaprojektowany tak, aby ściśle ograniczać zakres czynności użytkownika. Przykładowo, uniemożliwia użytkownikowi użycie serwera X do odczytu plików innego użytkownika.

Atrybut setgid dla katalogów

Atrybut setuid nie pełni żadnej roli dla katalogów. Atrybut setgid powoduje ustawienie przez system dla wszystkich nowych plików w katalogu takiej grupy właściciela, do jakiej przypisany jest właściciel tego katalogu, zamiast nadania im domyślnej grupy użytkownika. Jest to często używane udogodnienie, umożliwiające współużytkowanie katalogów, w których atrybuty nowych plików są automatycznie ustawiane tak, aby umożliwić dostęp do nich przez innych członków grupy. To udogodnienie jest przeznaczone bardziej dla użytkowników i administratorów niż programistów — większość programów, które polegają na właściwym prawie własności grupy dla nowych plików, powinny ustawiać ich atrybuty jawnie dla wyeliminowania popełnienia błędu przez operatora, przy zmianie uprawnień dla katalogu.

Trzeba tu podkreślić, że powyższe uwagi są prawdziwe dla większości systemów operacyjnych opartych o System V, lub podobnych w działaniu, w tym także dla Linuksa. Systemy oparte na rodzinie BSD ignorują bit setgid dla katalogów i traktują je wszystkie tak, jakby były setgid.

Bezpieczne używanie setguid i setgid

Uprawnienia setuid i setgid dla plików wykonywalnych dostarczają programiście bardzo potężnego narzędzia zarządzania przywilejami. Jednak są one przy tym także bardzo niebezpiecznymi narzędziami. Ich potęga wynika bezpośrednio z możliwości działania z większymi przywilejami. Jeśli zakłada się, że użytkownik nie może być obdarzony zaufaniem na tyle, aby korzystał z tych przywilejów poza programem, to nienaruszalność systemu zależy od stopnia, w jakim program może nałożyć nadzór na sposób wykorzystania przywilejów przez użytkownika. W szczególności jest to istotne dla programów setuid root, jako że złamanie zabezpieczeń może potencjalnie umożliwić użytkownikowi uzyskanie pełnych przywilejów superużytkownika.

Jako ilustrację rozważmy przykład z pocztą zaprezentowany powyżej. Agent setgid mail dostarczający pocztę może być napisany tak, aby jedynie umożliwić programowi dołączenie danych do już istniejącego pliku. Jednakże załóżmy, że programista włączył kod diagnostyczny do agenta pocztowego, który spowodował zapis w dzienniku zdarzeń do pliku nazwanego w określonym wierszu nagłówka pocztowego. Jeśli ten kod nie był usunięty w trakcie opracowania oprogramowania, to użytkownik mogłby przesłać informację z dziennika zdarzeń do dowolnego pliku, do którego miałby uprawnienia dostępu i ,w efekcie, zniszczyć pierwotną zawartość tego pliku.

Zwykle nie stanowi to problemu, ponieważ użytkownicy posiadają już zdolność niszczenia swoich własnych informacji. Ale w środowisku setgid mail, użytkownik nabywa prawo zapisu dowolnego pliku, do którego członkowie grupy mail mają prawo zapisu. Tak oto, jakiś złośliwy użytkownik mógłby skłonić agenta do użycia pliku skrzynki pocztowej innego użytkownika jako rejestru zdarzeń, a w efekcie spowodować usunięcie całej poczty tego użytkownika.

Jest to ekstremalny i cokolwiek wydumany przykład — większość rzeczywistych słabości zabezpieczeń jest znacznie mniej oczywista. Jest zatem szczególnie ważne postępowanie według, wspomnianej powyżej, zasady ograniczonego zaufania. Przykładowo, agent dostarczający pocztę mógłby być napisany jako więcej niż jeden program — zwyczajny program bez przywilejów, wykonujący większość wymaganych obowiązków, który w razie potrzeby wywołuje pomocniczy program, z uaktywnionym setgid mail. Znacznie łatwiej zabezpieczyć mały program wykonujący tylko jedno zadanie, niż złożony program realizujący wiele zadań.

Potwierdzanie tożsamości użytkowników

Mimo swej przydatności, uprawnienia dostępu do plików nie dają żadnej korzyści, jeśli użytkownicy mogą bez trudu przejmować nawzajem swoją tożsamość. Zatem, uwierzytelnianie — proces dowodzenia, że użytkownik jest tym za kogo się podaje — jest bardzo ważnym aspektem nadawania przywilejów.

Obecnie najbardziej powszechną w użyciu formą uwierzytelnienia jest proces zapytania użytkownika o nazwę (ang. username) i hasło (ang. password). Pomysł polegający na tym, że użytkownik przechowuje sekret, który dzieli z komputerem, dzięki czemu dowodzi kim jest, był znany odkąd pojawiły się pierwsze systemy wielodostępne. Standardy określające przechowywanie, porównywanie i przekazywanie haseł mogą ulegać zmianie, ale podstawowa idea pozostaje ta sama.

Tradycyjne uwierzytelnianie w UNIX-ie

Linux, czerpiący obficie ze swojego dziedzictwa UNIX-owego, przejął tradycyjne metody UNIX-a uwierzytelniania użytkowników. Owe metody są nadal używane jako standardowy sposób uwierzytelniania użytkownika. Omówimy pokrótce tę metodę, ponieważ pokaże ona zarówno jak został zaprojektowany dobry system, jak też i dlaczego ostatecznie ponosi on porażkę w zapewnieniu odpowiedniego poziomu bezpieczeństwa.

Podstawowe techniki

Standardowa informacja uwierzytelniająca w UNIX-ie jest przechowywana w dwóch plikach: /etc/passwd oraz /etc/group. Każdy plik zawiera zapisy, po jednym zapisie w wierszu, z polami oddzielonymi spacjami. Wpisy dla każdego pliku mogą być odczytane za pomocą funkcji getpw* i getgr*. Pierwsze i ostatnie pola są używane do uwierzytelnienia i zawierają nazwę użytkownika i hasło.

Hasła są przechowywane w wymieszanej postaci (ang. hashed form), uzyskanej za pomocą funkcji mieszającej (ang. hash function) crypt(3), z dołączoną na początku dwuznakową domieszką (ang. salt). Uwierzytelnienie jest dokonane poprzez żądanie nazwy użytkownika i hasła, sprawdzenie nazwy użytkownika z pomocą getpwnam(3), odzyskaniu wymieszanego hasła, zakodowania hasła podanego przez użytkownika z pomocą crypt(3)wraz z domieszką z przechowywanego hasła i porównania wyników. Jeśli zwrócony przez crypt(3) wymieszany łańcuch jest identyczny z przechowywanym, to hasła pasują do siebie i tak zostaje potwierdzona tożsamość użytkownika. (Zobacz poniżej, dla dokładniejszego wyjaśnienia uwierzytelnienia ręcznie podawanego hasła).

Ograniczenia

Tradycyjne uwierzytelnianie z hasłem ma jedną zaletę — jest wstecznie kompatybilne z prawie każdą odmianą UNIX-a. Oprócz tego jednego waloru, tradycyjne uwierzytelnianie jest niewystarczające niemal z każdego punktu widzenia.

Algorytm crypt(3) był uważany za bardzo dobry, kiedy go przyjęto po raz pierwszy we wczesnych l...

Zgłoś jeśli naruszono regulamin