Enabling CORS on a node.js server, Same Origin Policy issue

Recently we faced the famous “XMLHttprequest doesn’t allow Cross-Origin Resource Sharing” error.

To overcome the problem a very simple solution was needed.

Below I’ll try to give a quick overview of what is CORS and how we managed to work around the issue.

Cross-Origin Resource Sharing – CORS

In a nutshell CORS is the mechanism that allows a domain to request resources from another domain, for example, if domain http://websiteAAA tries to request resources from http://websiteBBB the browser won’t allow it due to Same Origin Policy restrictions.

The reason for having Same Origin Policy rules applied on the browser is to prevent unauthorized websites accessing content they don’t have permissions for.

I found a great example that emphasizes the need to have Same Origin Policies enforced by the browser: Say you log in to a service, like Google for example, then while logged in you go and visit a shady website that’s running some malware on it. Without Same Origin Policy rules, the shady website would be able to query Google with the authentication cookies saved in the browser from your session, which of course is a huge vulnerability.

Since HTTP is a stateless protocol, the Same Origin Policy rules allow the browser to establish a connection using session cookies and still keep each cookie private to the domain that made the request, encapsulating the privileges of each “service” running in the browser.

With that being said, imagine a situation where you, as a developer, need to communicate with an API sitting on a different domain. In this scenario you don’t want to hit the Same Origin Policy restrictions.

Workaround 1 – Request resources from a server

The most common way to get around this problem is to make the API request from your own server, where Same Origin Policy rules are not applied, and then provide the data back to the browser. However, this can be exploited:

Last semester I created an example of how an attacker would be able to spoof whole websites and apply a phishing attack circumventing Same Origin Policy restrictions.
The attack structure was very similar of how ARP poisoning is done.

A very brief overview of the attack:

  1. The user would land on a infected page
  2. The page would load a legitimate website by making a request from the attacker’s server where Same Origin Policies are not applied.
  3. The attacker would inject some code in the response to monitor the vicitms activity
  4. After the victm’s credentials were stolen he would stop the attack and redirect the user to the original requested page.

By spoofing the victim’s DNS it would make even harder to detect the attack, but even without DNS spoofing this approach would still catch some careless users.

All the code for the example is available on github
The attack was built on top of a nodeJS server and socketIO
The presentation slides for the attack can also be found here

Workaround 2 – JSONP

Another way to circumvent the problem is by using JSONP (JSON Padding). The wikipedia articles summarizes in a clear and simple way how JSONP works.

The magic of JSONP is to use a script tag to load a json file and provide a callback to run when the file finishes loading.

An example of using JSONP with jquery:

$.ajax({
url: "http://website.com/file.json
dataType: 'jsonp',
success: function (data) {
// Manipulate the response here
}
});

Even though making requests from your server or using JSONP can get around the Same Origin Policy restrictions it is not the best solution, which is why CORS started being implemented by the browser vendors.

With CORS a server can set the HTTP headers of the response with the information indicating if the resources can or can’t be loaded from a different origin.

If you are curious and want to snoop around looking into the HTTP response headers of a page, one way to do that is to use the developers tools that come with WebKit.
Below is a screenshot of all the resources loaded by the stack overflow home page.
Screen Shot 2013-02-14 at 6.34.24 PM

As you can see in the screenshot, the script loaded from `careers.stackoverflow.com/gethired/js` had the following HTTP header options appended to its response:

  • Access-Control-Allow-Headers
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Origin

That means that if you want to make an ajax call to `carrers.stackoverflow.com/gethired/js` from your own page, the browser will not apply Same Origin Policy restrictions since the `careers.stackoverflow` server has indicated that the script is allowed to be loaded from different domains.
*An important note to make is that only the `http://careers.stackoverflow.com/gethired/js` has the Same Origin Rules turned off, however, the `careersstackoverflow.com` domain still has them enabled on other pages.

This means you can enable the header options on a response level, making sure a few API calls are open to the public without putting your whole server in danger of being exploited.

This lead us to our problem.

The Problem

In the set up we currently have, one computer plays the role of the API server and we were trying to query that API asynchronously from the browser with a page being served from a different domain.

The results, as expected, were that the call was blocked by the browser.

Solution

