Dostęp do bazy danych w internecie

0

Cześć, załóżmy, że mam aplikację desktopową, która pobiera jakieś dane z bazy danych znajdującej się w necie. Generalnie jest tylko jedna taka baza na świecie.

I teraz są dwa podejścia do rozwiązania tego problemu:

  1. Aplikacja łączy się z bazą i wykonuje zwykłe zapytania SQL
  2. Aplikacja łączy się z jakimś API i pobiera dane za pomocą tego API np w JSON.

O ile kiedyś pierwsze rozwiązanie było normą, to teraz widzę wszędzie, żeby posługiwać się drugim rozwiązaniem. Więc pytam "Dlaczego?".

OK, jednym z powodów może być to, że API zawsze zwróci mi dane niezależnie od struktury bazy. Ale z drugiej strony wcale nie muszę robić selecta, wystarczy, że wywołam procedurę składowaną, która zrobi właściwie to samo - zwróci mi odpowiednie dane niezależnie od struktury bazy. Więc remis. Dlaczego zatem drugie rozwiązanie jest lepsze, skoro wymaga dużo więcej pracy?

4

Załóżmy, że masz w swojej aplikacji użytkowników i każdy z nich powinien mieć dostęp wyłącznie do swoich zasobów.

Jak zamierzasz komuś zabronić wykonać select * from users albo select * from jakaś_tabela, efektywnie pobierając z bazy wszystkie rekordy - nawet te, do których nie powinien mieć dostępu?

5
  1. wydzielanie danych tylko dla danego usera (jak wspomniał @Patryk27)
  2. możliwość logowania co, kto, kiedy (niby przy bezpośrednim połączeniu też się da ale tu jest to znacznie prościej)
  3. API wcale nie musi zwracać danych z jednej bazy, a Ty nawet o tym nie wiesz
  4. ukrywasz wrażliwe dane za serwisem, który je serwuje (generalnie patrz pkt 1)
2

Ja podam jeszcze jeden przykład "z życia wzięty". Firma X kupiła nasz soft do obsługi hurtowni (sprzedaż + magazyn). Sprytny admin mając dostęp do bazy chciał sobie "usprawnić robotę" i dziergał swoje skrypty, które robiły np inserty do poszczególnych tabel. W remanencie rocznym okazało się, ze jest rozjazd na kilkanaście mln zł. Okazało się, że admin nie miał świadomości, że sam insert nie załatwi tematu bo za każdym takim insertem musiał jeszcze stać odpowiedni dokument (insert w innej tabeli). Po wprowadzeniu czegoś na kształt API podaliśmy tylko odpowiednie wywołanie użytkownikom i po czasie okazało się, że nawet zaczęły aplikacje mobilne się pojawiać do naszego systemu przy zachowaniu pełnej spójności danych. :)

1

Ponieważ:

  • łatwiej wykorzystać dziury w MySQL 5.3 niż je znaleźć i wykorzystać w MojeREST API 138.1
  • filtrowanie i udostępnianie HTTP (REST, AJAX, HTML) z jakichś powodów uważa się za bezpieczniejsze
  • łatwiej zarządzać kontami użytkowników jeśli sam zakodujesz to zarządzanie - te wbudowane funkcje w bazę zwykle są słabe
0

OK, ale nie wiem, czy się rozumiemy. Użytkownicy aplikacji nie wykonują żadnych zapytań, tylko aplikacja to robi. Użytkownik klika przycisk, aplikacja wie jakie zapytanie ma wysłać, więc wysyła np:

SELECT pole FROM tabela WHERE id = 5 <-- to 5 oczywiście z czegoś wynika.

Nie ma też sytuacji, gdzie admin musi wykonać jaką operację na bazie. Przyjmijmy najprostszą możliwą sytuację: Jedna tabela, aplikacja wykonuje do niej SELECT (albo INSERT) z odpowiednimi warunkami. Dlaczego tutaj API miałoby być lepsze niż skonstruowanie kilku zapytań i wysłanie ich do bazy?

3

