Kiedyś świat był prosty, dwie metody GET i POST były wystarczające. Do tego od biedy HEAD i OPTIONS. A teraz? Jakieś takie wynalazki typu REST wymagające metod typu PUT, DELETE. Normalnie zgroza! A teraz pytanie - czy da się w tym scenariuszu wykorzystać CSRF?
CSRF na PUT / DELETE
Odpowiedź - technicznie jest to możliwe, w końcu od czego mamy Cross-Origin Resource Sharing. Ale choć jest to możliwe, nie jest to do końca trywialne zadanie.
Po pierwsze musimy pamiętać o rozróżnieniu między simple requests i preflighted requests. W przypadku PUT / DELETE mamy oczywiście do czynienia z tym drugim przypadkiem, więc na tym się skupmy.
Roboczo jeszcze załóżmy, że przypadek, z którym mamy do czynienia nie jest zupełnie RESTful. W założeniu tej architektury jest jej bezstanowość:
The client–server communication is further constrained by no client context being stored on the server between requests. Each request from any client contains all the information necessary to service the request, and session state is held in the client.
Właśnie z tego powodu można często spotkać się z sytuacją, gdy do każdego żądania dodawane są dane uwierzytelniające (username/password), lub jakiś API key. Jeśli tak jest, atak CSRF nie ma w praktyce większego znaczenia, bo jeśli atakujący dysponuje tymi danymi, po co mu CSRF (scenariusz, gdy atakujący zna te dane, ale nie ma dostępu do API pozwolę sobie pominąć)? Niekiedy jednak można spotkać się z sytuacją gdy informacja o tożsamości użytkownika jest przechowywana w sesji, a więc CSRF może mieć sens. W celu jego wykorzystania trzeba tylko wysłać żądanie z metodą PUT/DELETE razem z informacjami sesji. Jak to zrobić?
Od pewnego czasu jest to proste, takie możliwości daje XMLHttpRequest z withCredentials. Jest tylko jedno "ale" - serwer musi współpracować, czyli ustawić odpowiednie wartości kilku nagłówków.
Istnieje kilka nagłówków istotnych w kontekście CORS:
- Access-Control-Allow-Origin
- Access-Control-Expose-Headers
- Access-Control-Max-Age
- Access-Control-Allow-Credentials
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
W naszym scenariuszu istotne są przede wszystkim te trzy:
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials
- Access-Control-Allow-Methods
Zakładając, że wszystkie trzy mają odpowiednie wartości, nasz CSRF na PUT / DELETE może się udać. Ale jakie wartości są "odpowiednie"?
Zaczynając od Access-Control-Allow-Methods - metody PUT / DELETE muszę być wymienione w zwracanym nagłówku, w przeciwnym wypadku otrzymamy taki błąd:
XMLHttpRequest cannot load http://victim.example.home/rest.php. Method PUT is not allowed by Access-Control-Allow-Methods.
Drugi nagłówek, Access-Control-Allow-Credentials, powinien być ustawiony na true, inaczej po preflight request otrzymamy:
XMLHttpRequest cannot load http://victim.example.home/rest.php. Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://attacker.example.home' is therefore not allowed access.
Zakładając, że do tego etapu mamy szczęście, prawdopodobnie zderzymy się ze ścianą w przypadku Access-Control-Allow-Origin:
http://victim.example.home/rest.php. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'http://attacker.example.home' is therefore not allowed access.
W tym wypadku możliwe jest co prawda użycie wildcard, ale tylko dla requestów gdzie withCredentials nie jest ustawione na true:
For requests without credentials, the server may specify "*" as a wildcard, thereby allowing any origin to access the resource.
Jeśli dysponujemy możliwością umieszczenia naszego kodu w miejscu wskazanym przez Access-Control-Allow-Origin, wówczas nasz CSRF jest możliwy. Trzeba jednak przyznać, że prawdopodobieństwo takiego zbiegu okoliczności nie jest zbyt duże.
Jaki stąd wniosek? Po pierwsze dobrze jest pamiętać o możliwościach dawanych przez Cross-Origin Resource Sharing i XMLHttpRequest, bo istnieją scenariusze, gdy "egzotyczne" przypadki Cross-Site Request Forgery są możliwe do wykorzystania. Z drugiej jednak strony sama możliwość przewidzenia zawartości requesta (w uproszczeniu - brak tokenu anti-csrf) nie daje automatycznie możliwości wykorzystania tego (potencjalnego) CSRF w praktyce.