Jak mogę chronić nazwę użytkownika i hasło MySQL przed dekompilacją?

87

.classPliki Java można dość łatwo dekompilować. Jak mogę chronić swoją bazę danych, jeśli muszę używać danych logowania w kodzie?

Jakob Cosoroaba
źródło
Mam nadzieję, że nie masz nic przeciwko, że przełożyłem twoje pytanie. Usunąłem „nazwę użytkownika” i „hasło” oraz dodałem „inżynierię wsteczną” i „dekompilację”. Myślę, że są bardziej opisowe niż oryginały. Nawiasem mówiąc, doskonałe pytanie o podstawy!
William Brendel,
6
Zwróć uwagę, że fakt, że używasz języka Java, nie ma tutaj większego znaczenia. Posiadanie na stałe zakodowanych haseł w jakimkolwiek języku jest problematyczne w podobny sposób („stringi thebinary” pokazują stałe łańcuchowe w programach C).
Joachim Sauer
@saua: To prawda, ale być może ktoś opublikuje przykładowy kod, jak oddzielić nazwy użytkowników i hasła w Javie. Mógłbym nawet zrobić to sam, jeśli mam czas.
William Brendel,
1
Zauważyłem, że wiele odpowiedzi sugeruje, że próbujesz ukryć nazwę użytkownika / hasło przed nieautoryzowanym użytkownikiem, podczas gdy użytkownik uruchamiający aplikację jest w porządku. Uważam, że chcesz ukryć hasło przed WSZYSTKIM . Proszę wyjaśnić to w pytaniu.
pek
1
Załóżmy na przykład, że poświadczenia są używane do łączenia się z serwerem, do którego nikt inny niż aplikacja nie może się zalogować.
pek

Odpowiedzi:

123

Nigdy nie wpisuj haseł na stałe do swojego kodu. Niedawno pojawiło się to na liście 25 najbardziej niebezpiecznych błędów w programowaniu :

Zakodowanie na stałe tajnego konta i hasła w oprogramowaniu jest niezwykle wygodne - dla wykwalifikowanych inżynierów wstecz. Jeśli hasło jest takie samo we wszystkich programach, każdy klient staje się podatny na ataki, gdy hasło nieuchronnie staje się znane. A ponieważ jest zakodowany na stałe, naprawienie go jest ogromnym problemem.

Informacje konfiguracyjne, w tym hasła, należy przechowywać w oddzielnym pliku, który aplikacja odczytuje podczas uruchamiania. To jedyny prawdziwy sposób, aby zapobiec wyciekowi hasła w wyniku dekompilacji (nigdy nie kompiluj go do pliku binarnego na początek).

Więcej informacji na temat tego częstego błędu można znaleźć w artykule CWE-259 . Artykuł zawiera dokładniejszą definicję, przykłady i wiele innych informacji na temat problemu.

W Javie jednym z najłatwiejszych sposobów jest użycie klasy Preferences. Jest przeznaczony do przechowywania wszelkiego rodzaju ustawień programu, z których niektóre mogą zawierać nazwę użytkownika i hasło.

import java.util.prefs.Preferences;

public class DemoApplication {
  Preferences preferences = 
      Preferences.userNodeForPackage(DemoApplication.class);

  public void setCredentials(String username, String password) {
    preferences.put("db_username", username);
    preferences.put("db_password", password);
  }

  public String getUsername() {
    return preferences.get("db_username", null);
  }

  public String getPassword() {
    return preferences.get("db_password", null);
  }

  // your code here
}

W powyższym kodzie możesz wywołać setCredentialsmetodę po wyświetleniu okna dialogowego z pytaniem o nazwę użytkownika i hasło. Gdy potrzebujesz połączyć się z bazą danych, możesz po prostu użyć metod getUsernamei getPassword, aby pobrać zapisane wartości. Dane logowania nie zostaną zakodowane na stałe w Twoich plikach binarnych, więc dekompilacja nie będzie stanowić zagrożenia dla bezpieczeństwa.

Ważna uwaga: Pliki preferencji to zwykłe pliki tekstowe XML. Upewnij się, że podejmujesz odpowiednie kroki, aby uniemożliwić nieautoryzowanym użytkownikom przeglądanie plików surowych (uprawnienia UNIX, uprawnienia systemu Windows itp.). Przynajmniej w Linuksie nie stanowi to problemu, ponieważ wywołanie Preferences.userNodeForPackagespowoduje utworzenie pliku XML w katalogu domowym bieżącego użytkownika, który i tak jest nieczytelny dla innych użytkowników. W systemie Windows sytuacja może wyglądać inaczej.

