27 kwietnia 2009

Z rozdziału 14. o bezpieczeństwie w Grails z DGG2

W rozdziale 14. Security w "The Definitive Guide to Grails, Second Edition" autorzy przedstawiają dostępne w Grails mechanizmy wspierania tworzenia aplikacji webowych bezpiecznie(j). Znajdziemy w nim przedstawienie sposobów obsługi uwierzytelniania (ang. authentication), czyli stwierdzenia z kim rozmawiamy oraz autoryzacji (ang. authorization), czyli przypisania właściwych praw w aplikacji.

Jak to ujęli autorzy książki Graeme i Jeff:

"Of course, there is no point in reinventing the wheel, so we'll cover how you can use one of the security frameworks already available".

Istnieje wiele ataków na aplikacje webowe, ale najbardziej znanymi są SQL Injection oraz cross-site scripting (XSS). Pierwszy polega na przekazaniu parametrów żądania, w taki sposób, aby nieumiejętne tworzenie zapytań bazodanowych w aplikacji zwróciło więcej danych niż planowano, lub co gorsza, zmodyfikowało je w nieautoryzowany sposób. Drugi, XSS, to takie przekazanie parametrów żądania w postaci skryptu JavaScript, aby umieszczenie ich na stronie (przez wyświetlenie wprowadzonych przez użytkownika danych) albo w dziennikach zdarzeń (sam JavaScript w logach nie jest niczym strasznym, ale wyświetlenie go na stronie, to potencjalnie wykonanie go) spowodowało nieplanowane działanie aplikacji (najczęściej wykonanie kodu, który wymagałby specjalnych uprawnień do jego wykonania).

Rozwiązaniem przed SQL/HQL Injection to wykorzystanie mechanizmu przekazywania parametrów do zapytań HQL (podobnie jak w JPA) zamiast składania zapytania przez konkatenację tekstu, np. (Listing 14-2):
 Album.findAll("from Album as a where a.title = ?", [params.title])
Album.findAll("from Album as a where a.title = :title", [title:params.title])
Album.withCriteria {
eq('title', params.title)
}
Album.findAllByTitle(params.title)
Wstrzeliwanie skryptu Groovy (ang. Groovy injection) to kolejny rodzaj ataku na aplikację udostępniającą możliwość wykonywania skryptów Groovy bez ograniczenia dostępnych funkcji przez mechanizm bezpieczeństwa w Javie, np. (Listing 14-3):
 def execute = {
new GroovyShell().execute(params.script)
}
W szczególności możnaby przekazać "System.exit(1)" do wykonania.

Ciekawostką w kontekście bezpieczeństwa i Grails jest wskazanie "an attacker" jako "she" (!) - patrz str. 409:

The technique (XSS) involves injecting JavaScript written by the attacker into the page. An attacker able to control the JavaScript on your site is an incredibly dangerous scenario. She could do all manner of things, from stealing a user's cookie to changing a login form so that it sends requests to another server that captures usernames and passwords.

Tym samym uzmysłowiłem sobie, ile to razy prezentowałem aplikację, która była podatna na ataki XSS - chciażby wyświetlanie danych użytkownika. Jeśli zamiast poprawnego imienia wpiszemy skrypt typu:
 <script type="text/javascript>alert('hello')</script>
To niegroźne z pozoru Witaj <span id="imie">${session?.user?.firstName}</span>! spowoduje wykonanie skryptu. Grails umożliwa ochronę przed tego typu atakiem za pomocą:
 grails.views.default.codec="html"
w grails-app/conf/Config.groovy, który zamieni wszystkie specjalne znaki w dowolnym wyrażeniu ${...} w GSP na ich odpowiedniki jako encje HTML (ang. HTML entities), np. dla < będzie to &lt;. Dotyczy to wszystkich stron GSP, bez względu na tworzony rodzaj strony - HTML, PDF, JSON, itp. Zawężenie do pojedynczej strony GSP, to:
 <%@ defaultCodec="html" %>
Możemy również zamienić jedynie symbole dla pojedynczej zmiennej z metodą encodeAsHTML(), tj. (Listing 14-4):
 ${session?.user?.firstName?.encodeAsHTML()}
Możemy również skorzystać ze znacznika <g:textField>, który wykona za nas całą robotę i zadba o nielegalne symbole (a zastanawiałem się ostatnio, do czego miałbym wykorzystać ten znacznik, skoro mogę bezpośrednio wypisać tekst - teraz już jest jasne).

Podobna sytuacja będzie z adresami URL i XSS, tj. korzystajmy z <g:link> do tworzenia adresów URL, zamiast (Listing 14-5):
 <a href="/gtunes/albums?title=${params.title}">Show Album</a>