Ktoś broni użytkownikowi sprawdzić jak ta Twoja aplikacja działa, wydobyć dane do łączenia się z bazą, po czym zacząć samemu wysyłać zapytania?
Wydaje mi się, że za bardzo ufasz ludziom ;-)

0

Wg mnie (podkreślam, że jest to moja osobista opinia!) dostęp do danych przez jakikolwiek serwis nie powoduje, że te dane są w jakikolwiek sposób bardziej bezpieczne niż dane, do których jest dostęp "bezpośredni" (tzn. ma się usera i pass do bazy). Jeśli API umożliwia zmianę, dodanie, usunięcie danych to co najwyżej może to być "utrudnione" do usunięcia pojedynczych rekordów i monitorowane w sposób taki, że np. po dziesięciu usunięciach możliwość dalszego usuwania jest blokowana na np. 1h. Z poziomu SQLa możemy zaś napisać DELETE FROM tabelka i tyle :). Ale z drugiej strony na usera bazodanowego można nałożyć ograniczenia, że nie będzie mógł nic usunąć i/lub zmienić a tylko czytać.

Należy mieć też na uwadze, że wszelkie api wiążą się z narzutem czasowym oraz ilościowym przesyłanych danych. To pierwsze spowodowane jest tym, że oprócz odpytania bazy serwis musi jeszcze te dane zapakować w coś (JSON, XML, ...) i dopiero tak odesłać. Drugie związane jest z formatem w jakim aktualnie większość danych można dostać (bardzo popularne są formaty otwartym tekstem - JSON, XML (gdzie zdarza się, że metadane zajmują więcej niż same dane). Oczywiście można to pakować (bo dane tekstowe się dość dobrze pakują) ale to znowu dodatkowy czas.

2
Juhas napisał(a):

OK, ale nie wiem, czy się rozumiemy. Użytkownicy aplikacji nie wykonują żadnych zapytań, tylko aplikacja to robi. Użytkownik klika przycisk, aplikacja wie jakie zapytanie ma wysłać, więc wysyła np:

SELECT pole FROM tabela WHERE id = 5 <-- to 5 oczywiście z czegoś wynika.

Nie ma też sytuacji, gdzie admin musi wykonać jaką operację na bazie. Przyjmijmy najprostszą możliwą sytuację: Jedna tabela, aplikacja wykonuje do niej SELECT (albo INSERT) z odpowiednimi warunkami. Dlaczego tutaj API miałoby być lepsze niż skonstruowanie kilku zapytań i wysłanie ich do bazy?

Bo ponieważ że przede wszystkim wysyłasz zapytanie a nie request.
Jaka jest różnica?

  1. Jeśli wysyłasz zapytanie SQL (przez BDE/ dbExpress / JDBC czy SQL-over-AJAX) to takie zapytanie bardzo łatwo zmodyfikować w locie. O ile nie masz szyfrowania między aplikacją a siecią, to dziecko to potrafi.

Taki SQL może:

  • mieć zmieniony warunek (np. zamiast "= 5" to "!= 5")
  • dodany dodatkowy kod (np. "; drop table tabela")
  • pobrane inne niż potrzeba pola (np. zamiast "current_balance" to "initial_balance")
    itd.

A po stronie serwera masz ograniczone możliwości biznesowej czy technicznej walidacji takiego zapytania. No chyba że się okopiesz i dodasz pełno czeków, unikalnych indeksów, zmaterializowanych widoków i foreign keyów tyle że przez to Ci siądzie baza wydajnościowo.

  1. Natomiast jeśli wysyłasz request do API o treści:

"getRowById(5)" to tu już masz mniejsze pole do popisu i fikania. Możesz sobie próbować przesłać zamiast 5 coś innego, ale jeśli serwer ma przynajmniej poprawną walidację to coś innego niż liczbę odrzuci. A nazwy endpointów możesz sobie wymyślać, ale Ci to nic nie da, bo dostaniesz się co najwyżej do takich które zostały intencjonalnie upublicznione.

1
Juhas napisał(a):

Cześć, załóżmy, że mam aplikację desktopową, która pobiera jakieś dane z bazy danych znajdującej się w necie. Generalnie jest tylko jedna taka baza na świecie.

Baza danych może być jedna ale wiele aplikacji klienckich na różne platformy. PC, telefony, tablety lodówki, żelazka itp. A żelazko może nie pogadać bezpośrednio z bazą danych.
Poza tym, często aplikacja, żeby coś wyświetlić musi coś pobrać z kilku tabel i przetworzyć. API może to zrobić za klienta i wysłać gotowy model dla widoku i może to być znacznie wydajniejsze w komunikacji jak potrzebujesz policzyć pogrupowane sumy z tysięcy rekordów ale to zależy od aplikacji.

1
abrakadaber napisał(a):

Należy mieć też na uwadze, że wszelkie api wiążą się z narzutem czasowym oraz ilościowym przesyłanych danych. To pierwsze spowodowane jest tym, że oprócz odpytania bazy serwis musi jeszcze te dane zapakować w coś (JSON, XML, ...) i dopiero tak odesłać. Drugie związane jest z formatem w jakim aktualnie większość danych można dostać (bardzo popularne są formaty otwartym tekstem - JSON, XML (gdzie zdarza się, że metadane zajmują więcej niż same dane). Oczywiście można to pakować (bo dane tekstowe się dość dobrze pakują) ale to znowu dodatkowy czas.

Ja bym akurat twierdził odwrotnie. Api może odciążać bazę, bo w warstwie api żądania do bazy mogą być odpowiednio kolejkowane i można generalnie dużo łatwiej przygotować jakąkolwiek ochronę przed floodowaniem. Tymczasem, gdy wystawisz połączenie do bazy bezpośrednio na świat, ryzykujesz znacznie więcej.
Kolejna zaleta api, możesz sobie zmienić baze na inną, pod spodem, a api wystawione na zewnątrz wciąż pozostanie bez zmian. Możesz szybko przełączyć api na inny serwer bazy, a nikt tego nie zauważy. Ja nie widzę żadnych zalet wystawiania dostępu do bazy bezpośrednio na świat. Prawidłowe działanie, to api rest-owe, dobrze znane i obsługiwane wszędzie, od php, przez aplikacje desktopowe, a na mobilnych kończąc.

0
vpiotr napisał(a):

Bo ponieważ że przede wszystkim wysyłasz zapytanie a nie request.
Jaka jest różnica?

  1. Jeśli wysyłasz zapytanie SQL (przez BDE/ dbExpress / JDBC czy SQL-over-AJAX) to takie zapytanie bardzo łatwo zmodyfikować w locie. O ile nie masz szyfrowania między aplikacją a siecią, to dziecko to potrafi.

A z ciekawości, jak to się robi? Jakiś sniffer, czy coś?
Co do szyfrowania, aplikacja w takim przypadku powinna obsługiwać SSL? I po stronie serwera też powinien być SSL? Czy chodzi tu o inny rodzaj szyfrowania?

Taki SQL może:

  • mieć zmieniony warunek (np. zamiast "= 5" to "!= 5")
  • dodany dodatkowy kod (np. "; drop table tabela")
  • pobrane inne niż potrzeba pola (np. zamiast "current_balance" to "initial_balance")
    itd.
    (...)
  1. Natomiast jeśli wysyłasz request do API o treści:

"getRowById(5)" to tu już masz mniejsze pole do popisu i fikania. Możesz sobie próbować przesłać zamiast 5 coś innego, ale jeśli serwer ma przynajmniej poprawną walidację to coś innego niż liczbę odrzuci. A nazwy endpointów możesz sobie wymyślać, ale Ci to nic nie da, bo dostaniesz się co najwyżej do takich które zostały intencjonalnie upublicznione.

No dobra, ale równie dobrze mogę zamiast 5 wrzucić 3 w taki sam sposób jak w SQL i też dostane inne dane.

0
Krzywy Programista napisał(a):

Ja bym akurat twierdził odwrotnie. Api może odciążać bazę, bo w warstwie api żądania do bazy mogą być odpowiednio kolejkowane i można generalnie dużo łatwiej przygotować jakąkolwiek ochronę przed floodowaniem. Tymczasem, gdy wystawisz połączenie do bazy bezpośrednio na świat, ryzykujesz znacznie więcej.

Przecież to się ma nijak do tego co ja napisałem. Nie pisałem nic o bezpieczeństwie! a jedynie o narzucie czasowym i przede wszystkim ilości przesyłanych danych.

Kolejna zaleta api, możesz sobie zmienić baze na inną, pod spodem, a api wystawione na zewnątrz wciąż pozostanie bez zmian. Możesz szybko przełączyć api na inny serwer bazy, a nikt tego nie zauważy.

Jakbyś czytał cały wątek a nie tylko ostatni mój post to byś wiedział, że to napisałem jako pierwszą moją odpowiedź

0

Nawet jeśli ograniczysz swoim użytkownikom dostęp tylko do SELECT i do tego tylko na jednej tabeli, to i tak można rozłożyć twoją bazę danych. Przykłady z Postgresa:

SELECT pg_sleep(5);
SELECT generate_series(1, 1000000);

Dla MySQL też jest:

SELECT * FROM benchmark(1000000, md5(1));

czy

SELECT sleep(5);

Zakładam, że dla "dużych komercyjnych DB" również istnieją odpowiedniki tych zapytań.

1
Juhas napisał(a):
vpiotr napisał(a):

Bo ponieważ że przede wszystkim wysyłasz zapytanie a nie request.
Jaka jest różnica?

  1. Jeśli wysyłasz zapytanie SQL (przez BDE/ dbExpress / JDBC czy SQL-over-AJAX) to takie zapytanie bardzo łatwo zmodyfikować w locie. O ile nie masz szyfrowania między aplikacją a siecią, to dziecko to potrafi.

A z ciekawości, jak to się robi? Jakiś sniffer, czy coś?

Z tego co wiem to najprościej zrobić przez proxy.
Ale są pewnie bardziej złożone sposoby.

https://www.owasp.org/index.php/Man-in-the-middle_attack
https://www.veracode.com/security/man-middle-attack
z SSL też trzeba uważać: https://technet.microsoft.com/en-us/library/ms189067(v=sql.105).aspx

0

No dobrze, SQLa można przechwycić i zmienić. Ale skoro tak, to to samo można zrobić z wywołaniem API. Więc jaka jest różnica jeśli wyślę SQL: DELETE from TABELA WHERE ID = 5, a attacker zmieni na DELETE FROM TABELA, a jak wyślę wywołanie api:
DelTabela(5), a attacker zmieni to na serię DelTabela(1), DelTabela(2), DelTabela(3) itd.

Gdzie tu jest różnica?

5

@Juhas:

  1. Nie trzeba nic hackować. Wystarczy ci jakis wireshark i już widzisz co aplikacja wysyła i gdzie
  2. API ma to do siebie że nie pozwala na dowolne zapytania i moze walidować po stronie serwera poprawność zapytań przed ich wykonaniem. Możesz np. sprawdzać czy user ma prawo usuwać to co zlecił albo czy nie usuwa za dużo.
  3. Można powyłączać sobie sleep czy benchmark, ale mimo to zabić bazę można prostym selectem z kilkoma cross joinami a na koniec sortowaniem (bo przy dużej ilosci danych ram sie skończy i baza zacznie sortować za pomocą dysku).
0

Ale w taki sposób za pomocą API musiałbym walidować każde wywołanie, zgadza się? W stylu "Czy może zrobić operację DelTabela z argumentem 1", "Czy może zrobić operację DelTabela z argumentem 2..." itd. Zgadza się? Czy tak się robi?

1

@Juhas:

Skoro użytkownik może usunąć tylko wpis o id = 5 to w ogóle nie powinno być tam paraametru tylko DeleteMyData(), więc nie zmieni nic. I tutaj właśnie jest potrzebne API, bo ono będzie wiedzieć, że w DeleteMyData() chodzi o usunięcie rekordów z id = 5.

EDIT:

Nie, tego się tak nie robi. Wysyłasz do API zapytanie (przyjmijmy, że to HTTP REST) DELETE /my/data i masz parę opcji:

  • Operacja się powiodła i zwracamy nic: HTTP 204 No Content
  • Operacja się nie powiodła, bo nie masz uprawnień: HTTP 403 Forbidden
  • Operacja się nie powiodła, bo nie jesteś zalogowany: HTTP 401 Unauthorized
  • Operacja się nie powiodła, bo system nie obsługuje usuwania danych: HTTP 405 Method not allowed
  • etc.

Więc Twoja aplikacja nic nie sprawdza po swojej stronie, a jedynie wysyła zapytanie: "zrób to" i API odpowiada: "udało się" lub "nie zrobię tego".

0

OK, a na jakiej podstawie API ma zadecydować, które dane są moje? Przecież to bez sensu. Wszystkie dane mogą być publiczne. I co wtedy? Tu oczywiście nie mówię o aktualizacji, tylko o pobieraniu. Ale przy okazji aktualizacji to też na jakiejś podstawie API musi stwierdzić, że mogę pracować na tych rekordach, a na tamtych już nie. Jak to powinno być realizowane?

Tyle tych pytań, bo nigdy nie robiłem WebAPI, a przymierzam się.

1

a na jakiej podstawie API ma zadecydować, które dane są moje?

To zależy od danych, typu aplikacji, etc. Przykładowo jak mam aplikację dla szpitali i tam są wpisy w kartę pacjenta, to moje są te, których jestem autorem (brzmi dość oczywiście). Więc w DB będę miał klucz obcy author_id i jeśli jest on równy mojemu id to znaczy, że są "moje". Wyświetlić dalej mogę wszystkie dla danego pacjenta, ale edytować mogę swoje.

Wszystkie dane mogą być publiczne. I co wtedy?

Widoczność danych a możliwość ich edycji to 2 różne rzeczy, ale to dalej ty decydujesz w API czy dana osoba może wyświetlić te dane czy nie.

Ale przy okazji aktualizacji to też na jakiejś podstawie API musi stwierdzić, że mogę pracować na tych rekordach, a na tamtych już nie.

Przecież będziesz miał jakiś system logowania zapewne? Tan tworzysz sesje lub inną formę identyfikacji użytkownika (tokeny, sesje, ciastka, etc.) i następnie na podstawie tego wiesz kto wysłał zapytanie i jakie ma uprawnienia.

0

@Juhas lekcja na dziś: autoryzacja i autentykacja.

0

No dobra, to teraz tak. Mam API KEY i jakiś APi secret. Użytkownik wkleja to sobie do programu i łączy się z serwerem. Serwer sobie sprawdza - jest git. Wszystko się zgadza. I co się dzieje dalej?

Rozumiem, że bez sensu byłoby informowanie użytkownika i o jego ID, więc pewnie cała dalsza komunikacja powinna iść z użyciem API Key. Ale skoro tak, to użytkownik równie dobrze może zmienić sobie API Key tak samo jak może zmienić zapytanie wysyłane do bazy.

Więc jak to powinno być poprawnie? Czy API Key powinno być na tyle zabezpieczone (zajebiście losowy ciąg z jakąś sumą kontrolną na końcu), żeby nie można było samemu utworzyć? Wtedy obniżamy prawdopodobieństwo, że taki użytkownik nagmera w czyichś danych.

Czy tak to powinno wyglądać?

0

No raczej. Klucz to losowy ciąg ważny przez jakiś ograniczony czas. Są już do tego gotowe rozwiązania, więc nie warto implementować samemu.

0

Jak to "ważny przez jakiś czas?" Jak używam Google Analytics, to tam klucz się nie zmienia. Używam jeszcze kilku serwisów i klucz też jest ten sam. Cały czas już od prawie roku.

1 użytkowników online, w tym zalogowanych: 0, gości: 1