Więcej ważnych uwag: W komentarzach do tej odpowiedzi i innych było wiele dyskusji na temat prawidłowej architektury w tej sytuacji. Oryginalne pytanie tak naprawdę nie wspomina o kontekście, w którym aplikacja jest używana, więc opowiem o dwóch sytuacjach, które przychodzą mi do głowy. Pierwszy to przypadek, w którym osoba korzystająca z programu już zna (i ma prawo znać) dane uwierzytelniające do bazy danych. Drugi to przypadek, w którym programista próbuje ukryć dane uwierzytelniające bazy danych przed osobą korzystającą z programu.

Pierwszy przypadek: użytkownik jest upoważniony do poznania danych logowania do bazy danych

W takim przypadku sprawdzi się rozwiązanie, o którym wspomniałem powyżej. PreferenceKlasa Java będzie przechowywać nazwę użytkownika i hasło w postaci zwykłego tekstu, ale plik preferencji będzie mógł odczytać tylko upoważniony użytkownik. Użytkownik może po prostu otworzyć plik XML preferencji i odczytać dane logowania, ale nie stanowi to zagrożenia dla bezpieczeństwa, ponieważ na początku znał dane uwierzytelniające.

Drugi przypadek: próba ukrycia danych logowania przed użytkownikiem

Jest to bardziej skomplikowany przypadek: użytkownik nie powinien znać danych logowania, ale nadal potrzebuje dostępu do bazy danych. W takim przypadku użytkownik uruchamiający aplikację ma bezpośredni dostęp do bazy danych, co oznacza, że ​​program musi wcześniej znać dane logowania. Rozwiązanie, o którym wspomniałem powyżej, nie jest odpowiednie w tym przypadku. Możesz przechowywać dane logowania do bazy danych w pliku preferencji, ale użytkownik będzie mógł odczytać ten plik, ponieważ będzie właścicielem. W rzeczywistości nie ma dobrego sposobu na bezpieczne korzystanie z tego przypadku.

Prawidłowy przypadek: użycie architektury wielowarstwowej

Prawidłowym sposobem na to jest utworzenie warstwy pośredniej między serwerem bazy danych a aplikacją kliencką, która uwierzytelnia poszczególnych użytkowników i umożliwia wykonanie ograniczonego zestawu operacji. Każdy użytkownik miałby własne poświadczenia logowania, ale nie do serwera bazy danych. Poświadczenia umożliwiałyby dostęp do warstwy środkowej (warstwy logiki biznesowej) i byłyby różne dla każdego użytkownika.

Każdy użytkownik miałby własną nazwę użytkownika i hasło, które można przechowywać lokalnie w pliku preferencji bez żadnego zagrożenia bezpieczeństwa. Nazywa się to architekturą trójwarstwową (warstwy to serwer bazy danych, serwer logiki biznesowej i aplikacja kliencka). Jest to bardziej złożone, ale naprawdę jest to najbezpieczniejszy sposób zrobienia tego rodzaju rzeczy.

Podstawowa kolejność operacji to:

  1. Klient jest uwierzytelniany za pomocą warstwy logiki biznesowej przy użyciu osobistej nazwy użytkownika / hasła użytkownika. Nazwa użytkownika i hasło są znane użytkownikowi i nie są w żaden sposób powiązane z danymi logowania do bazy danych.
  2. Jeśli uwierzytelnianie powiedzie się, klient wysyła żądanie do warstwy logiki biznesowej z prośbą o podanie pewnych informacji z bazy danych. Na przykład spis produktów. Zwróć uwagę, że żądanie klienta nie jest zapytaniem SQL; jest to zdalne wywołanie procedury, takiej jak getInventoryList.
  3. Warstwa logiki biznesowej łączy się z bazą danych i pobiera żądane informacje. Warstwa logiki biznesowej odpowiada za tworzenie bezpiecznego zapytania SQL na podstawie żądania użytkownika. Wszelkie parametry zapytania SQL powinny zostać oczyszczone, aby zapobiec atakom polegającym na iniekcji SQL.
  4. Warstwa logiki biznesowej wysyła listę zapasów z powrotem do aplikacji klienckiej.
  5. Klient wyświetla listę inwentarza użytkownikowi.

Zauważ, że w całym procesie aplikacja kliencka nigdy nie łączy się bezpośrednio z bazą danych . Warstwa logiki biznesowej otrzymuje żądanie od uwierzytelnionego użytkownika, przetwarza żądanie klienta dotyczące listy zapasów, a dopiero potem wykonuje zapytanie SQL.