Jeśli jednak musimy, korzystajmy z encodeAsURL(), tj. (Listing 14-6):
 <a href="/gtunes/albums?title=${params.title?.encodeAsURL()}">Show Album</a>
Uwaga na DoS (ang. Denial of Service), który widzieliśmy już przy wykonaniu skryptu System.exit(1). Możemy paść jego ofiarą również w sytuacji wykonania zbyt obciążającego system zapytania, np. (Listing 14-7):
 def list = {
if (!params.max) params.max = 10
[albumList: Album.list(params)]
}
Wystarczy wykonać powyższe domknięcie z parametrem "max" ustawionym na 1000000 i system...nie pozbiera się przez jakiś czas. A wystarczy (Listing 14-8):
 def list = {
params.max = Math.min(params.max?.toInteger() ?: 0, 100)
[albumList: Album.list(params)]
}
Niby niewiele potrzeba, a późniejsze utrzymywanie systemu będzie znacznie przyjemniejsze. Zostaliście ostrzeżeni! :)

Uważajmy również na automatyczne przypisywanie wartości z żądania do atrybutów klas dziedzinowych. Wystarczy odpowiednio spreparować żądanie, aby (Listing 14-9):
 def update = {
def user = User.get(params.id)
user.properties = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
ustawiło egzemplarz użytkownika z danymi, które nie powinny zostać zmienione! Zamiast tego, skorzystajmy z zawężonego przypisywania danych (Listing 14-10):
 def update = {
def user = User.get(params.id)
user.properties['firstName', 'lastName', 'phoneNumber', 'password'] = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
Ponownie niewiele potrzeba, aby system był bezpieczniejszy (chociaż świadomość istnienia tego typu "kwiatków" nie należy do powszechnej wiedzy...niestety).

Metody encodeAsHTML() oraz encodeAsURL() są dostarczane przez kodeki w Grails. Zgodnie z konwencją Grails, klasa kodeka kończy się na "Codec" i posiada dwie metody statyczne static encode(target) oraz static decode(target). Zapisujemy klasę w katalogu grails-app/utils i od tej pory wykonanie metod encodeAs[nazwa-kodeka]() oraz decode[nazwa-kodeka]() sprowadzi się do wykonania, odpowiednio, metody encode i decode. W książce znajdziemy przykład kodeka do kodowania i dekodowania danych algorytmem Blowfish, np.:
 def encrypted = "This is some secret info".encodeAsBlowfish()
def unencrypted = encrypted.decodeBlowfish()
Następnie wymienia się dostępne "gotowce" do wdrożenia mechanizmów uwierzytelnienia i autoryzacji - Spring Security (dawne Acegi), wtyczki Authentication oraz JSecurity. Ich implementacja opiera się na mechaniźmie filtrów w Grails (nie mylić z filtrami z Java Servlets, aczkolwiek można je skonfigurować podobnie). Filtry przypominają aspekty z AOP, gdzie tworzymy klasę, której metody wykonywane są przed (before) i po (after) akcjach kontrolerów. Tworzymy filtr przez utworzenie klasy w katalogu grails-app/conf, której nazwa kończy się na "Filters". W ramach klasy filtra definiujemy statyczną zmienną właściwość filters z definicjami, gdzie i jaki filtr "przyłożyć" kontrolerom w konwencji:
 class [NazwaKlasyFiltrów]Filters {
static filters = {
[nazwa-filtra1](controller:["*"|"nazwa-kontrolera"], action:["*"|"nazwa-akcji"]) {
before = {
...
}
after = { model ->
...
}
}
[nazwa-filtra2]...
}
}
Możemy użyć symbolu '*' (gwiazdka), aby wskazać wszystkie kontrolery i/lub akcje. De facto, możemy użyć dowolnego wyrażenia regularnego do ich określenia, np. secure(controller:"(admin|secure)", action="*"). Zamiast parametrów controller i action możemy uzyć parametru uri, określającego adres URL, którego dotyczy filtr.

Jeśli domknięcie w before lub after zwróci false oznacza to, że nie będzie wykonana przesłonięta akcja kontrolera.

Domknięcie after wykonywane jest po akcji kontrolera, ale przed wyświetleniem strony GSP. Jeśli jednak chcielibyśmy wykonać filtr po stronie GSP korzystamy z afterView, np. (Listing 14-14):
 after = {
request.currentTime = System.currentTimeMillis()
}
afterView = {
log.debug "View took ${System.currentTimeMillis() - request.currentTime}ms"
}
Zakończenie rozdziału to przedstawienie instalacji i konfiguracji wtyczki JSecurity oraz jej wdrożenie do aplikacji GTunes do stworzenia sekcji "My Music" z możliwością odsłuchania utworów (za pomocą QuickTime opakowanego w tworzony znacznik GSP - media:player).