Instead of hacking around and trying to make the requests from a different server or using JSONP techniques we simply added the proper header options to the responses of the API server.

We are building our API on a nodeJS server, and to add extra headers options to the response could not have been easier:

First we added the response headers to one of the API calls and it worked perfectly, however we wanted to add the option to all our API calls, the solution: use a middleware.

We created a middleware which sets the response header options and pass the execution to the next registered function, the code looks like this:

//CORS middleware
var allowCrossDomain = function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
}

app.configure(function () {
  app.set('port', config.interfaceServerPort);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(allowCrossDomain);
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

// API routes
app.get('/list/vms', routes.listGroup);
app.get('/list/vms/:ip', routes.listSingle);
app.get('/list/daemons', routes.listDaemons);

That’s it for CORS, later we’ll cover another cool header option, the X-Frame-Options

If you are interested in finding more about Same Origin Policy or CORS check out this links:
http://en.wikipedia.org/wiki/JSONP
http://geekswithblogs.net/codesailor/archive/2012/11/02/151160.aspx
https://blog.mozilla.org/services/2013/02/04/implementing-cross-origin-resource-sharing-cors-for-cornice/
https://developers.google.com/storage/docs/cross-origin
http://www.tsheffler.com/blog/?p=428
http://techblog.hybris.com/2012/05/22/cors-cross-origin-resource-sharing/
http://security.stackexchange.com/questions/8264/why-is-the-same-origin-policy-so-important
http://www.w3.org/TR/cors/
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
https://developer.mozilla.org/en-US/docs/Server-Side_Access_Control
http://www.bennadel.com/blog/2327-Cross-Origin-Resource-Sharing-CORS-AJAX-Requests-Between-jQuery-And-Node-js.htm

Solution: Glance Errors – OpenStack Folsom Basic Install Ubuntu Linux Test Deployment

I’ve been trying to follow this tutorial to set up a test deployment of OpenStack, and I’ve been having nothing but problems. At least one of them, however, was entirely my doing.

In this post (way down at the bottom) I’d talked about a couple of specific errors I was receiving when trying to test Glance:

glance image-list

Error communicating with /v1/images: [Errno 111] Connection refused

and

glance image-create 
	    --location http://uec-images.ubuntu.com/releases/12.04/release/ubuntu-12.04-server-cloudimg-amd64-disk1.img 
	    --is-public true --disk-format qcow2 --container-format bare --name "Ubuntu"

Error communicating with /v1/images: [Errno 110] Connection timed out 

Glance, as a rule, uses REST commands. Since this is an HTTP protocol, this means that the problem has something to do with an incorrectly configured network setting somewhere.

Stage 1: Test Glance using REST

To make sure Glance itself wasn’t the problem, we tested it by using the Links browser to do a direct RESTful API call to the Glance service:

links 'http://localhost:9292/v1/images/detail'

Which returned:

{"images": []}

The fact that we got anything back at all meant the API call was successful. Its result indicated that the default image store was empty.

Stage 2: Finding the problem

This really came down to due diligence.

The tutorial provided a script to populate the Keystone database with endpoints. It was written assuming I, the user, was following the networking specifics from the tutorial – like the IP ranges for the different networks. I wasn’t.

The beginning of the script looks like this:

#!/bin/sh
#
# Keystone Endpoints
#
# Description: Create Services Endpoints

# Mainly inspired by http://www.hastexo.com/resources/docs/installing-openstack-essex-20121-ubuntu-1204-precise-pangolin
# Written by Martin Gerhard Loschwitz / Hastexo
# Modified by Emilien Macchi / StackOps
#
# Support: openstack@lists.launchpad.net
# License: Apache Software License (ASL) 2.0
#


# MySQL definitions
MYSQL_USER=keystone
MYSQL_DATABASE=keystone
MYSQL_HOST=localhost
MYSQL_PASSWORD=password

# Keystone definitions
KEYSTONE_REGION=RegionOne
SERVICE_TOKEN=password
SERVICE_ENDPOINT="http://localhost:35357/v2.0"

# other definitions
MASTER="192.168.0.1"

Line 28 assigns the IP address of my controller node to a variable called master, only I’d changed the IP address of the controller node to something more suitable to my environment.

This meant I should have checked the script and changed this line to reflect the actual IP address of my controller node.

Stage 3: Solution

This was tedious because the script had already been successfully run (with the wrong settings). It had populated the Keystone “endpoints” table with what was now junk data.

First, we got rid of it using MySQL:

mysql -u glance -ppassword

mysql>> use keystone;           // Select keystone DB
mysql>> delete from endpoint;   // Purges data from 'endpoint' table
mysql>> select * from endpoint; // Confirms table is empty

And, really quickly, here are the commands to view databases and tables at the MySQL prompt:

mysql>> show databases; // Shows... databases.
mysql>> show tables;    // Shows... tables.

So after purging the table, I needed to replace the offending IP address in the script with the actual IP address of my controller node. Then, it was as simple as running the script again.

After doing that, it worked like a charm!

Solution: Ubuntu Linux Multiple Networks No Internet Connection

In a previous post I talked about some trouble with internet connectivity I’d had on an Ubuntu installation where I’d configured my /etc/network/interfaces file for multiple networks. Even though two of the four networks the computer was connected to had internet connections, the computer was using one of the static networks to try to connect to the web.

In short, I’d specified too many default gateways in my interfaces file, and had to comment out the ones referring to the static networks.

My interfaces file looked like this:

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet dhcp

# OpenStack - Management Network
auto eth1
  iface eth1 inet static
  address 192.168.100.2
  netmask 255.255.252.0
  gateway 192.168.100.1

# OpenStack - API/Public Network
auto eth2
  iface eth2 inet static
  address 192.168.200.2
  netmask 255.255.252.0
  gateway 192.168.200.1

# OpenStack - Data Network
auto eth3
  iface eth3 inet static
  address 192.168.220.2
  netmask 255.255.252.0
  gateway 192.168.220.1

My supervisor’s first throught was to check the routing with the command route -n, which produced:

CENSORED@openstack-controller:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.100.1   0.0.0.0         UG    100    0        0 eth1
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.100.0   0.0.0.0         255.255.252.0   U     0      0        0 eth1
192.168.200.0   0.0.0.0         255.255.252.0   U     0      0        0 eth2
192.168.220.0   0.0.0.0         255.255.252.0   U     0      0        0 eth3

This showed that the default gateway, highlighted on line 4, is the gateway for one of my static networks. We tried adding the proper gateway with route add default gw 192.168.200.1, which caused route -n to display:

CENSORED@openstack-controller:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.100.1   0.0.0.0         UG    0      0        0 eth1
0.0.0.0         192.168.200.1   0.0.0.0         UG    100    0        0 eth2
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.100.0   0.0.0.0         255.255.252.0   U     0      0        0 eth1
192.168.200.0   0.0.0.0         255.255.252.0   U     0      0        0 eth2
192.168.220.0   0.0.0.0         255.255.252.0   U     0      0        0 eth3

So now we had two default gateways, and still no connection.

We deleted the gateway for the static network (route del default gw 192.168.100.1) and the internet worked perfectly.

In the end we solved the problem by commenting out the “gateway” setting for each static network in the /etc/network/interfaces file (highlighted lines):

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet dhcp

# OpenStack - Management Network
auto eth1
  iface eth1 inet static
  address 192.168.100.2
  netmask 255.255.252.0
#  gateway 192.168.100.1

# OpenStack - API/Public Network
auto eth2
  iface eth2 inet static
  address 192.168.200.2
  netmask 255.255.252.0
  gateway 192.168.200.1

# OpenStack - Data Network
auto eth3
  iface eth3 inet static
  address 192.168.220.2
  netmask 255.255.252.0
#  gateway 192.168.220.1

After rebooting the only default gateway displayed by the route -n command was the one we wanted:

CENSORED@openstack-controller:/home/senecacd# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.200.1   0.0.0.0         UG    100    0        0 eth2
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.100.0   0.0.0.0         255.255.252.0   U     0      0        0 eth1
192.168.200.0   0.0.0.0         255.255.252.0   U     0      0        0 eth2
192.168.220.0   0.0.0.0         255.255.252.0   U     0      0        0 eth3

Our ping test confirmed the internet was back, and there was much merriment and rejoicing!