Ostatnio na BugTraq pojawiła się następująca wiadomość:
Pidgin IM Client Password Disclosure Vulnerability (...) The credentials used to connect to the required service i.e. username and password is not encrypted properly. The credentials can be extracted in clear text by dumping process memory of the live pidgin process when a connection is set. The vulnerability allows anyone with access to the client system to obtain the username and password. (...)Czy to źle?
I tak, i nie, wszystko zależy od sposobu, w jaki się na to popatrzy. Dokładnie taką samą "podatność" można znaleźć na przykład w programie Miranda, ale i w programach pocztowych czy przeglądarkach internetowych. Dlaczego tak jest i co z tego wynika?
Sprawa jest prosta, jeśli coś ma być użyte przez program, musi być w pamięci. Trzymając się przykładu komunikatora, program potrzebuje hasła, by nawiązać połączenie z serwerem. Hasło to później nie jest potrzebne, chyba, że połączenie zostanie zerwane i musi zostać nawiązane ponownie. Jak to może zostać zrealizowane:
- program poprosi użytkownika o podanie hasła,
- program hasło ma cały czas w pamięci,
- program wczyta hasło z pliku konfiguracyjnego,
Pierwsze rozwiązanie nie spotka się z wielkim uznaniem użytkowników. Podejrzewam, że ich większość potraktuje taką funkcję jako nieudolność programistów, którzy "nawet hasła zapamiętać nie potrafią". Nawet jeśli jakiś użytkownik jest na tyle świadomy, że nie pozwala na zapisanie hasła w pliku, to podejrzewam, że w przypadku konieczności podawania hasła przy każdym rozłączeniu z serwerem (a były takie czasy, gdy było to dość częste zdarzenie), potraktuje jako wadę programu. Dlatego w praktyce raz wpisane hasło, pozostaje w pamięci "na wszelki wypadek", bo może się przydać kolejny raz. Nie jest to rozwiązanie eleganckie. W zasadzie tego typu dane powinny być dostępne w pamięci wyłącznie wtedy, gdy są potrzebne, a później powinny być z niej usuwane. Zresztą usuwanie danych z pamięci wcale nie jest trywialnym zagadnieniem, bo jeśli na przykład na jakimś buforze jest wykonana operacja typu memset, a następnie bufor ten nie jest nigdy wykorzystywany (lub jest zwalniany), może się okazać, że kompilator w ramach optymalizacji przykładową operację typu memset pominie (patrz komentarz do SecureZeroMemory). Pojawia się więc klasyczny konflikt między bezpieczeństwem a wygodą (funkcjonalnością). Ze względu na bezpieczeństwo, sekrety (hasła) powinny być usuwane. Z drugiej strony użytkownicy oczekują, że komunikator (czy program pocztowy) po prostu działają, łączą się z serwerami, pobierają pocztę. Do tych operacji potrzebne jest hasło, czyli musi się ono znaleźć w pamięci. Skoro z uwagi na wygodę użytkownika odrzucimy rozwiązanie z każdorazowym wpisywaniem hasła, można próbować usuwać hasło po użyciu i ponownie pobierać je z ustawień, gdy będzie to potrzebne. Można też próbować szyfrować hasła w pamięci i nie przechowywać ich w formie jawnej. Można, tylko po co?
Zasada z usuwaniem sekretu z pamięci jest zasadą słuszną, choć sensowna jest tylko w części przypadków. Jeśli loguję się do jakiegoś systemu, to moje hasło potrzebne jest tylko przez chwilę (na czas procesu jego weryfikacji), później powinno zostać usunięte, bo inaczej może zostać "odzyskane" na przykład przez wrogi kod. Tak było w przypadku kilku rozwiązań do szyfrowania dysków, które nie usuwały wpisanego hasła z buforu klawiatury, co pozwalało na jego odzyskanie. Tutaj nie ma wątpliwości, że hasło, na podstawie którego generowany jest klucz, powinno być usunięte zaraz po tym, gdy zostanie wykorzystane. Zresztą klucz również.
Co w przypadku wspomnianego komunikatora lub programu pocztowego? Problem leży w tym, że hasło to jest wykorzystywane przez cały czas. Może być oczywiście usuwane z pamięci i pobierane z pliku konfiguracyjnego (najlepiej zaszyfrowanego), może być szyfrowane w pamięci. Tylko po co? Atak polegający na odczytaniu pamięci zakłada, że atakujący może uruchomić swój kod. W tym przypadku nie robi mu wielkiej różnicy, czy to hasło jest już w pamięci, czy on sam odczyta je z pliku konfiguracyjnego. Nawet jeśli plik jest zaszyfrowany, to gdzieś musi istnieć klucz, który pozwala na jego odczytanie. Nawet jeśli tym kluczem jest "master password", które należy wpisać przy uruchamianiu programu, to znów - hasło (klucz) będzie w pamięci, lub znowu pojawi się problem częstego wpisywania tego "głównego hasła" (w końcu jakoś trzeba odszyfrować hasło zawarte w pliku). Analogicznie w przypadku szyfrowania danych w pamięci - tu również program musi dysponować kluczem, który pozwala na odszyfrowanie tych danych. Dane nie są dostępne w formie jawnej, ale są możliwe do (stosunkowo) łatwego pozyskania. W rezultacie zysk wynikający z zaimplementowania tego typu mechanizmów obronnych jest w praktyce znikomy i nie uzasadnia poniesionych kosztów.
Warto przeczytać również to wytłumaczenie: Plain Text Passwords. Może ten temat w przyszłości rozwinę, w szczególności w temacie "dlaczego jednak warto szyfrować hasła w plikach konfiguracyjnych".
Poza tym to raczej nie tyle "zapis hasła do pliku", ale "(skuteczne) usunięcie hasła z pamięci po wykorzystaniu" byłoby tu przydatne. Hasło MUSI się znaleźć w pamięci, jeśli ma być użyte, ale nie musi tam pozostać po wykorzystaniu.