William Brendel
źródło
4
Jak dokładnie to uniemożliwia komuś uzyskanie nazwy użytkownika / hasła? Nie możesz po prostu przeczytać tego z pliku?
Joe Phillips
Jak powiedziałem w mojej odpowiedzi, jeśli uprawnienia do plików są ustawione poprawnie, tylko użytkownik uruchamiający program ma dostęp do odczytu tego pliku preferencji. W środowiskach UNIX odbywa się to automatycznie. Windows może wymagać dodatkowych kroków (naprawdę nie jestem pewien, ponieważ nie używam go zbyt często).
William Brendel,
Myślę, że chodzi o to, że użytkownik uruchamiający aplikację nie jest tym, przed którym próbujesz ją powstrzymać. W takim przypadku musiałbyś to zaszyfrować.
Michael Haren
Tak, Michael ma rację. Zasadniczo chodzi o to, że znasz już nazwę użytkownika / hasło, więc nie ma potrzeby ukrywania ich przed sobą. Będzie jednak ukryty przed innymi użytkownikami dzięki uprawnieniom do plików.
William Brendel,
7
Jeśli wdrażasz (na przykład) aplikację do edycji bazy danych dla użytkownika i nie chcesz, aby znał on nazwę użytkownika i hasło bazy danych, to źle zaprojektowałeś rozwiązanie, a oprogramowanie klienckie powinno komunikować się z serwerem (przez np. usługa sieciowa), która zajmuje się obsługą bazy danych.
JeeBee
15

Umieść hasło w pliku, który odczyta aplikacja. NIGDY nie osadzaj haseł w pliku źródłowym. Kropka.

Ruby ma mało znany moduł o nazwie DBI :: DBRC do takiego użycia. Nie wątpię, że Java ma odpowiednik. Zresztą napisanie takiego nie jest trudne.

Keltia
źródło
7
Chociaż to nieco ułatwia późniejszą zmianę haseł, nie rozwiązuje to podstawowego problemu z bezpieczeństwem.
Brian Knoblauch
Tak. Zobacz także odpowiedź Williama Brendela.
Keltia,
1
Metoda, którą Keltia i ja wskazałem, jest przyjętym sposobem radzenia sobie z tym problemem. Oddzielenie danych logowania od skompilowanego kodu jest jedną z najbardziej podstawowych praktyk bezpieczeństwa oprogramowania. Umieszczenie danych logowania w osobnym pliku jest skutecznym sposobem osiągnięcia tego.
William Brendel,
Ponadto fakt, że informacje konfiguracyjne znajdują się w pliku zwykłego tekstu, powinien zostać anulowany przez ograniczenia systemu operacyjnego. Na przykład w systemie UNIX plik zwykłego tekstu powinien należeć do użytkownika uruchamiającego program i mieć uprawnienia 0600, aby tylko właściciel mógł go odczytać.
William Brendel,
4
OK, więc plik może być odczytany tylko przez użytkownika uruchamiającego program. Świetny. To niczego nie rozwiązuje. :-) Ja, użytkownik, przed którym staramy się nie ujawniać hasła, mogę je odczytać równie łatwo jak aplikację ...
Brian Knoblauch
3

Piszesz aplikację internetową? Jeśli tak, użyj JNDI, aby skonfigurować go zewnętrznie w aplikacji. Przegląd jest dostępny tutaj :

JNDI zapewnia aplikacji jednolity sposób znajdowania i uzyskiwania dostępu do usług zdalnych w sieci. Usługą zdalną może być dowolna usługa przedsiębiorstwa, w tym usługa przesyłania wiadomości lub usługa specyficzna dla aplikacji, ale oczywiście aplikacja JDBC jest zainteresowana głównie usługą bazy danych. Po utworzeniu i zarejestrowaniu obiektu DataSource w usłudze nazewnictwa JNDI aplikacja może korzystać z interfejsu API JNDI w celu uzyskania dostępu do tego obiektu DataSource, którego można następnie użyć do połączenia się ze źródłem danych, które reprezentuje.

Tim Howland
źródło
podany link jest zły
James Oravec
2

Bez względu na to, co zrobisz, poufne informacje będą przechowywane gdzieś w jakimś pliku. Twoim celem jest uczynienie go tak trudnym, jak to tylko możliwe. To, ile z tego możesz osiągnąć, zależy od Twojego projektu, potrzeb i grubości portfela Twojej firmy.

Najlepszym sposobem jest nigdzie nie przechowywanie haseł. Osiąga się to za pomocą funkcji skrótu do generowania i przechowywania skrótów haseł:

hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366

Algorytmy skrótu to funkcje jednokierunkowe. Zamieniają dowolną ilość danych w „odcisk palca” o stałej długości, którego nie można cofnąć. Mają również tę właściwość, że jeśli dane wejściowe zmieni się choćby odrobinę, wynikowy hash jest zupełnie inny (patrz przykład powyżej). Jest to świetne do ochrony haseł, ponieważ chcemy przechowywać hasła w postaci, która je chroni, nawet jeśli sam plik haseł jest zagrożony, ale jednocześnie musimy być w stanie zweryfikować, czy hasło użytkownika jest poprawne.

