# A simple sidejacking detector. # # The script raises an alarm whenever more than one client makes use of the # same cookie, where a user is defined a as (IP, user agent) pair. @load notice @load http-request @load http-reply module HTTP; export { redef enum Notice += { Sidejacking }; const cookie_expiration = 1 hr &redef; type cookie_info: record { url: pattern; # URL pattern matched against Host header. keys: set[string] &optional; # Cookie keys that define the user session. pat: pattern &optional; # Cookie keys pattern, instead of a set. }; # List of cookie information per service (taken from Firesheep handlers). const cookie_list: table[string] of cookie_info = { ["Amazon"] = [$url=/amazon.com/, $keys=set("x-main")], ["Basecamp"] = [$url=/basecamphq.com/, $keys=set("_basecamp_session", "session_token")], ["bit.ly"] = [$url=/bit.ly/, $keys=set("user")], ["Cisco"] = [$url=/cisco.com/, $keys=set("SMIDENTITY")], ["CNET"] = [$url=/cnet.com/, $keys=set("urs_sessionId")], ["Dropbox"] = [$url=/dropbox.com/, $keys=set("lid")], ["Enom"] = [$url=/enom.com/, $keys=set("OatmealCookie", "EmailAddress")], ["Evernote"] = [$url=/evernote.com/, $keys=set("auth")], ["Facebook"] = [$url=/facebook.com/, $keys=set("xs", "c_user", "sid")], ["Flickr"] = [$url=/flickr.com/, $keys=set("cookie_session")], ["Foursquare"] = [$url=/foursquare.com/, $keys=set("ext_id", "XSESSIONID")], ["GitHub"] = [$url=/github.com/, $keys=set("_github_ses")], ["Google"] = [$url=/google.com/, $keys=set("NID", "SID", "HSID", "PREF")], ["Gowalla"] = [$url=/gowalla.com/, $keys=set("__utma")], ["Hacker News"] = [$url=/news.ycombinator.com/, $keys=set("user")], ["Harvest"] = [$url=/harvestapp.com/, $keys=set("_enc_sess")], ["NY Times"] = [$url=/nytimes.com/, $keys=set("NYT-s", "nyt-d")], ["Pivotal Tracker"] = [$url=/pivotaltracker.com/, $keys=set("_myapp_session")], ["Slicehost"] = [$url=/manage.slicehost.com/, $keys=set("_coach_session_id")], ["tumblr"] = [$url=/tumblr.com/, $keys=set("pfp")], ["Twitter"] = [$url=/twitter.com/, $keys=set("_twitter_sess", "auth_token")], ["Yahoo"] = [$url=/yahoo.com/, $keys=set("T", "Y")], ["Yelp"] = [$url=/yelp.com/, $keys=set("__utma")], ["Windows Live"] = [$url=/live.com/, $keys=set("MSPProf", "MSPAuth", "RPSTAuth", "NAP")], ["Wordpress"] = [$url=/wordpress.com/, $pat=/wordpress_[0-9a-fA-F]+/] } &redef; } # Map cookies to users, who are defined as a (address, user-agent) pair. type cookie_map: table[string] of set[addr,string] ; global evict_reported: function(map: cookie_map, cookie: string) : interval; global cookies: cookie_map &write_expire = cookie_expiration &expire_func=evict_reported; # Hijacked sessions that have already been reported. global hijacking_reported: set[string]; # Once a cookie entry expires, this function evicts the corresponding entry # from the set of reported cookies. function evict_reported(map: cookie_map, cookie: string) : interval { delete hijacking_reported[cookie]; return 0 secs; } # Create a unique user session identifier based on a pattern of cookie keys. function sessionize(cookie: string, info: cookie_info) : string { local id = ""; local fields = split(cookie, /; /); if (info?$pat) { for (i in fields) { local s = split1(fields[i], /=/); if (s[1] == info$pat) id += s[2]; } } if (info?$keys) { local matches: set[string]; matches = set(); for (i in fields) { s = split1(fields[i], /=/); if (s[1] in info$keys) add matches[s[2]]; } if (|matches| == |info$keys|) for (m in matches) id += m; } return id; } event http_all_headers(c: connection, is_orig: bool, hlist: mime_header_list) { if (! is_orig) return; local cookie = ""; local ua = ""; local host = ""; for (i in hlist) { local hdr = hlist[i]$name; local value = hlist[i]$value; if (hdr == "COOKIE") cookie = value; else if (hdr == "USER-AGENT") ua = value; else if (hdr == "HOST") host = to_lower(value); } if (cookie == "") return; local id = ""; local desc = ""; if (host != "") for (k in cookie_list) { local info = cookie_list[k]; if (info$url in host) { id = sessionize(cookie, info); desc = k; break; } } if (id == "") id = cookie; if (id !in cookies) cookies[id] = set() &mergeable; local client = c$id$orig_h; add cookies[id][client, ua]; if (|cookies[id]| <= 1) return; if (id !in hijacking_reported) { local s = lookup_http_request_stream(c); desc = (desc == "" ? "" : fmt("%s ", desc)); NOTICE([$note=Sidejacking, $src=client, $msg=fmt("%ssession hijacked by %s (%d users/cookie)", desc, client, |cookies[id]|)]); add hijacking_reported[id]; } }