Wednesday, December 23, 2009
5 ways to prevent clickjacking on your website (and why they suck)
For example, imagine an attacker who builds a web site that has a button on it that says "click here for a free iPod". However, on top of that web page, the attacker has loaded an iframe with your mail account, and lined up exactly the "delete all messages" button directly on top of the "free iPod" button. The victim tries to click on the "free iPod" button but instead actually clicked on the invisible "delete all messages" button. In essence, the attacker has "hijacked" the user's click, hence the name "Clickjacking".The problem with clickjacking attack is that it is extremely difficult to prevent. Unlike other popular vulnerabilities like CSRF, XSS, SQL injection, this one is based on a functionality that is widely used in the web nowadays - frames (I'm skipping the case of plugin-based-clickjacking for clarity here). Frames allow you to nest one webpage or widget in another page - this is now used for login pages, commenting, previewing content in CMSes, for JavaScript interactions and a million other things.
Browsers nowadays use same origin policy to protect your data if you're framing or being framed from another domain (this prevents JavaScripts from talking to each other and accesing documents across the domain boundary). But JavaScript is not required for a clickjacking attack - CSS is enough. In the simplest form (e.g. used in recent Facebook users attack), you're just using a small <iframe>, and position it absolutely. The rest is just social engineering.
Our users have a few options to protect themselves. So maybe 1% of them will be "protected". But what can we - web developers do to prevent the clickjacking on our sites? Sadly, not much, but here's the list:
Monday, December 21, 2009
New Facebook clickjacking attack in the wild - fb.59.to
Some Facebook users today saw a comment looking like this (new pix!):
Clicking on the comment that links to
http://www.facebook.com/l.php?u=http%253A%252F%252Ffb.59.to%252F%253F4ff11a526ae73e9f170bbe6702ebb93c&h=..somehash...&ref=nf
redirects users to http://fb.59.to web page.
On this page they are given a fake Turing test that tricks them into clicking a "blue button" which is their clickjacked Facebook page positioned at adding a new comment ("Share" button). The whole web page looks like this (clickjacked area is marked green):
In page source we can see that there is a IFRAME element:
<iframe frameborder=0 scrolling=no height=25 width=100 src="2.php?u=http://fb.59.to/?...somehash...." ></iframe><span style=background-color:yellow;><font style=font-size:13 ; color=white>
The target URL (2.php) has another IFRAME which in turn has yet another one with the target page being
<div style="left:-90px;top:-386px;position:absolute;" <iframe height=400 width=250 src="http://www.facebook.com/sharer.php?u=http://fb.59.to/?hash" frameborder=0 scrolling=no> </iframe> </div>
Clicking on the button shares the malicious link on Facebook.
The page has a meta-redirect set up to a Youtube movie launching in 12 seconds so a users might get the impression that the movie launched because they successfully passed the Turing test.
Update: The attack does not work in IE and Opera only because of incorrect HTML used in one of the pages in this malicious site. Doing a simple fix in HTML makes both mentioned browsers also vulnerable to the attack.
Thanks go to Grzegorz Ciborowski and Pawel Czernikowski for detecting the attack.
Wednesday, October 21, 2009
Hardening PHP: magic_quotes_gpc - False sense of security
However, staying secure is not that simple - you need to know all the limits and caveats of your tools, because sometimes they promise you something but do not deliver - as it is in case of magic quotes functionality.
Magic quotes
PHP magic_quotes_gpc are sometimes recommended as a protection from SQL injection .When this ini setting is on, all GET/POST/COOKIE variables are automatically run through addslashes() function, basically escaping all quote characters with "\".All is good
Let's look at an example, where that protection would work:<?php
// some admin login functionality
$logged_in = mysql_query("SELECT 1 FROM users WHERE login='admin' AND password=sha1('" . $_POST['password'] . "')");
// process $logged_in
When magic_quotes_gpc are ON injecting the PASSWORD variable won't work. A simplest attack vector "' OR 1=1 -- " would transform to
SELECT 1 FROM users WHERE login='admin' AND password=sha1('\' OR 1=1 -- ')
That's a perfectly escaped SQL query, nothing can get injected. magic_quotes_gpc escape the dangerous single quote for us. So - goodbye SQL injection? WRONG!Magic quotes only insert a backslash before a few characters, nothing more. That protects you from SQL injection only in some particular cases like above and only by coincidence. To prove that, let's analyze the following script.
So much for magic...
<?php
// display a single post based on id
$post_data = mysql_query("SELECT title, content FROM posts WHERE post_id={$_GET['id']}");
// display $post
The same "' OR 1=1 -- " transforms to invalid query:SELECT title, content FROM posts WHERE post_id= \' OR 1 = 1 --
and if you have display_errors ON, you just made the attacker very happy with your database details outputted right before his eyes. Even if you don't, there still is a possiblity of blind sql injection.
But what if an attacker set id parameter to one of these:
- 1 AND (SELECT COUNT(*) from another table_name) BETWEEN 0 AND 100
- -10000 union select user_password from users where user_login=CHAR(97,100,109,105,110) (admin)
In this case a single mysql query parameter that was left unprotected could lead to your whole database content dumped to the attacker (I'm not kidding - see sqlmap and try for yourself).
Know your context
The fundamental problem with magic_quotes_gpc is that they know nothing about the context. They don't know if you're using the data to insert it into MySQL, Oracle, or if you're writing to a file. Maybe you're sending it through SOAP or displaying it in HTML? Or maybe all of it. They just don't have enough information, only you know it. Escaping values depends on a context in which they are used. You should disable magic quotes now also for the following reasons:- they mangle your data before you get the chance to reach it and can lead to double escaping,
- most frameworks expect them to be off, and if they're enabled, try to undo these changes at bootstrap,
- they're deprecated in PHP 5.3 and will be removed in PHP 6.
- to output to a HTML - use htmlspecialchars()
- to escape a string in MySQL - use mysql_real_escape_string().
- to escape a string in Postgresql - use pg_escape_string()
- When you are expecting an integer in SQL query - simply cast to int
- ALWAYS use prepared statements when querying databases.
Have any comments on the subject to share? Please do so, this is my first security-related post and I'd like to hear your opinion.
Tuesday, September 29, 2009
Weird date format from FreeTDS with mssql.datetimeconvert = 0
We were migrating to newest daily FreeTDS. As soon as we recompiled FreeTDS, our PHP applications started acting weird, showing invalid dates. We narrowed down all problems to a change in date format we got from SQL server.
A simple query:
echo $mdb2->getOne("SELECT datefield FROM table");
now returned some weird data format:2009年01月01日 31695:06However, when we used mssql_* functions, skipping MDB2 altogether the date format was OK. I noticed one bug report: mssql.datetimeconvert force set to 0 - generate wrong dates. It was correct - even though we used mssql.datetimeconvert = 1 in our php.ini, MDB2 driver sets it to 0 on connect().
But the same code used to work with older FreeTDS. It seems that FreeTDS changed its internal date format, some locale settings etc. And disabling datetimeconvert by MDB2 triggered the problem as we started getting this new weird format.
I didn't want to mess up with internal date handling by MDB2 and keep patches to apply on future MDB2 updates - luckily we found a simple solution: recompile PHP with the newest FreeTDS libraries and everything goes back to normal. The same script now outputs:
2009年09月29日 18:11:00
Even though mssql.datetimeconvert = 0 thanks to MDB2, we are receiving standard Y-m-d H:i:s format we love so much. Hope that helps anyone with similar problem.
Thursday, September 24, 2009
Using shared SVN working copy on samba
We use Subversion as our VCS of choice so we simply kept working copies on staging server and used TortoiseSVN to do an svn update. Later on we would e.g. migrate the database, update configuration files etc. This proved to be very simple and effective technique.
However,when our administration changed authentication procedures and gave every developer separate account (we used a single user name to log in to our samba share before, now we authorize through NT domain) - it suddenly stopped being so effective.
As soon as one developer updated the staging working copy, he started "owning it". Anyone else trying to update it later on got access denied errors. The reason? Subversion client created files in .svn that are read-only and these could not be deleted and recreated by anyone else (and Subversion client does this during svn update).
Luckily, there is an easy solution, found here and also mentioned in SVN FAQ. In our case, inserting:
in smb.conf solved our problem. Any developer that is authenticated to use our staging share may now update the working copy.[global] delete readonly = yes
Disclaimer: Although using working copy on samba share is not recommended by Subversion team, we use this technique for years now with different server and client versions and this was our first problem. However - we don't do development on this working copy, we only update it, so it's not a real full-blown multiuser working copy.
Thursday, September 17, 2009
HTTP File server released
Problem
Imagine a situation where your application has to store and retrieve files on the web (i.e. not on a local filesystem). You have many options - you may upload them to FTP server, e-mail them, use some file hosting services like Dropbox, upload files using a HTML form, use WebDAV server. Finally you may mount some remote filesystem like NFS.All of these options are valid, but they all carry certain amount of requirements that may not always be met:
- To use FTP, you need to set up a remote FTP server, have an implemented FTP client in your language of choice and the ability to open FTP connections on the system you're using.
- To use e-mail you need to be able to handle POP3 and SMTP protocols and have a mail server set up.
- WebDAV, although convenient, is hard to set up in the first place. The protocol itself takes some time to implement.
- Using any other web application like Dropbox requires you to have a client for their services and you need to accept the licence restrictions.
- HTML form - an excelent choice. If you're doing the uploads manually, you may write a simple script in minutes - but what if you want to upload files automatically (e.g. in a batch script)? You need to make a HTTP request with form and the file within encoded, you have to deal with mime-types, encoding file contents etc. Not really fast to implement.
- Mounting remote filesystem is impossible on a shared Linux server, or Windows server.
Solution
HTTP File server to the rescue. This small little fellow, written in PHP5 is a simple REST-oriented file server with minimal requirements:- PHP 5 (5.2 I suppose)
- web server (Apache will do)
- writable directory (this is where your files will be stored)
Example
Example usage:# store file on server - use HTTP POST wget --post-file=file_to_send.txt http://server/index.php/path/to/store/file.txt -O - # retrieve file - use HTTP GET wget http://server/index.php/path/to/store/file.txtThat's pretty much it. The server is so simple, it doesn't (yet?) offer even the ability to list directory contents. All it does is store files and retrieve them.
Download
Download HTTPFileServer and take a look for yourself. Your comments are welcome. The project is BSD licensed.Tuesday, March 10, 2009
jQuery hijack plugin - nice addition to jQuery UI 1.7
I have just published a jQuery plugin that I've used with great success on many of my last projects - jQuery hijack. What hijacking is and why is it of any importance?
The amazing world of widgets
When loading a widget on a page, say tab or dialog, we are often loading its content from another URL via AJAX. This is a common technique and nothing new - we may e.g. use jQuery.load() or jQuery.tabs() from Jquery UI to achieve this. Let's say we are loading a table containing a product list to a tab. In this table we have some columns so we can sort it by clicking on a column header and page the results by using the pager links we developed.
What happens when we click on any link used to e.g. sort or go to next page in our loaded content? It replaces the whole page. The same thing happens when we have e.g. a search form within our tab content and we submit it. Although completely understandable (and there are many ways to avoid it), it's not exactly the best behavior. What can help you - is hijacking. jQuery hijack plugin was designed exactly to come to your rescue.
Hijacking to the rescue
Hijacking or hijaxing is a term used by Chris Thatcher a long time ago in a jQuery UI thread, where he proposed a way of capturing all the links within a widget content and making them reload only that widget. And this is exactly the core functionality of jQuery hijack plugin.
By using the plugin, we can call a simple one function jQuery.hijack() and voila - from now on, all links and forms are hijacked - so paging links in the tab will simply display another page of results in this tab, search form will also display results inline - everything requires only one line of code (usually).
More info
The plugin works flawlessly with jQuery UI widgets, like tabs or dialogs, I also heavily used it with jqModal plugin. This 1KB plugin also allows you to:
- skip hijacking some forms/links
- use click() handlers for links to skip following them at all
- skip submitting forms (validation)
- always run a particular function after reloading content (e.g. to init some objects)
I've created a demonstration page for plugin features where you can see it in action with jQuery UI. You may download the plugin at its Google code page. The plugin is dual licensed under MIT/GPL licenses.
Wednesday, March 4, 2009
Javascript: The Good Parts presentation
A few days ago Douglas Crockford gave a presentation at Google Tech Talks and it has just been published at Google Tech Talks Youtube channel.
Youtube linkDouglas Crockford is one of my personal gurus when it comes to Javascript. Currently at Yahoo, he's the author of JSON data-interchange format, Javascript verifier JSLint and many books on Javascript. He seems to know more about Javascript internal workings than any other person I am aware of.
In this presentation Douglas speaks about some good parts of Javascript:
- lambda
- dynamic objects
- loose typing
- object literals
and mentions its bad parts, like e.g.:
- global variables
- with statement
- semicolon insertion by JS parser
- null, undefined, false and NaN
The talk is full of examples of tricky JS code, he also describes his famous Module pattern for easy creation of objects with private and public properties and methods. You could also learn what semicolon insertion exactly is and why
return
{
ok: false
};
does something you'd rather not expect. Douglas describes his personal experience with Javascript in his career and talks about works on next version of ECMAScript standard (ES3.1).
Watching the talk he gave is definately a well-spent time for a Javascript developer. If you've never heard of Douglas, check out his webpage for more information and be sure to check out JSLint - it's a tool that verifies pasted Javascript code and warns about poor programming practices used. Like Douglas said - it WILL hurt your feelings badly ;)
Monday, March 2, 2009
jQuery optionTree demo
Update: Version 1.2 of the plugin is now capable of loading trees via AJAX - this is not possible to demonstrate on this blog, so I moved the demonstration to a separate site - see the new demo page .
This jquery plugin converts passed JSON option tree into dynamically created SELECT elements allowing you to choose one nested option from the tree.
It should be attached to a (most likely hidden) INPUT element. It requires an option tree object. Object property names become labels of created select elements, each non-leaf node in the tree contains other nodes. Leaf nodes contain one value - it will be inserted into attached INPUT element when chosen.
The plugin also supports loading additional levels via AJAX calls and preselecting given items at load.
Example 1
<input type="text" name="demo1" />
var option_tree = {
"Option 1": {"Suboption":200},
"Option 2": {"Suboption 2": {"Subsub 1":201, "Subsub 2":202},
"Suboption 3": {"Subsub 3":203, "Subsub 4":204, "Subsub 5":205}
}
};
$('input[name=demo1]').optionTree(option_tree);
Example 2 - change event and configuration
<input type="hidden" name="demo2" />
var option_tree = {
"Option 1": {"Suboption":200},
"Option 2": {"Suboption 2": {"Subsub 1":201, "Subsub 2":202},
"Suboption 3": {"Subsub 3":203, "Subsub 4":204, "Subsub 5":205}
}
};
var options = {empty_value: -1, choose: '...'};
$('input[name=demo2]').optionTree(option_tree, options)
.change(function() { alert('Field ' + this.name + ' = ' + this.value )});
Example 3 - preselected options
<input type="hidden" name="demo3" />
var option_tree = {
"Red": {"Default":100},
"Blue": {"Variant 1": {"Default":100, "Another":101},
"Variant 2": {"Default":100, "Another":102, "and another":103}
}
};
var options = {preselect: {'demo3': 100}}; // value for default option (include field name)
$('input[name=demo3]').optionTree(option_tree, options)
.change(function() { alert('Field ' + this.name + ' = ' + this.value )});
More info
Plugin is dual licensed undel MIT / GPL licenses.Additional information about this plugin is available on its google code pages. You may download the plugin from there or from its jQuery plugin site. It has been tested with jQuery 1.3 and 1.4. Feel free to comment on the plugin and suggest additional features on google code project site.