Benjamin Livshits
Weidong Cui
Microsoft Research, Redmond
Recent
popularity of interactive AJAX-based Web 2.0 applications has given rise to a
new breed of security threats: JavaScript worms. In this paper we propose
Spectator, the first automatic detection and containment solution for
JavaScript worms. Spectator performs distributed data tainting by observing and
tagging the traffic between the browser and the Web application. When a piece
of data propagates too far, a worm is reported. To prevent worm propagation, subsequent
upload attempts performed by the same worm are blocked. Spectator is able to
detect fast and slow moving, monomorphic and polymorphic worms with a low rate
of false positives. In addition to our detection and containment solution, we
propose a range of deployment models for Spectator, ranging from simple
intranet-wide deployments to a scalable load-balancing scheme appropriate for
large Web sites.
In this paper we demonstrate the effectiveness and efficiency of Spectator through both large-scale simulations as well as a case study that observes the behavior of a real-life JavaScript worm propagating across a social networking site. Based on our case study, we believe that Spectator is able to detect all JavaScript worms released to date while maintaining a low detection overhead for a range of workloads.
Web applications have been a prime target for application-level security attacks for several years. A number of attack techniques, including SQL injections, cross-site scripting, path traversal, cross-site request forgery, HTTP splitting, etc. have emerged, and recent surveys have shown that the majority of Web sites in common use contain at least one Web application security vulnerability [39,43]. In fact, in the last several years, Web application vulnerabilities have become significantly more common than vulnerabilities enabled by unsafe programming languages such as buffer overruns and format string violations [40].
While Web application vulnerabilities have been around for some time and a range of solutions have been developed [18,15,23,30,21,45,25], the recent popularity of interactive AJAX-based Web 2.0 applications has given rise to a new and considerably more destructive breed of security threats: JavaScript worms [11,13]. JavaScript worms are enabled by cross-site scripting vulnerabilities in Web applications. While cross-site scripting vulnerabilities have been a common problem in Web based-applications for some time, their threat is now significantly amplified with the advent of AJAX technology. AJAX allows HTTP requests to be issued by the browser on behalf of the user. It is no longer necessary to trick the user into clicking on a link, as the appropriate HTTP request to the server can just be manufactured by the worm at runtime. This functionality can and has been cleverly exploited by hackers to create self-propagating JavaScript malware.
The
first and probably the most infamous JavaScript worm is the Samy worm released
on MySpace.com, a social networking site in 2005 [36]. By exploiting a cross-site scripting
vulnerability in the MySpace site, the worm added close to a million users to
the worm author's “friends” list. According to MySpace site maintainers, the
worm caused an explosion in the number of entries in the friends list across
the site, eventually leading to resource exhaustion. Two days after the attack
the site was still struggling to serve requests at a normal pace.
The Samy worm gets its name from the MySpace login of its creator. Initially, the malicious piece of JavaScript (referred to as the payload) was manually placed in Samy's own MySpace profile page, making it infected. Each round of subsequent worm propagation consists of the following two steps:
Note
that the enabling characteristic of a JavaScript worm is the AJAX propagation
step: unlike “old-style” Web applications, AJAX allows requests to the server
to be done in the background without user's knowledge. Without AJAX, a worm
such as Samy would be nearly impossible. Also observe that worm propagation
happens among properly authenticated MySpace users because only authenticated
users have the ability to save the payload in their profiles.
While
Samy is a relatively benign proof-of-concept worm, the impact of JavaScript
worms is likely to grow in the future. There are some signs pointing to that
already: another MySpace worm released in December 2006 steals user
passwords by replacing links on user's profile site with spoofed HTML made to
appear like login forms [6].
The stolen credentials were subsequently hijacked for the purpose of sending
spam. Similarly, Yamanner, a recent Yahoo! Mail worm, propagated through the
Webmail system affecting close to 200,000 users by sending emails with embedded
JavaScript to everyone in the current user's address book [4]. Harvested emails were then transmitted to a
remote server to be used for spamming. Additional information on eight
JavaScript worms detected in the wild so far is summarized in our technical
report [22]. Interested readers are also
referred to original vulnerability reports [4,36,6,5,27,35,34].
The impact of JavaScript worms will likely increase if attackers shift their attention to sites such as ebay.com, epinions.com, buy.com, or amazon.com, all of which provide community features such as submitting product or retailer reviews. The financial impact of stolen credentials in such a case could be much greater than it was for MySpace, especially if vulnerability identification is done with the aid of cross-site scripting vulnerability cataloging sites such as xssed.com [31]. Today cross-site scripting vulnerabilities are routinely exploited to allow the attacker to steal the credentials of a small group of users for financial gain. Self-propagating code amplifies this problem far beyond its current scale. It is therefore important to develop a detection scheme for JavaScript worms before they become commonplace.
A comprehensive detection solution for JavaScript worms presents a tough challenge. The server-side Web application has no way of distinguishing a benign HTTP request performed by a user from one that is performed by a worm using AJAX. An attractive alternative to server-side detection would be to have an entirely client-side solution. Similarly, however, the browser has no way of distinguishing the origin of a piece of JavaScript: benign JavaScript embedded in a page for reasons of functionality is treated the same way as the payload of a worm. Filtering solutions proposed so far that rely on worm signatures to stop their propagation [38] are ineffective when it comes to polymorphic or obfuscated payloads, which are easy to create in JavaScript; in fact many worms detected so far are indeed obfuscated. Moreover, overly strict filters may cause false positives, leading to user frustration if they are unable to access their own data on a popular Web site.
This
paper describes Spectator, a system for detecting and containing JavaScript
worms, and makes the following contributions:
The
rest of the paper is organized as follows. Section 2 describes the overall
architecture of Spectator. We formally describe our worm detection algorithm
and Spectator implementation in Sections 3 and 4, respectively.
Section 5 describes the experiments and case studies we performed. Section 6
discusses Spectator design choices, tradeoffs, and threats to the validity of
our approach. Finally, Sections 7 and 8 summarize related work and
provide our conclusions.
This section provides an overview of Spectator architecture and design assumptions. Section 3 gives a formal description of our worm detection algorithm.
A
recent study concluded that over 90% of Web applications are vulnerable to
some form of security attack, including 80% vulnerable to cross-site
scripting [43]. Cross-site scripting,
which is at the root of JavaScript worms, is commonly identified as the most
prevalent Web application vulnerability.
While it is widely recognized that secure programming is the best defense against application-level vulnerabilities, developing fully secure applications remains a difficult challenge in practice. For example, while MySpace was doing a pretty good job filtering well-formed JavaScript, it failed to filter out instances of java\nscript, which are interpreted as legal script in Internet Explorer and some versions of Safari. Despite best intentions, insecure applications inevitably get deployed on widely used Web sites.
The goal of Spectator is to protect Web site users from the adverse effects of worm propagation after the server has failed to discover or patch a vulnerability in a timely manner. The essence of the Spectator approach is to tag or mark HTTP requests and responses so that copying of the content across a range of pages in a worm-like manner can be detected. Note that JavaScript worms are radically different from “regular” worms in that they are centralized: they typically affect a single Web site or a small group of sites (the same-origin policy of JavaScript makes it difficult to develop worms that propagate across multiple servers).
Spectator consists of an HTTP proxy inspecting the traffic between the user's browser and a Web server in order to detect malicious patterns of JavaScript code propagation. Our tagging scheme described in Section 4 is a form of distributed tainting: whenever content that contains HTML is uploaded to the server, Spectator modifies it to attach a tag invisible to the end-user. The tag is preserved on the server and is contained in the HTML downloaded by subsequent requests. Spectator injects client-side support so that tags are reliably propagated on the client side and cannot be removed by worms aware of our tagging scheme. Client-side support relies on HTTP-only cookies and does not require specialized plug-ins or browser modifications, thus removing the barrier to client-side adoption.
Worm detection at the Spectator proxy works by looking for long propagation chains. Our detection algorithm is designed to scale to propagation graphs consisting of thousands of nodes with minimal overhead on every request. Whenever a long propagation chain is detected, Spectator disallows further uploads that are caused by that chain, thereby containing further worm propagation.
The Spectator detection algorithm is designed to detect propagation activity that affects multiple users. With every HTML upload, we also record the IP address of the user issuing the request. The IP address is used as an approximation of user identity. We keep track of IP addresses so that a user repeatedly updating their profile is not flagged as worm. If multiple users share an IP address, such as users within an intranet, this may cause false negatives. If the same user connects from different IP addresses, false positives might result. Worm detection relies on sufficiently many users adopting Spectator. However, since Spectator relies on no additional client-side support, it can be deployed almost instantaneously to a multitude of users.
To
make the discussion above more concrete, a diagram of Spectator's architecture
is shown in Figure 1. Whenever a user attempts to
download a page containing Spectator tags previously injected there by
Spectator, the following steps are taken, as shown in the figure:
Whenever
an upload containing HTML is observed, the following steps are taken:
The
Spectator worm detection algorithm relies on the following properties that
guarantee that we can observe and record the propagation of a piece of data
during its entire “round trip”, captured by steps 1-6 above, thereby enabling
taint tracking. The properties described below give the information required to
formally reason about the Spectator algorithm. A detailed discussion of how
Spectator ensures that these properties hold is delayed until Section 4.
Property 1:
Reliable HTML input detection.
We can detect user input that may contain HTML and mark it
as tainted. Additionally, we can mark suspicious user input without disturbing
server-side application logic so that the mark propagates to the user.
Property 2:
Reliable client-side tag propagation.
Browser can propagate taint tags from an HTTP response to a
subsequent request issued by the browser.
This section describes our worm detection algorithm. Section 3.1 formalizes the notion of a worm and Section 3.2 talks about our detection algorithm. Finally, Section 3.3 discusses worm containment.
We
introduce the notion of a propagation graph that is updated whenever
new tags are inserted. Each node of the graph corresponds to a tag and edges
represent causality edges. Each node carries with it the IP address of the
client the tag originates from.
Definition 1 A tag is a long integer uniquely
identifying an HTML upload to Spectator.
Definition
2 A causality edge is a tuple of tag-IP
address pairs
representing the fact that
requested by
originated from a page
requested by
that has
associated with it.
Definition
3 Propagation graph
, where vertices
is a set of tag-IP pairs
and
is the set of causality edges
between them.
Definition
4 The distance between two nodes
and
, denoted as
, in a propagation graph
is the smallest number of
unique IP addresses on any path connecting
and
.
Definition
5 Diameter of a propagation graph
, denoted as
, is the maximum distance
between any two nodes in
.
Definition
6 We say that
contains a worm if
exceeds a user-provided
threshold
.
Note that the propagation graph is acyclic. While it is possible to have node
sharing, caused by a page with two tags generating a new onehaving a cycle in
the propagation graph is impossible, as it would indicate a tag caused by
another one that was created chronologically later. Ideally, we want to perform
worm detection on the fly, whenever a new upload request is observed by
Spectator. When a new edge is added to the propagation graph
,
we check to see if the diameter of updated graph
now
exceeds the user-defined threshold
.
The issue that complicates the design of an efficient algorithm is that we
need to keep track of the set of unique IP addresses encountered on the current
path from a root of the DAG. Unfortunately, computing this set every time
an edge is added is exponential in the graph size in the worst case. Storing
the smallest set of unique IP addresses at every node requires
space
in the worst case: consider the case of a singly-linked list where every node
has a different IP address. Even if we store these sets at every node, the
computation of the IP address list at a node that has more than one predecessor
still requires an exponential amount of work, as we need to consider all ways
to traverse the graph to find the path with the smallest number of unique IP
addresses. Our goal is to have a worm detection algorithm that is as efficient
as possible. Since we want to be able to detect slow-propagating worms, we
cannot afford to remove old tags from the propagation graph. Therefore, the
algorithm has to scale to hundreds of thousands of nodes, representing tags
inserted over a period of days or weeks.
In this section we describe an iterative algorithm for detecting when a newly added propagation graph edge indicates the propagation of a worm. As we will demonstrate later, the approximation algorithm is mostly conservative, meaning that if there is a worm, in most cases, the approximation approach will detect it no later than the precise one.
The
graph
maintained by our algorithm is a forest approximating the
propagation graph
. Whenever node sharing is
introduced, one of the predecessors is removed to maintain the single-parent
property. Furthermore, to make the insertion algorithm more efficient, some of
the nodes of the graph are designated as storage stations; storage
stations accelerate the insertion operation in practice by allowing to “hop”
towards a root of the forest without visiting every node on the path.
We use the following representation for our approximate algorithm.
points to the nearest storage station on its
path to the root or null if
is
the root. Every node
in
has a set of IP addresses
associated with it. The number of IP addresses
stored at a node is at most
, where
is a user-configured parameter.
At
every node
we maintain a depth value
denoted as
, which is an approximation of the number of unique IP
addresses on the path from
to the root. Whenever the
value exceeds the user-defined threshold
, we raise an alarm.
For
space reasons, detailed pseudo-code for the insertion algorithm that describes
the details of data structure manipulation is given in our technical
report [22]. Here we summarize the essence of
the insertion algorithm. Whenever a new causality edge from node
to node
is added to
:
Observe that the the maximum
value computed by this algorithm is exactly
because the maximum distance in
is
that between a node and a root.
Notice that the approach described in this section is essentially a greedy
algorithm: in the presence of multiple parents, it chooses the parent that it
believes will result in higher overall diameter of the final approximation
graph
.
Of course, the advantage of the approximate algorithm is that it avoids the
worst case exponential blow-up. However, without the benefit of knowing future
insertion operations, the greedy algorithm may yield a lower diameter,
potentially leading to false negatives. While this has never happened in our
experiments, one such example is described below.
|
|
Example 1 Consider the propagation graph displayed in Figure 3. Suppose we first insert the two nodes on the
bottom left with IPs
and
and
then the node with
. When we add
to
the graph, the approximation algorithm will decide to remove the newly created
edge (showed as dashed) because doing so will result in a greater diameter.
However, the greedy algorithm makes a suboptimal decision: when nodes
on the right with IPs
and
are
added, the resulting diameter will be 3, not 4 as it would be with
the precise approach. ![]()
Maintaining
an approximation allows us to obtain a very modest time and space bounds on new
edge insertion, as shown below. Discussion of how our approximate detection
algorithm performs in practice is postponed until Section 5.
Insertion Time Complexity. The complexity of the algorithm at every
insertion is as follows: for a graph
with
nodes, we consider
storage stations
at the most. Since storage stations having non-overlapping lists of IP
addresses, having more storage stations on a path from a root of
would
mean that we have over
IPs in total on that particular path, which
should have been detected as a worm. At every storage station, we perform an
average
time containment check. So, as a result, our approximate insertion algorithm
takes
time
on average.
Space Complexity. We store
IP addresses at the storage stations
distributed throughout the propagation graph
. This is easy
to see in the worst case of every IP address in the graph being unique. The
union of all IP lists stored at all storage stations will be the set of all graph
nodes. Additionally, we store IP addresses at the nodes between
subsequent storage stations. In the worst case, every storage station
contains
nodes and we store
IP addresses, which preserves the
total space requirement of
. More precisely, with at most
storage
stations, we store approximately

