Default | Coy | Dark | Funky | Okaidia | Solarized | Twilight
In this example, line numbering is enabled.
module HTTP;
export {
redef enum Notice::Type += {
## Cookie reuse by a different user agent
SessionCookieReuse,
## Cookie reuse by a roaming user.
SessionCookieRoamed,
## Cookie reuse by an attacker.
Sidejacking
};
## Control how to define a user. If the flag is set, a user is defined
## solely by its IP address and otherwise defined by the (IP, user agent)
## pair. It can make sense to deactive this flag in a deployment upstream
## of a NAT. However, false positivies can then arise when the same user
## sends the same session cookie from multiple user agents.
const user_is_ip = T &redef;
## Whether to keep track of multiple IP addresses for the same host. This
## can reduce false positives for roaming clients that leave and join the
## network under a new IP address, yet use the same session within the
## cookie expiration interval. It makes only sense to set this flag when
## actually sees DHCP or ARP traffic.
const use_aliasing = F &redef;
## Whether to restrict the analysis only to the known services listed
## below.
const known_services_only = T &redef;
## Time after which a seen cookie is forgotten.
const cookie_expiration = 1 hr &redef;
## Describes the cookie information of a web service, such as Twitter.
type ServiceInfo: record
{
desc: string; # Service description.
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.
};
# We track the cookie inside the HTTP state of the connection.
redef record Info += {
cookie: string &optional;
};
## Known session cookie definitions (from Firesheep handlers).
const services: vector of ServiceInfo = {
[$desc="AKF Demo", $url=/verify.akfdemo.com/, $keys=set("session")],
[$desc="Amazon", $url=/amazon.com/, $keys=set("x-main")],
[$desc="Basecamp", $url=/basecamphq.com/,
$keys=set("_basecamp_session", "session_token")],
[$desc="bit.ly", $url=/bit.ly/, $keys=set("user")],
[$desc="Cisco", $url=/cisco.com/, $keys=set("SMIDENTITY")],
[$desc="CNET", $url=/cnet.com/, $keys=set("urs_sessionId")],
[$desc="Enom", $url=/enom.com/,
$keys=set("OatmealCookie", "EmailAddress")],
[$desc="Evernote", $url=/evernote.com/, $keys=set("auth")],
[$desc="Facebook", $url=/facebook.com/,
$keys=set("datr", "c_user", "lu", "sct")],
[$desc="Fiverr", $url=/fiverr.com/, $keys=set("_fiverr_session")],
[$desc="Flickr", $url=/flickr.com/, $keys=set("cookie_session")],
[$desc="Foursquare", $url=/foursquare.com/,
$keys=set("ext_id", "XSESSIONID")],
[$desc="Google", $url=/google.com/,
$keys=set("NID", "SID", "HSID", "PREF")],
[$desc="Gowalla", $url=/gowalla.com/, $keys=set("__utma")],
[$desc="Hacker News", $url=/news.ycombinator.com/, $keys=set("user")],
[$desc="Harvest", $url=/harvestapp.com/, $keys=set("_harvest_sess")],
[$desc="LinkedIn", $url=/linkedin.com/, $keys=set("bcookie")],
[$desc="NY Times", $url=/nytimes.com/, $keys=set("NYT-s", "nyt-d")],
[$desc="Pivotal Tracker", $url=/pivotaltracker.com\/dashboard/,
$keys=set("tracker_session")],
[$desc="Posterous", $url=/.*/, $keys=set("_sharebymail_session_id")],
[$desc="Reddit", $url=/reddit.com/, $keys=set("reddit_session")],
[$desc="ShutterStock", $url=/shutterstock.com/, $keys=set("ssssidd")],
[$desc="StackOverflow", $url=/stackoverflow.com/,
$keys=set("usr", "gauthed")],
[$desc="tumblr", $url=/tumblr.com/, $keys=set("pfp")],
[$desc="Twitter", $url=/twitter.com/,
$keys=set("_twitter_sess", "auth_token")],
[$desc="Vimeo", $url=/vimeo.com/, $keys=set("vimeo")],
[$desc="Yahoo", $url=/yahoo.com/, $keys=set("T", "Y")],
[$desc="Yelp", $url=/yelp.com/, $keys=set("__utma")],
[$desc="Windows Live", $url=/live.com/,
$keys=set("MSPProf", "MSPAuth", "RPSTAuth", "NAP")],
[$desc="Wordpress", $url=/wordpress.com/, $pat=/wordpress_[0-9a-fA-F]+/]
} &redef;
}
@ifdef(use_aliasing)
@load roam
@endif
## Per-cookie state.
type CookieContext: record
{
mac: string; ## MAC address of the user.
client: addr; ## IP address of the user.
user_agent: string; ## User-Agent header.
last_seen: time; ## Last time we saw the cookie from this user.
conn: string; ## Last seen connection this cookie appeared in.
cookie: string; ## The session cookie, as seen the last time.
service: string; ## The web service description of this cookie.
};
# Map cookies to their contextual state.
global cookies: table[string] of CookieContext &read_expire = cookie_expiration;
# Create a unique user session identifier based on the relevant cookie keys.
# Return the empty string if the sessionization does not succeed.
function sessionize(cookie: string, info: ServiceInfo) : string
{
local id = "";
local fields = split(cookie, /; /);
if (info?$keys)
{
local matches: table[string] of string;
for (i in fields)
{
local s = split1(fields[i], /=/);
if (s[1] in info$keys)
matches[s[1]] = s[2];
}
if (|matches| == |info$keys|)
for (key in info$keys)
{
if (id != "")
id += "; ";
id += key + "=" + matches[key];
}
}
if (info?$pat)
{
for (i in fields)
{
s = split1(fields[i], /=/);
if (s[1] == info$pat)
id += id == "" ? fields[i] : cat("; ", fields[i]);
}
}
return id;
}
function is_aliased(client: addr, ctx: CookieContext) : bool
{
if (client in Roam::ip_to_mac)
{
local mac = Roam::ip_to_mac[client];
if (mac == ctx$mac && mac in Roam::mac_to_ip
&& client in Roam::mac_to_ip[mac])
return T;
}
return F;
}
function update_cookie_context(ctx: CookieContext, c: connection)
{
ctx$last_seen = network_time();
ctx$conn = c$uid;
if (use_aliasing && ctx$client in Roam::ip_to_mac)
ctx$mac = Roam::ip_to_mac[ctx$client];
}
function format_address(a: addr) : string
{
if (use_aliasing && a in Roam::ip_to_mac)
return fmt("%s[%s]", a, Roam::ip_to_mac[a]);
else
return fmt("%s", a);
}
function report_session_reuse(c: connection, ctx: CookieContext)
{
local attacker = format_address(c$id$orig_h);
local victim = format_address(ctx$client);
NOTICE([$note=SessionCookieReuse, $conn=c,
$suppress_for=10min,
$user=fmt("%s '%s'", attacker, c$http$user_agent),
$msg=fmt("%s reused %s session %s via cookie %s",
attacker, ctx$service, ctx$conn, ctx$cookie),
$identifier=cat(ctx$cookie, c$http$user_agent)
]);
}
function report_session_roamed(c: connection, ctx: CookieContext)
{
local roamer = format_address(c$id$orig_h);
NOTICE([$note=SessionCookieRoamed, $conn=c,
$suppress_for=10min,
$user=fmt("%s '%s'", roamer, c$http$user_agent),
$msg=fmt("%s roamed %s session %s via cookie %s",
roamer, ctx$service, ctx$conn, ctx$cookie),
$identifier=cat(ctx$cookie, roamer)]);
}
# Create a unique user ID based on the notion of user.
function make_client(c: connection) : string
{
return user_is_ip ? fmt("%s", c$id$orig_h) :
fmt("%s '%s'", c$id$orig_h, c$http$user_agent);
}
function report_sidejacking(c: connection, ctx: CookieContext)
{
local attacker = format_address(c$id$orig_h);
NOTICE([$note=Sidejacking, $conn=c,
$suppress_for=10min,
$user=fmt("%s '%s'", attacker, c$http$user_agent),
$msg=fmt("%s hijacked %s session %s via cookie %s",
attacker, ctx$service, ctx$conn, ctx$cookie),
$identifier=cat(ctx$cookie, make_client(c))]);
}
# Track the cookie value inside HTTP.
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if (is_orig && name == "COOKIE")
c$http$cookie = value;
}
# We use this event as an indicator that all headers have been seen. That is,
# this event guarantees that the HTTP state inside the connection record
# has all fields populated.
event http_all_headers(c: connection, is_orig: bool, hlist: mime_header_list)
{
if (! is_orig)
return;
if (! c$http?$cookie || c$http$cookie == "" )
return;
local cookie = "";
local service = c$http$host;
if (service != "")
for (s in services)
{
local info = services[s];
if (info$url in service)
{
cookie = sessionize(c$http$cookie, info);
if (cookie != "")
{
service = info$desc;
break;
}
}
}
if (cookie == "")
{
if (known_services_only)
return;
cookie = c$http$cookie;
}
else
cookie = fmt("subset %s", cookie);
local client = c$id$orig_h;
local user = make_client(c);
if (cookie !in cookies)
{
local mac = "";
if (use_aliasing && client in Roam::ip_to_mac)
mac = Roam::ip_to_mac[client];
cookies[cookie] =
[
$mac=mac,
$client=client,
$user_agent=c$http$user_agent,
$last_seen=network_time(),
$conn=c$uid,
$cookie=cookie,
$service=service
];
return;
}
local ctx = cookies[cookie];
if (cookie != ctx$cookie)
return;
if (user_is_ip)
{
if (client == ctx$client)
{
if (c$http$user_agent != ctx$user_agent)
{
report_session_reuse(c, ctx);
# Uncommenting this will have the effect of reversing the
# current and previous user agent, resulting in another notice
# when the previous user agent appears again.
#ctx$user_agent = c$http$user_agent;
}
update_cookie_context(ctx, c);
}
else if (c$http$user_agent != ctx$user_agent)
{
report_sidejacking(c, ctx);
}
else if (use_aliasing)
{
if (is_aliased(client, ctx))
{
update_cookie_context(ctx, c);
report_session_roamed(c, ctx);
}
else
report_sidejacking(c, ctx);
}
}
else if (client == ctx$client && c$http$user_agent == ctx$user_agent)
update_cookie_context(ctx, c);
else
report_sidejacking(c, ctx);
}