1. Disrupting Client State
1.1. Problem
An application shouldn’t
count on the validity of client-side data. With JavaScript and AJAX, an
application should never depend on the accuracy of the client-side logic
or state. This recipe discusses how to disrupt the client state, in the
hopes that such information will be passed back to the server with
adverse consequences.
1.2. Solution
From within this JavaScript, identify the item that you’re
interested in. If it’s hidden, serialized, or obscured, you may have to
trace out the JavaScript line by line. You can copy and paste the
JavaScript file into the editor of your choice. In the case where the
entire script is obfscucated, some editors allow you to auto-indent and
format. This helps considerably with comprehension.
Once you’ve identified the variable, method, or object you’re
interested in disrupting, move over to the Console tab in Firebug. From
here, you can enter a line or multiple lines of custom Javascript to run
within your current browser Javascript sandbox. For example, if your
task is as simple as setting a new high-score value, you may write
HighScore=1000 and then click the
page’s Submit High Scores button and be done with it. If you’re going
for something more complex, such as overriding the default behavior of
an object so that it will pull an account with a different account
number on the next AJAX callback, you’ll need to craft your own custom
JavaScript.
1.3. Discussion
Because all web data must eventually travel via HTTP requests,
there is nothing you can do via this recipe that you cannot also do by
tampering with a request. The advantage this recipe offers is that it
allows you to hijack the application itself, so that the application
will set the parameters in accordance to a specific change in state.
This saves you a lot of time, compared to the alternatives: computing
all the HTTP parameters manually, just attempting generic attacks,
trying to guess malicious values, or assigning values randomly.
The best example of this recipe is JavaScript-based games. Many JavaScript-based games will have a furiously
complicated internal state. Furthermore, when reporting the game results
to the server, it is not easy to tell which values correspond to the
score, the number of tries left, or other details. Yet they’ll
communicate back to the server for key details: updating score,
downloading new levels, or changing the difficulty. If you’re going to
cheat, say to get the highest high score (which would be stored
server-side), it’s easier to modify the game than to intercept the
communication. Changing the current_score JavaScript variable might be
easier than deserializing a very long HTTP parameter, tweaking one part,
and reserializing it.
2. Checking for Cross-Domain Access
2.1. Problem
When your application runs JavaScript from another site there is a
risk that the other site could change their script to include malicious
elements. Make sure your application doesn’t rely on untrusted external
scripts.
2.2. Solution
Right-click on a page and select View Page Source. Search the
source for the tag <script>
specifically where the source ("src") attribute is set. If the
source is set to a page outside of a domain you control, then the
application relies upon cross-domain JavaScript.
Or, test this programmatically. In a Unix prompt or Cygwin, download one or more pages to scan into a folder. Navigate to this folder in
in a command shell, Cygwin, or a command-line terminal. Example 1 will identify every instance in your
set of pages where a script refers to an external source.
Example 1. Search multiple files for external script references
#!/usr/bin/perl
use HTML::TreeBuilder;
use URI;
#Specify valid hosts and domains here. The script will skip these.
my @domains = ( "example.com",
"img.example.com",
"js.example.com" );
#Parse each file passed via the command line:
foreach my $file_name (@ARGV) {
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($file_name);
$tree->elementify();
#Find each instance of the "script" tag
@elements = $tree->find("script");
foreach my $element (@elements) {
#Get the SRC attribute
my $src = $element->attr("src");
if( $src ) {
$url = URI->new($src);
$host = $url->host;
#Skip the specified domains
if(!(grep( /$host/i, @domains ))) {
#From the SRC URL, print just the Host
print $host;
}
}
}
#Delete the tree to start over for the next file
$tree = $tree->delete;
}
|
2.3. Discussion
There are times when running untrusted JavaScript is not only
permissible, but necessary for your website to operate correctly.
Mashups, sites that blend functionality from multiple sources, will need
to load JavaScript from their sources. For example, you can embed a
Google map or YouTube video without running external code. If such
functionality is crucial to your website, then this recipe is largely
moot. On the other hand, very few sites require functionality from other
sites—usually they’re incorporating data or an entire page. If you can,
grab the data you need via a gateway on your application server, then
deliver it within the same page as your other content. This allows your
application to filter out just the data it needs and thus reduces the
trust placed in a website you can’t control.
When deciding whether or not to include external scripts, ask
yourself: would you grant this third party access to your source code
revision control respository? To your user’s data? Including such a
script on your website gives them implicit permission to execute
JavaScript code within your domain. This lets the third party edit the
appearance and functionality of your application, as well as access to
your user’s cookies.
3. Reading Private Data via JSON Hijacking
3.1. Problem
Every URL used for an AJAX request can also be accessed directly
from a web browser or from within another page. This means cross-site
reference forging (CSRF) attacks, can be applied to AJAX requests as
well. Beyond this, there’s a new attack out known as AJAX hijacking, or
more specifically, JSON hijacking. This new attack allows one to read
private data via a CSRF-like attack, rather than initiate an action à la
CSRF. So far it applies only to JSON serialized data—if your application
does not use JSON, it is safe. We’ll walk you through testing for JSON
hijacking in this recipe.
3.2. Solution
If your application returns JSON data at a particular URL, first log into your application, then try browsing
directly to that URL. If no data is returned, then it’s likely your
application already checks for a token or secret parameter beyond the
HTTP cookies. If it does return data, check the JSON response data to
see if your server includes any specific protection against JSON
hijacking. If your application returns confidential, but unprotected
JSON data upon request, you should flag it as vulnerable to JSON
hijacking.
For example, an AJAX application may send a request to http://www.example.com/json/clientInfo.jsp?clientId=3157304449.
If this page immediately responds with JSON, such as {"user": { "fname": "Paula", "lname":"Brilliant", "SSN":
"078-05-1120" }, "accountNumber": "3157304449" }, then it’s
likely this application is vulnerable to JSON hijacking. An attacker
could inject JavaScript to submit requests for many identifiers,
gathering information on each account.
3.3. Discussion
Note that this recipe applies in two situations. First and
foremost, if your application displays this data without any authentication, you can be sure it can be read by a
malicious attacker just by navigating to the page. The case where such
protection will help is if the data is also available to a logged-in
user and an attacker executes a CSRF-like attack against that user. For
example, gMail was susceptible to a JSON hijacking attack where a victim
would visit the attacker’s website. The attacker’s website would issue a
request to gMail for their contact list, and if the victim was already
logged in, the attacker’s page would receive and parse the JSON, and
finally submit the data back to the attacker’s server.
After authentication is in place, JSON hijacking protection can
take a variety of forms. Google appends while(1) into their JSON data, so that if any
malicious script evaluates it, the malicious script enters an infinite
loop. Comment characters such as /*
and */ should be sufficient to escape
the entire string, rendering it unable to be evaluated. Using our
example above, if the string read while(1);
{"user": { "fname": "Paula", "lname":"Brilliant", "SSN": "078-05-1120"
}, "accountNumber": "3157304449" }, then it would be
protected—a script evaluating it would be stuck in an infinite
loop.
JSON serialization is a way to transmit data in an easily parsed
format. Javascript can parse JSON using the built-in eval() function,
although this is discouraged. Using the eval() function without any other validation
is a weakness that may be exploited by code injection. It’s not worth
the risk, so people have now written full-fledged JSON parsers that
include validation. You can find references to them at http://www.json.org.
Some websites are now purposefully offering public
(non-confidential) data via JSON. They hope that other websites will
read this data, in order to create mashups or other services. This
recipe only really applies when the data being published is confidential
or otherwise private. For example, Google’s gMail would reveal one’s
address book contacts via JSON hijacking, which was an obvious
vulnerability. Meanwhile, Reddit (a social news site) offers JSON feeds
for nearly all of its public news feeds, which is just an additional
feature. You can find Reddit’s JSON feed at http://json.reddit.com.