Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Kardynalny błąd w popularnej javowej bibliotece kryptograficznej Bouncy Castle – można omijać logowanie, jeśli użyto bcrypt-a
Sami badacze piszą tak:
CVE-2020-28052 is an authentication bypass vulnerability discovered in Bouncy Castle’s OpenBSDBcrypt class. It allows attackers to bypass password checks.
O co dokładnie chodzi? Zerknijcie tutaj. Użytkownik loguje się, podając hasło. Aplikacja liczy hash hasła i porównuje go z wartością w bazie. W jaki sposób porównuje? Mniej więcej tak:
boolean isEqual = sLength == newBcryptString.length();
for (int i = 0; i != sLength; i++)
{
isEqual &= (bcryptString.indexOf(i) == newBcryptString.indexOf(i));
}
return isEqual;
Problemem jest tutaj funkcja indexOf(), o której czytamy:
The indexOf() method returns the position of the first occurrence of specified character(s) in a string.
Zauważmy, że tutaj programiści podają kolejne liczby (od 0 do 59; długość hasha bcrypt to 60). Zatem to porównanie:
bcryptString.indexOf(0) == newBcryptString.indexOf(0)
zwróci nam prawdę (w obu przypadkach wartości będą równe -1; w hashach bcrypt() nie ma wartości o kodzie ASCII 0 (występują tutaj tylko znaki z Base64, $, 2a no i koszt (czyli cyfry):
Podobnie bcryptString.indexOf(1) == newBcryptString.indexOf(1) jak i bcryptString.indexOf(2) == newBcryptString.indexOf(2) zwróci nam „true” (ponownie, w hashach bcrypt nie ma znaków o kodzie ASCII 1 czy 2).
W tym momencie programista myślał, że mamy już porównane trzy pierwsze znaki z obu hashy – a tymczasem guzik prawda. Mamy zawsze wynik true.
Jeśli popatrzymy na tablicę ASCII, to okaże się że realnie porównywanych jest zaledwie kilka znaków (przypominam, patrzymy tylko do kodu 59 włącznie):
Będą to cyfry, slash (tylko te znaki z przedziału kodów ASCII 0-59) mogą być wartościami Base64() oraz znak $ (separator w bcrypt).
Przy czym dolar możemy odrzucić. Porównanie:
bcryptString.indexOf(36) == newBcryptString.indexOf(36)
ponownie będzie zawsze prawdziwe (bo pierwsze wystąpienie dolara w każdym hashu bcrypt jest zawsze takie samo). Podobnie możemy odrzucić porównanie z dwójką. Również porównania z kosztem (patrz wcześniejszy zrzut ekranowy z bcrypt) będą prawdziwe – bo porównujemy hashe o tym samym koszcie. W większości przypadków będą to dwie różne cyfry (więc mamy kolejne dwa porównania spełnione).
Zostaje więc zatem do spełnienia raptem 8 z 60 porównań, które powinny być zrealizowane przy poprawnej weryfikacji hasha.
Badacze podsumowują to tak:
Na badanej próbce, około 20% haseł udało się obejść techniką bruteforce (max 1000 prób). W rzadkich przypadkach można się było zalogować dowolnym hasłem:
An attacker must brute-force password attempts until the bypass is triggered. Our experiments show that 20% of tested passwords were successfully bypassed within 1,000 attempts. Some password hashes take more attempts, determined by how many bytes lie between 0 and 60 (1 to 59). Further, our investigation shows that all password hashes can be bypassed with enough attempts. In rare cases, some password hashes can be bypassed with any input.
Podatne są wersje Bouncy Castle 1.65 oraz 1.66.
–ms
Nie programuję na co dzień, ale przypadkiem nie jest tak, że parametrem funkcji 'indexOf()’ jest string? Zatem 'indexOf(36)’ nie zwraca '$’ ale pozycję substringu „36” w stringu bcryptString? Jeśli tak, to problemem jest to że ta procedura porównuje pozycje znaków/stringów od „0” do „59” w dwóch ciągach znaków, zamiast kolejnych znaków obu ciągów.
Może być char może być int (https://www.w3schools.com/java/ref_string_indexof.asp#:~:text=The%20indexOf()%20method%20returns,(s)%20in%20a%20string.) Tutaj ewidentnie jest int
Czy dobrze rozumiem, że to taki gruby babol, bo miało być porównanie hashy znak po znaku, a wyszło niewiadomoco?
Czyli w zamyśle miało być:
bcryptString[i] == newBcryptString.indexOf[i];
Wydaje mi się, że użycie w tym przypadku indexOf jest zwyczajnie grubym błędem w postaci niezrozumienia co robi indexOf. Mam racje?
Masz rację, że to ewidentnie pomyłka programisty, raczej przemęczenie niż niezrozumienie polecenia.
Widziałbym to tak:
bcryptString[i] == newBcryptString[i]
albo tak:
bcryptString.charAt(i) == newBcryptString.charAt(i)
https://www.w3schools.com/java/ref_string_charat.asp