Zabezpieczenie API przed wywołaniem przez użytkownika

0

Powiedzmy mam aplikację typu QUIZ z typową komunikacją backend frontend. W jaki sposób obronić się przed sytuacją, że użytkownik zgaduje odpowiedni endpoint, zgaduje metodę autoryzacji (a user/password są analogiczne jak do logowania) i np. dodaje sobie punkty?

0

Musisz więcej powiedzieć o grze:

  1. Milionerzy, zła odpowiedź kończy grę - zła odpowiedź kończy grę niezależnie czy request poszedl od frontu czy z api
  2. Dostajesz punkty za dobre odpowiedzi, ale możesz próbować ile chcesz - można wysyłać cały czas odp 'A' wiec dodajesz limiter na to jak szybko może wygenerować się nowe pytanie. Dodatkowo możesz ukryć na stronie token po stronie frontu, który musi być przeslany razem z requestem. Nie będzie to twarde zabezpieczenie, ale utrudni życie atakującemu.

Punktu karne, żeby był offset na bruteforcea? Ban jeżeli ktoś odpowiada za czesto lub o 3:00 w nocy?

5

W jaki sposób może dodać sobie punkt? Odpowiedź sprawdzasz na frontach i wysyłasz na jakiś endpoint ile punktów ma sobie dodać? To bez sensu.
Powinieneś wysyłać na endpoint tylko odpowiedź oraz id pytania. Najlepiej to dla każdej nowej gry zrobić sesję, gdzie mamy powiązanie id sesji, id pytania i odpowiedź oraz id tabeli która to spina. Jak user wyśle odpowiedź to podaje Id tabeli, która spina to wszystko oraz odpowiedź. Jak odpowiedź została już udzielona to rzucasz błąd. Jak odpowiedź jest poprawna to dodajesz punkty. Wszystko robisz po stronie backendu, front odpowiada tylko za prezentację, bez logiki biznesowej

1

Tutaj tak naprawdę bardzo dużo zależy od technologii, ale jeśli masz pełnokrwisty front-end to tak jak pisał @Michalk001 - to backend powinien kontrolować przebieg gry, nie front.

1

Pierwotna idea była taka, że użytkownik zaczyna grę z losowymi pięcioma pytaniami i gdy odpowiedź jest poprawna (sprawdzane właśnie na froncie) to leci call do API dodający punkt. Teraz rozumiem, stworzę jakąś sesje gry z powiązanymi pytaniami, walidacja czy użytkownik odpowiada na aktywne pytanie związane z sesją, a na tworzenie sesji gry przykładowo jeszcze jakiś delay. Kompletnie zamysł był dziwny. Dzięki za nakierowanie

1

No to idealnie, to możesz sobie losować po stronie backendu, najlepiej jak skończy jedno pytanie, to losować kolejne. Możesz również ustalić poziom trudności danego pytania i później w losować odpowiednie pytanie. Sesja/Stan gry sieci sobie w bazie danych, połączone z userem i możesz mu zwrócić w łatwy sposób historię gier i wraz z pytaniami i odpowiedziami.

2
Lastie napisał(a):

Pierwotna idea była taka, że użytkownik zaczyna grę z losowymi pięcioma pytaniami i gdy odpowiedź jest poprawna (sprawdzane właśnie na froncie) to leci call do API dodający punkt.

To jest właśnie idealny przykład na to że nie wszystko można zrobić na froncie bezpiecznie. Frontend to strasznie niebezpieczne miejsce wystawione na hackerów. IMHO Jeśli chcesz żeby coś było zabezpieczone (bardziej) to zawsze musisz to robić na backendzie

1

W skrócie "nie da się" zrobić takiego zabezpieczenia po stronie frontendu. Użytkownik nic nie musi zgadywać, bo te rzeczy widać w przeglądarce. Wciśnij w chrome Ctrl+shift+i, wybierz zakładkę "network" i odśwież tę stronę, to sam zobaczysz.
Jeżeli zasady gry są takie, że masz odpowiedzieć na pytanie w ciągu jakiegoś czasu, to musisz wysłać na front jedynie informacje, które może widzieć użytkownik, czyli coś w kształcie:

{
  "id":1
  "question":"W którym roku była bitwa pod Grunwaldem",
  "answers":[
    {
      "id":1
      "answer":"1939"
    },
    {
      "id":2
      "answer":"966"
    },
    {
      "id":3
      "answer":"1410"
    }
    {
      "id":4
      "answer":"1815"
    }
  ]
}

Zanotować po stronie backendu który użytkownik, o której godzinie dostał które pytanie.

Następnie dostać w zwrotce:

{
  "answerId":3
}

Sprawdzić, czy użytkownik się zgadza, czy nie upłynęło za dużo czasu i czy odpowiedź na to pytanie ma id = 3. Jeżeli tak, to naliczyć mu punkty za tę odpowiedź. Wszystko to musi zostać zrobione po stronie backendu.

Jeżeli dowolna z tych odpowiedzialności zostanie przeniesiona na frontend, to będę sobie w stanie napykać tyle punktów na ile pozwoli wydajność twojego serwera.

Jeżeli np. nie będziesz sprawdzał jakie pytanie dostałem, to wyłapię zwrotkę, która nabiła punkty:

{
  "questionId":1
  "answerId":3
}

Napiszę pętlę, która będzie tę odpowiedź wysyłać na twój serwer i liczba punktów skoczy mi pod sufit.

Przy małym ruchu można wykorzystać mechanizm sesji trzymanych w pamięci/bazie i będzie ok. Problem pojawi się, jak z takiego układu:
screenshot-20230215213721.png
Będziesz musiał przejść do czegoś takiego:
screenshot-20230215213906.png
Czyli ruch po stronie backendu, jest obsługiwany przez wiele hostów równolegle i nie masz pewności, że instancja backendu, która wysłała pytanie, będzie tą samą instancją, która to pytanie weryfikuje.
Możliwości, które wtedy masz, to albo pozostawić sesję po stronie backendu i trzymać jej dane we wspólnej dla wszystkich bazie danych:
screenshot-20230215214136.png
Albo przenieść dane sesji, na stronę frontendową, ale w taki sposób, żeby użytkownik nie mógł ich zmienić po swojej stronie. Możliwym rozwiązaniem, jest przesyłanie danych w formie zaszyfrowanej, albo podpisanej. Przykładowo:

{
  "userId":"Wacek",
  "questionId":1,
  "validTo":123212345,
  "hash":1234-1234-1234-1234
}

Gdzie hash, to suma kontrolna wzbogacona o jakieś tajne, znane tylko po stronie backendu hasło. Te dane są przez front odsyłane do serwera razem z odpowiedzią na pytanie, sprawdzana jest ich integralność i w zależności od tego, czy została, lub nie została naruszona nabijamy użytkownikowi punkty, lub bana.

Jest to niższy poziom bezpieczeństwa, bo znalezienie tego hasła, to jedynie kwestia użytej mocy obliczeniowej i czasu, ale jeżeli ryzyko związane ze złamaniem tego algorytmu to jedynie wbicie się na pierwszą pozycję rankingu w milionerach wystarczy.

Wciąż jest dość oczywiste ryzyko ataku polegającego na tym, że:

  • pobieram pytanie

  • wysyłam wszystkie możliwe odpowiedzi, wraz z oryginalnymi danymi sesji, tak szybko jak mogę

    W tym przypadku jeżeli czas na odpowiedź to 1 minuta, jestem w stanie wysłać 100 requestów na sekundę, to nabiję sobie około 150 punktów, zamiast potencjalnego 1, nawet nie znając odpowiedzi na pytanie. Żeby temu przeciwdziałać, należy kontrolować czy w danym "rozdaniu" padła więcej niż jedna odpowiedź i akceptować jedynie pierwszą. Ale jak można to robić nie tracąc na wydajności (a przynajmniej na skalowalności), to już trochę za długi temat.

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