IP
addresses. Note that in practice storage stations allow insertion operations to
run faster because instead of visiting every node on the path from the root, we
can instead “hop” to the next storage station, as demonstrated by the
bound.
However, using storage stations also results in more storage space being taken
up as shown by the
bound. Adjusting parameter
allows
us to explore this space-time trade-off: bigger
results
in faster insertion times, but also requires more storage.
Worm Containment Complexity. When a worm is detected, we walk the tree
that the worm has infected and mark all of its nodes as such. This takes
time
because in the worst case we have to visit and mark all nodes in the tree. The
same bound holds for when we mark nodes in a tree as false positives.
Whenever the depth of the newly added node exceeds detection threshold
,
we mark the entire tree containing the new edge as infected. To do so, we maintain
an additional status at every leaf. Whenever a tree is deemed infected by our
algorithm, we propagate the infected status to every tree node. Subsequently,
all uploads that are caused by nodes within that tree are disallowed until
there is a message from the server saying that it is safe to do so.
When the server fixes the vulnerability that makes the worm possible, it needs to notify the Spectator proxy, at which point the proxy will remove the entire tree containing the new edge from the proxy. If the server deems the vulnerability reported by Spectator to be a false positive, we never subsequently report activity caused by nodes in this tree as a worm. To do so, we set the node status for each tree node as a false positive and check the node status before reporting a worm.
Distributed
tainting in Spectator is accomplished by augmenting both upload requests to
insert tracking tags and download requests to inject tracking cookies and
JavaScript.
To track content propagation on the client side, the Spectator proxy maintains a local session for every page that passes through it. Ideally, this functionality would be supported by the browser natively; in fact, if browsers supported per-page cookies, that is, cookies that expire once the current page is unloaded, this would be enough to precisely track causality on the client side. Since such cookies are not supported, we use a combination of standard per-session browser cookies and injected JavaScript that runs whenever the current page is unloaded to accomplish the same goal.
Whenever
a new page is sent by the Spectator proxy to the browser, a new session
tuple
is generated, consisting of two long integer values, which
are randomized 128-bit integers, whose values cannot be easily guessed by
the attacker. Our client-side support consists of two parts:
HTTP-only Spectator cookie in the browser. We augment every server
response passing through Spectator with an HTTP-only cookie containing
.
The fact that the session ID is contained in an HTTP-only cookie means that it
cannot be snooped on by malicious JavaScript running within the browser,
assuming the browser correctly implements the HTTP-only attribute. For a page
originating from server
, the domain of the session ID cookie is set
to
, so it is passed back to Spectator on every request to
, allowing us to
perform causality tracking as described above.
Ideally, we would like to have a per-page cookie that expires as soon as the
page is unloaded. Unfortunately, there is no support for such cookies. So, we
use session cookies that expire after the browser is closed, which may not
happen for a while. So, if the user visits site
served by Spectator, then visits site
,
and then returns to
, the Spectator cookie would still be sent to
Spectator by the browser.
Injected client-side JavaScript to signal page unloads. In order to
terminate a propagation link that would be created between the two unrelated
requests to server
, we inject client-side JavaScript into every
file that Spectator sends to the browser. Furthermore, before passing the page
to the client, within Spectator we add an unload
event handler, which sends an XmlHttpRequest
to the special URL __spectator__ to
“close” or invalidate the current session, so that subsequent requests with the
same
are
ignored. The __spectator__ URL does
not exist on server
: it is just a way to communicate with
Spectator while including
created for server
(notice that it is
not necessary to pass the session ID as a parameter to Spectator, as the
session ID cookie will be included in the request as well).
Injected client-side code is shown in Figure 5.
To make it so that malicious JavaScript code cannot remove the unload
handler, we mediate access to functions window.attachEvent
and window.detachEvent as
suggested in the BEEP system [16] by
injecting the JavaScript shown in Figure 6
at the very top of each page served by Spectator. Furthermore, we also store
is
a private member of class handler [8]; this way it is not part of the
handler.unload function source code
and cannot be accessed with a call to toString.
To prevent
from being accessed through DOM traversal,
the original script blocks defining the unload
handler and containing the numerical value of
embedded verbatim is subsequently removed
through a call to removeChild in the next
script block, similar to what is suggested by
Meschkat [26].
|
|
|
Figure 5: Intercepting page unload
|
While
the basic client-side support is relatively simple to implement, there are two
types of potential attacks against our client-side scheme to address, as
described below.
Worm Relaying. First, the attacker might attempt to break a
propagation chain by losing the session ID contained in the browser, a
technique we refer to as worm relaying. Suppose we have a page in the
browser loaded from server
. The attacker may programmatically direct the
browser to a different server
, which would in turn connect to
.
Server
in
this attack might be set-up solely for the sole purpose of relaying requests to
server
,
as shown in Figure 4. Notice that since the
session ID cookie will not be sent to
and its value cannot be examined. We introduce
a simple restriction to make the Spectator proxy redirect all accesses to
that
do not contain a session ID cookie to the top-level
URL
such as www.D.com.
In fact, it is quite common to disallow access, especially programmatic access
through AJAX RPC calls, to an inside URL of large site such as www.yahoo.com
by clients that do not already have an established cookie. With this
restriction,
will
be unable to relay requests on behalf of the user.
Tampering
with unload events. To make it so that malicious
JavaScript code cannot remove the unload handler
or trigger its own unload handler after Spectator's, we mediate access to
function window.detachEvent by
injecting the JavaScript shown in Figure 6
at the very top of each page served by Spectator. If malicious script attempts
to send the unload event to Spectator
prematurely in an effort to break the propagation chain, we will receive more
that one unload event per session. When a
sufficient number of duplicate unload events
is seen, we raise an alarm for the server, requiring a manual inspection. It is
still possible for an attacker to try to cause false positives by making sure
that the unload event will never be sent.
This can be accomplished by crashing the browser by exploring a browser bug or
trying to exhaust browser resources by opening new windows. However, this
behavior is sufficiently conspicuous to the end-user to prompt a security
investigation and is thus not a good candidate for inclusion within a worm.
Opening
a new window.
Note that opening a new window will not help an attacker break causality
chains. If they try to perform a malicious upload before the unload
event in the original window is sent to the proxy, Spectator will add a
causality link for the upload. Fetching a new page with no tags before the
malicious upload will not help an attacker evade Spectator because this clean
page and the original page share the same HTTP-only cookie. As such, Spectator
will think that the upload is caused by either of the sessions corresponding to
that cookie. This is because Spectator's approximation approach selects the
parent node with a larger depth when a node has multiple predecessors.
The
primary goal of server-side support is to embed Spectator tags into suspicious
data uploaded to a protected Web server in a transparent and persistent manner
so that (1) the tags will not interfere with the Web server's application
logic; and (2) the embedded tags will be propagated together with the data when
the latter is requested from the Web server. To achieve these goals of
transparency and persistence, we need to be able to reliably detect suspicious
data to embed Spectator tags into uploaded input. Next, we discuss our
solutions to the challenges of transparency and persistence that do not
require any support on the part of the Web server.
Data uploads are suspicious if they may contain embedded JavaScript.
However, for a cross-site scripting attack to be successful, this JavaScript is
usually surrounded with some HTML tags. The basic idea of detecting suspicious
data is to detect the presence of HTML-style content in the uploaded
data. Of course, such uploads represent a minority in most applications, which
means that Spectator only needs to tag and track a small portion of all
requests. Spectator detects suspicious data by searching for opening matching
pairs of HTML tags <tag attribute1=... attribute2=...>
and
</tag>
. Since many servers may
require the uploaded data to be URL- or HTML-encoded, Spectator also attempts
to decode the uploaded data using these encodings before attempting the
pattern-matching.
Spectator embeds a tag immediately preceding the first opening >
for each matching pair of HTML tags.
(Note that if the original data is URL encoded, Spectator will re-encode the
tagged output as well.) To illustrate how tag insertion works, consider an HTTP
request containing parameter
<div><b onclick="javascript:alert(...)">...</b></div>
This
parameter will be transformed by Spectator into a request containing
<div spectator_tag=56><b onclick="javascript:alert(...)"
spectator_tag=56>...</b> </div>
. We
tested this scheme with several real-world web servers chosen from a cross-site
scripting vulnerability listing site xssed.com. For vulnerable servers that
reflect user input verbatim, this scheme works well as expected. Our further
investigations into three popular Webmail sites, Hotmail, Yahoo Mail, and
Gmail, have shown this scheme did not work because the Spectator tags were
stripped by the Web servers. While this is difficult to ascertain, our
hypothesis is that these sites use a whitelist of allowed HTML attributes.
To handle Web sites that may attempt to strip Spectator tags, we propose an
alternative approach. In this new scheme, Spectator embeds tags directly into
the actual content surrounded by HTML tags. For example
<b>hello world...</b>
will be transformed
by Spectator into a request containing
<b>spectator_tag=56hello world...</b>
We
tested this scheme with the three Webmail sites above and found that it works
for all of them. However, there is a possibility that such tags may interfere
with Web server's application logic. For example, if the length of the actual
content is explicitly specified in the data, this tagging scheme will affect
data consistency. Unfortunately, while our approach to decode and augment the
uploaded traffic works for the sites we have experimented with, in the worst
case, the server may choose an entirely new way to encode uploaded parameters.
In this case, properly identifying and tagging HTML uploads will require
server-side cooperation.
|
|
An experimental evaluation of Spectator poses a formidable challenge. Since we do not have access to Web sites on which real-life worms have been released, worm outbreaks a