Jak nie implementować kryptografii

Czy właśnie podjąłeś decyzję, że twój dynamicznie rozwijający się startup zasługuje jednak na szyfrowanie haseł użytkowników w bazie? Przeczytaj jak pod żadnym pozorem nie należy tego robić.

Implementacja funkcji kryptograficznych jest dziedziną programowania najeżoną pułapkami. Proste i zwykle poprawne techniki programistyczne mogą całkowicie skompromitować bezpieczeństwo zaimplementowanego przez nas protokołu kryptograficznego. Ilu programistów ma na przykład świadomość faktu, że ominięcie fragmentu wielokrotnie powtarzanej pętli za pomocą prostego wyboru if..then może dać atakującemu wartościową informację na temat przetwarzanego w niej klucza prywatnego? Wydaje się to dość oczywistą optymalizacją pętli, a tak właśnie działał atak czasowy na algorytm RSA i wiele innych.

Z tego powodu implementację funkcji kryptograficznych najlepiej zostawić specjalistom i korzystać z gotowych modułów realizujących precyzyjnie zdefiniowane funkcje biznesowe. Zasada ta powinna oznaczać nie tylko zakaz implementacji prywatnych algorytmów szyfrujących (bo to i tak robią nieliczni zapaleńcy), ale także pozornie oczywistego wykorzystania sprawdzonych algorytmów - jak np. MD5 - do haszowania haseł - i to już jest błąd nagminny.

Zobacz również:

  • Indywidualna kryptografia na dobre uniemożliwi współdzielenie kont na Netflixie?

WSJ

Przeanalizujmy błąd popełniony przez programistów należącego do The Wall Street Journal portalu WSJ.com i opisany w 2009 roku przez Travisa H. w prezentacji "Web 2.0 Crypto". Autorzy aplikacji wykorzystali popularną w systemach uniksowych funkcję crypt() do szyfrowania cookie identyfikującego zalogowanych użytkowników. Pierwszym argumentem tej funkcji jest tzw. salt czyli losowa ale jawna wartość powodująca, że za każdym razem haszowanie hasła - nawet takiego samego - da inny wynik. Drugim argumentem jest samo hasło.

Programiści portalu zasugerowali się jednak nazwą funkcji i wykorzystali ją nie jak funkcję skrótu, tylko jak funkcję szyfrującą - zamiast hasła użytkownika na wejściu podawali jego login z doklejonym tajnym kluczem, wbudowanym w aplikację.

Problem polegał jednak na tym, że klasyczna funkcja crypt() bierze pod uwagę jedynie osiem pierwszych znaków ciągu wejściowego. Dlatego jeśli login użytkownika brzmiał "dandylion", a klucz aplikacji "WSJ123" to funkcja crypt() faktycznie przyjmowała na wejściu jedynie fragment "dandylio", a cała reszta nie miała znaczenia - a w szczególności klucz aplikacji .Wystarczyło więc znać nazwę użytkownika by skutecznie podszyć się pod niego.

Podejrzliwemu programiście może nasunąć się pytanie - a skąd włamywacz może znać takie szczegóły implementacji jak te opisane powyżej? Zazwyczaj to proste do odgadnięcia - pierwsze co robi się przy testowaniu bezpieczeństwa aplikacji webowej to sprawdzenie identyfikatorów sesji. Sprawdzamy jaką mają długość (to często wskazuje na popularne algorytmy), z jakich znaków się składają i - w szczególności - jak się zmieniają w czasie oraz w zależności od parametrów takich jak nazwa użytkownika.

Co gorsza taka konstrukcja funkcji szyfrującej umożliwia stosunkowo łatwe odgadnięcie tajnego klucza zaszytego w aplikacji. Jak? Rejestrujemy konto z loginem o długości siedmiu znaków - w skrócie zostanie więc użyty pierwszy znak klucza aplikacji. Zakładamy, że jest to litera "a", generujemy token i sprawdzamy czy aplikacja nas wpuści. Jeśli nie to testujemy kolejne znaki. Po odgadnieciu pierwszego znaku skracamy login i zgadujemy drugą literę klucza. W przypadku WSJ autorowi opisanego ataku zajęło to... 17 minut a tajny klucz aplikacji brzmiał po prostu "March20".

Funkcje skrótu

Współcześnie trudno spotkać implementację zawierającą podobny błąd - ten opisany powyżej pochodzi z 2001 roku, kiedy zarządzanie sesją i uwierzytelnieniem nie było jeszcze jedną z podstawowych funkcji popularnych frameworków. Dzisiaj nie brakuje jednak błędnych implementacji haszowania haseł użytkowników przechowywanych w bazie. Haszowanie jest odpowiedzią na dość oczywiste zagrożenie - jeśli przechowujemy wszystkie hasła w postaci niezaszyfrowanej to wyciek bazy oznacza naruszenie bezpieczeństwa wszystkich użytkowników. Jeśli hasła są przechowywane w postaci skrótów wygenerowanych przez kryptograficzną funkcję haszującą to ujawnienie skrótu teoretycznie nie skutkuje ujawnieniem hasła. Skrót powinien jest teoretycznie nieodwracalny...

Ale znając skrót można przecież nadal próbować zgadnąć hasło testując kolejne kombinacje znaków, licząc ich skróty i porównując z tymi, które zostały wykradzione - współczesne procesory mogą wykonywać setki tysięcy takich operacji w ciągu sekundy. Rewolucją pod względem łamania zahaszowanych haseł okazały się też tzw. tęczowe tablice (rainbow tables) czyli specjalnie posortowane tablice (o wielkościach rzędu kilkudziesięciu gigabajtów) zawierające pregenerowane skróty wszystkich kombinacji liter np. do ośmiu znaków długości. Z ich wykorzystaniem łamanie haseł zajmuje kilka sekund.

Ataki tego typu są skuteczne zawsze wtedy gdy hasło jest jedynie przepuszczane przez funkcję haszującą (np. MD5, SHA1 i inne). Już samo zastosowanie modyfikatora (salt) powoduje radykalny wzrost ilości kombinacji skrótu każdego hasła ale nadal jest niewystarczające. Problem ten występuje np. w starszych wersjach systemu Windows (algorytm LMHASH) oraz bazy MySQL (do wersji 4.1) - oraz w wielu aplikacjach webowych (np. w starszych wersjach Drupala czy Wordpressa).

W celu komercyjnej reprodukcji treści Computerworld należy zakupić licencję. Skontaktuj się z naszym partnerem, YGS Group, pod adresem [email protected]

TOP 200