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ě.
„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í.
Author:

Discussion

Your comment:

© 2019 Ondřej Tůma McBig. Ondřej Tůma | Based on: Morias | Twitter: mcbig_cz | RSS: articles, twitter