Główna » Java, Polecane

Cross-site request forgery

29 March 2009 Brak komentarzy

BezpieczeństwoCross-site request (Wikipedia: PL i EN) forgery jest to bardzo interesujący sposób ataku na aplikację internetową. Najciekawsze w nim jest, że sam atakowany użytkownik nie musi praktycznie wykonywać żadnej czynności. Pierwsze wzmianki o tej “metodzie” pochodzą z 1988 roku z artykułu Norm’a Hardy.  W 2008 roku ofiarami tego typu ataków padły serwisy takich gigantów jak Amazon czy eBay.

Atak polega na przesłaniu do użytkownika kodu, który wykonany po stronie ich klienta przesyła spreparowane żądania do innego serwera, wykorzystując istniejącą, uwierzytelnioną sesję. Przykładem może być przesłanie do osoby na czacie kodu HTML wyświetlającego obrazek, którego atrybut src wskazuje przykładowo http://www.mojbank.pl/transfer.htm?dla=atakujacy&kwota=10000. W przypadku gdy atakowana osoba w danym momencie w tej samej przeglądarce w której otworzy się obrazek posiada uwierzytelnioną sesję do banku, przelew taki może zostać wykonany (oczywiście to trywialny przykład – ale obrazuje dobrze jak działa CSRF).

Przy tworzeniu aplikacji internetowej warto już od samego początku o nim pamiętać. Późniejsze zabezpieczenie przed tego typu atakiem będzie znacznie kosztowniejsze, co szczególnie ważne – zabezpieczenie przed CSRF – to jedne z istotnych elementów certyfikacji PCI.

Oto opis przykładowego rozwiązania, używając Spring Framework:

Tworzymy klasę abstrakcyjnego formularza SimpleFormController (wszystkie formularze w naszej aplikacji będą po niej dziedziczyły, oczywiście dla innych typów formularzy, należy stworzyć odpowiednie klasy).
W klasie tej, gdy formularz jest wyświetlany, do sesji podpinamy unikatowy identyfikator, w momencie przesłania formularza, porównamy go z wartością przesłaną jako parametr zapytania.
Oto fragmenty kodu tej klasy:

[java]
public class AbstractSimpleFormController extends SimpleFormController {

private static final String REQUEST_ID = “request_id”;

protected final ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
validateRequest(request);
return onSubmitForm(request, response, (E) command, errors);
}

protected ModelAndView onSubmitForm(HttpServletRequest request, HttpServletResponse response, E form, BindException errors) throws Exception {
return super.onSubmit(request, response, form, errors);
}

protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
Map
map = new HashMap();
markRequestAndAddToModel(request, map);
return map;
}

private static void markRequestAndAddToModel(HttpServletRequest request, Map map) {
String requestId = IdGenerator.generateId();
request.getSession().setAttribute(REQUEST_ID, requestId);
map.put(“requestId”, requestId);
}

private static void validateRequest(HttpServletRequest request) {
String fromRequest = request.getParameter(REQUEST_ID);
String fromSession = (String) request.getSession().getAttribute(REQUEST_ID);
//noinspection CallToStringEquals
if (fromRequest == null || !fromRequest.equals(fromSession))
throw new FinPlanRuntimeException(“Request invalid”);
}
}
[/java]

Oczywiście, w miarę potrzeby, należy zmodyfikować inne przeładowane wersje metod.
W formularzu należy dodać następujący kod:

[xml]

“>
[/xml]

Oto przykładowy kod generujący unikatowy klucz zapytania:

[java]
public class IdGenerator {

public static String generateId() {
return UUID.randomUUID().toString();
}
}
[/java]

Rozwiązanie to ma pewną wadę, mianowicie, gdy użytkownik korzysta z kilku okien pracując w naszej aplikacji, odwołanie do nowego formularza nadpisze klucz, powodując problemy przy przesłaniu wcześnie otwartego.
By poprawić sytuację można używać różnych nazw dla klucza zapisywanego w sesji – ale to i tak nie sprawdzi się dla tego samego formularza otwartego w dwóch oknach. W większym projekcie można pokusić się o zastosowanie czegoś w rodzaju listy dozwolonych kluczy przechowywanych w  atrybucie sesji, po pomyślnym przesłaniu formularza należało by jedynie pamiętać o usunięciu go z listy. Mogło by to rodzić problemy z rosnącą listą dla niedokończonych formularzy, można by je jednak “czyścić” znając czas wygenerowania klucza – a w większości zastosowań na czas życia sesji takich martwych kluczy nie uzbierało by się najprawdopodobniej za wiele.

Zostaw odpowiedź!

Musisz się zalogować aby móc komentować.