Rozwój oprogramowania
Ataki czasowe w PHP: Praktyczny przykład - dlaczego `hash_equals()` ma znaczenie
Chcesz poznać realistyczny przykład ataku czasowego na tokeny API w Wordpress/Laravel/PHP? Omówimy to tutaj, w tym wskazówki i najlepsze praktyki dotyczące ataków czasowych.
Wielu programistów polega na prostym porównaniu przy użyciu == lub == podczas sprawdzania kluczy API, tokenów lub innych tajnych ciągów. Na pierwszy rzut oka wygląda to rozsądnie - ale jest pewien haczyk: różnice czasowe w porównaniu mogą zostać wykorzystane przez atakujących.
W tym artykule opisano realistyczny przykład tego, jak koncepcyjnie działa atak czasowy.
Prawdziwy scenariusz (uproszczony przykład)
Wyobraź sobie, że wewnętrzna mikrousługa akceptuje żądania z tokenem parametru zapytania i porównuje go z tokenem API GitHub przechowywanym w środowisku.
Niezabezpieczony kod (uproszczony):
$expected = getenv('GITHUB_API_TOKEN');$provided = $_GET['token'] ?? '';if ($provided == $expected) { } PHP
Wyjaśnię, dlaczego jest to problematyczne w następnej sekcji.
Jak działa atak czasowy?
Jeśli implementacja porównywania tokenów porównuje znak po znaku i przerywa natychmiast przy pierwszym błędzie, to żądanie z poprawnym pierwszym znakiem trwa średnio nieco dłużej.
Prawidłowy pierwszy znak prowadzi zatem średnio do nieco dłuższego czasu przetwarzania niż nieprawidłowy pierwszy znak. To samo dotyczy drugiego znaku i tak dalej.
Atakujący wykorzystuje to, testując wszystkie możliwe znaki dla każdej pozycji, bardzo często mierząc czasy odpowiedzi i analizując je statystycznie. Znak z największą średnią/proporcjonalnie dłuższym czasem jest prawdopodobnie poprawny. W ten sposób token jest rekonstruowany krok po kroku.
Dlaczego hash_equals() jest właściwym wyborem?
PHP zapewnia funkcję porównywania ciągów znaków o stałym czasie działania z hash_equals(). Gwarantuje ona, że czas wykonania porównania nie zależy od wspólnych prefiksów - o ile oba ciągi są tej samej długości.
Bezpieczna alternatywa (dla Laravel, PHP, Wordpress):
$expected = getenv('GITHUB_API_TOKEN'); $provided = $_GET['token'] ?? '';if (hash_equals($expected, $provided)) { } PHP
Uwaga: hash_equals() jest "niezależne od czasu" tylko wtedy, gdy długości są równe. Dobrą praktyką jest projektowanie tokenów tak, aby ich długość była stała (np. HMAC, UUID o stałej długości lub losowe ciągi Base64 o stałej długości).
Czy wiesz, że? Tylko UUID-V4 jest całkowicie losowo generowany i dlatego jest często używany do tokenów bezpieczeństwa. Ponieważ nie zawiera informacji związanych z czasem lub urządzeniem, oferuje wysoki stopień nieprzewidywalności, a tym samym chroni przed atakami opartymi na wzorcach lub przewidywaniach. Właśnie dlatego UUID-V4 jest preferowanym wyborem, jeśli chodzi o bezpieczne, trudne do odgadnięcia identyfikatory.
Dodatkowe środki obrony - więcej niż tylko hash_equals()
- Stała długość tokena
Używaj tokenów o stałej długości, np. UUID-V4. Jeśli atakujący odgadnie długość, jest to mniej pomocne, o ile same znaki są bezpieczne.
- Ograniczenie szybkości
Ogranicz liczbę żądań na adres IP lub konto. Ataki czasowe wymagają wielu pomiarów; ograniczenie szybkości znacznie zwiększa wysiłek i koszty dla atakującego.
- Rejestrowanie i monitorowanie
Rozpoznawaj nietypowe wzorce (wiele prób użycia tokenów, systematyczne zmiany niektórych parametrów) i reaguj automatycznie.
- Sól / HMAC / podpisy
Używaj podpisanych tokenów (HMAC z tajnym kluczem) lub tokenów OAuth zamiast otwartego porównywania surowych kluczy statycznych.
- TLS/HTTPS
Szyfrowanie chroni kanały komunikacji - chociaż nie zapobiega atakom czasowym na wewnętrzne elementy serwera, jest to podstawowy wymóg.
- Rotacja tokenów
Krótka żywotność tokenów zmniejsza korzyści wynikające z ich naruszenia.
Wnioski
Ataki czasowe nie są problemem czysto teoretycznym - zostały one zbadane w praktyce i mogą mieć poważne konsekwencje, zwłaszcza dla publicznie dostępnych punktów końcowych. Dobra wiadomość: dla programistów PHP/Laravel/Wordpress pierwszy środek zaradczy jest bardzo prosty:
- Zamień bezpośrednie porównania wrażliwych ciągów na
hash_equals().
- Zapewnij stałą długość tokenów, ograniczenie szybkości, logowanie i bezpieczne zarządzanie tokenami.