Poor Http / Publisher : samonosná cookie

Poor Http a Poor Publisher implementují PoorSession. Jde o samonosnou cookie, do které může webová aplikace ukládat různá data. Tento mechanismus je sice omezený svou velikostí, nicméně k běžnému testování, zda je uživatel přihlášen nebo si zapnul nějaký speciální režim, vyhovuje dostatečně.

3.8.2011 00:00 | Ondřej Tůma | přečteno 8118×

„Samonosná cookie – je taková cookie, ve které jsou uložena všechna data aplikace a není třeba dalšího úložiště dat. Proto jsou tyto data kryptována, aby je uživatel nemohl nijak podvrhnout.“

Celá implementace spočívá v triku, kdy jsou data serializována, zaheslována, zkomprimována a nakonec převedena na byte64. Opačnými kroky jsou pak data získána zpět. V datech, ve slovníku, je také uvedena expirace session, takže i v případě, že útočník podvrhne stará data, aplikace je zahodí. Klíč kterým jsou data kryptována a následně de-kryptována je samozřejmě konfigurovatelný, ve výchozím stavu jde o identifikátor ze svn commitu příslušného souboru (informace o poslední úpravě souboru na SF.net).

Vytvoření a zrušení session

Následující funkce doLogin vytvoří novou session. Do jejího datového slovníku uloží id uživatele, časovou známku a pokud vstupní proměnná ip není nastavena na None, False, 0 nebo prázdný string, nastaví také ip adresu klientské aplikace. Následně nastaví hlavičky odpovědi, čímž zajistí, že se tato session dostane do klientské aplikace – browseru. Ve skutečnosti nevytvoří novou session, ale použije již existující session, kterou poslal browser serveru společne s požadavkem.


def doLogin(req, id, ip = None):
    cookie = PoorSession(req)
    cookie.data["id"] = id
    cookie.data["timestamp"] = int(time())
    if ip:
        cookie.data["ip"] = req.get_remote_host()
    cookie.header(req, req.headers_out)
    req.log_error("Login cookie was be set.", LOG_INFO)
#enddef

Funkce doLogout naopak rovnou předpokládá, že společně s požadavkem poslal browser i cookie ve které je uloženo id přihlášeného uživatele. Pokud tomu tak je, zavolá metodu destroy, která vymaže data, a nastaví session záporný čas. Nakonec opět zapíše vytvořenou cookie do hlavičky odpovědi. Díky tomu pak browser získá informaci že cookie může smazat. I kdyby tomu tak nebylo a browser ji sám posílal dál, nebude již obsahovat žádná data.


def doLogout(req):
cookie = PoorSession(req)
if not "id" in cookie.data:
req.log_error("Login cookie not found.", LOG_INFO)
return

cookie.destroy()
cookie.header(req, req.headers_out)
req.log_error("Login cookie was be destroyed (Logout)", LOG_INFO)
#enddef

Práce se session

Naproti tomu, se funkce checkLogin pokouší získat Session z cookie. Pokud je v pořádku a obsahuje id přihlášeného uživatele a pokud má nastavenou správnou ip adresu klienta, vrátí tuto session. V opačném případě zaloguje chybu, případně smaže session a vrátí None. Volitelným parametrem je redirectUri, ten pokud je nastaven a funkce vyhodnotí situaci záporně, dojde k přesměrování na jinou adresu. Pod takovou adresou, pak může server vracet chybovou hlášku o nutnosti přihlášení, přihlašovací formulář atd.


def checkLogin(req, redirectUri = None):
cookie = PoorSession(req)
if not "id" in cookie.data:
req.log_error("Login cookie not found.", LOG_INFO)
if redirectUri:
redirect(req, redirectUri)
return None

if "ip" in cookie.data and cookie.data["ip"] != req.get_remote_host():
cookie.destroy()
cookie.header(req, req.headers_out)
req.log_error("Login cookie was be destroyed (invalid IP address)",
LOG_INFO)
if redirectUri:
redirect(req, redirectUri)
return None

return cookie
#enddef
Použití těchto funkcí může být například stejné, jako je v následující ukázce kódu. Důležité je, že aby se data uložená v session neztratila, je nutné je zapsat do hlavičky odpovědi. Browser pak dle nastavení tuto cookie smaže, nebo s dalším požadavkem pošle na server.


# kontrola zda je uživatel přihlášen
session = checkLogin(req, "/login")

# inkrementování hodnoty uložené v session
session.data['count'] = session.data.get('count', 0) + 1

# uložení session do klientského browseru
session.header(req, req.headers_out)


# registrace nové session (přihlášení)
doLogin(req, 'x', True)
http.redirect(req, "/")

# odstranění session (odlášení)
doLogout(req)
http.redirect(req, "/login")

Veškerá práce se session je jasně limitovávána prací s cookie. To znamená, že pokud nedojde k jejímu přepsání ze strany serveru, klient ji bude vždy posílat automaticky, dokud nevyprší. V případě testování přihlášení, kdy nedochází k žádnému zápisu, resp. změně, není třeba ji s každou odpovědí posílat, tedy generovat hlavičky. Navíc, třída PoorSession vytváří novou session jak v případě, kdy server s požadavkem žádnou cookie nedostal, tak i v případě kdy došlo k její expiraci nebo z nějakého důvodu není čitelná.

Expirace je implicitně nastavena na 0, to znamená, že k jejímu zneplatnění dojde až se rozhodne browser, to znamená při zavření. Tato hodnota je pro kontrolu uvedena také v datové části session pokud je nastavena. Je tedy silně nedoporučováno nepoužívat klíč „expires“ v session slovníku data, protože při nastavení bude tato hodnota přepsána.

Dalším rozšířením by mohlo být zápis na filesystém serveru, nebo do nějakého dalšího úložiště, například databáze. V takovém případě je ale nutné do systému zahrnout nějaký systém na mazání již zastaralých session. Protože ty jsou předem nespecifikovatelně trvalé a jsou ukládány na straně klienta, je použití právě samonosné cookie elmi výhodné a velmi často dostačující.
Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1855