Niepowiązana uwaga: W dawnych czasach internetu po kliknięciu łącza „Nie pamiętam hasła” strony internetowe wysyłały pocztą e-mail hasło w postaci zwykłego tekstu. Prawdopodobnie przechowywali je gdzieś w bazie danych. Gdy hakerzy uzyskaliby dostęp do ich bazy danych, uzyskaliby dostęp do wszystkich haseł. Ponieważ wielu użytkowników używałoby tego samego hasła w wielu witrynach internetowych, był to ogromny problem z bezpieczeństwem. Na szczęście w dzisiejszych czasach nie jest to powszechna praktyka.

Teraz pojawia się pytanie: jaki jest najlepszy sposób przechowywania haseł? Uznałbym tego (uwierzytelnianie i zarządzanie użytkownikami usług stormpath za) rozwiązanie dość jedna cholernie idealny:

  1. Twój użytkownik wprowadza poświadczenia i jest to sprawdzane na podstawie skrótu hasła
  2. Hasła są generowane i przechowywane, a nie hasła
  3. Hashe są wykonywane wielokrotnie
  4. Hashe są generowane przy użyciu losowo generowanej soli
  5. Hashe są szyfrowane kluczem prywatnym
  6. Klucz prywatny jest przechowywany w fizycznie innym miejscu niż skróty
  7. Klucze prywatne są aktualizowane w czasie
  8. Zaszyfrowane skróty są podzielone na części
  9. Te fragmenty są przechowywane w fizycznie oddzielnych lokalizacjach

Oczywiście nie jesteś Google ani bankiem, więc jest to dla Ciebie przesada. Ale potem pojawia się pytanie: ile bezpieczeństwa wymaga Twój projekt, ile masz czasu i pieniędzy?

W przypadku wielu aplikacji, chociaż nie jest to zalecane, przechowywanie zakodowanego na stałe hasła w kodzie może być wystarczająco dobrym rozwiązaniem. Jednak dzięki łatwemu dodaniu kilku dodatkowych kroków zabezpieczeń z powyższej listy możesz uczynić swoją aplikację znacznie bezpieczniejszą.

Na przykład załóżmy, że krok 1 nie jest akceptowalnym rozwiązaniem dla twojego projektu. Nie chcesz, aby użytkownicy wpisywali hasło za każdym razem lub nawet nie chcesz, aby użytkownicy znali hasło. Nadal masz gdzieś poufne informacje i chcesz je chronić. Masz prostą aplikację, nie ma serwera do przechowywania plików lub jest to zbyt kłopotliwe dla Twojego projektu. Twoja aplikacja działa w środowiskach, w których nie jest możliwe bezpieczne przechowywanie plików. To jeden z najgorszych przypadków, ale mimo to dzięki dodatkowym środkom bezpieczeństwa możesz mieć znacznie bezpieczniejsze rozwiązanie. Na przykład możesz przechowywać poufne informacje w pliku i możesz zaszyfrować plik. Możesz mieć prywatny klucz szyfrowania zakodowany na stałe w kodzie. Możesz zaciemnić kod, więc utrudniasz komuś złamanie go.ten link . (Chcę jeszcze raz Cię ostrzec, że nie jest to w 100% bezpieczne. Sprytny haker z odpowiednią wiedzą i narzędziami może to zhakować. Jednak w oparciu o Twoje wymagania i potrzeby może to być wystarczająco dobre rozwiązanie dla Ciebie).

Caner
źródło
1

To pytanie pokazuje, jak przechowywać hasła i inne dane w zaszyfrowanym pliku: 256-bitowe szyfrowanie Java AES oparte na hasłach

Aaron Digulla
źródło
1
Gdzieś w kodzie źródłowym nadal trzeba odszyfrować zaszyfrowane hasło, aby utworzyć połączenie, to uważane hasło nadal istnieje.
Bear0x3f
0

MD5 to algorytm mieszający, a nie algorytm szyfrowania, w skrócie nie można odzyskać haszowania, można tylko porównać. Powinien być idealnie używany do przechowywania informacji uwierzytelniających użytkownika, a nie nazwy użytkownika i hasła db. nazwa użytkownika db i pwd powinny być zaszyfrowane i przechowywane w pliku konfiguracyjnym, co najmniej.

renegadeMind
źródło
Słyszałem o ludziach, którzy generują wszystkie możliwe kombinacje ciągów i przechowują odpowiadające im skróty MD5. Więc kiedy znajdują czyjś hash MD5, po prostu znajdują przechowywany skrót i otrzymują odpowiedni ciąg.
Nav