1

I set up a server on http://localhost:8080 where http://example.com can do POST requests :

'use strict';

const express = require('express');

const app = express();
const port = 8080;

// allowing CORS for example.com
app.use('/', function (req, res, next) {
    res.header('Access-Control-Allow-Origin', 'http://example.com');
    if (req.method === 'OPTIONS') {
        res.header('Access-Control-Allow-Methods', 'OPTIONS, POST');
        res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length');
        res.status(200).send();
    } else {
        next();
    }
});

// handling POST requests
app.use('/', function (req, res) {
    console.log('a client did a POST request');
    res.status(200);
});

app.listen(port, () => console.log ('server started on port ' + port));

It works fine : I can't do a POST request to http://localhost:8080 from http://localhost:8081 because of the same origin policy.

Then I wrote a web extension for Firefox that will try to do a POST request to http://localhost:8080 from any domain.

Here is its manifest :

{
    "manifest_version" : 2,
    "name" : "aBasicExtension",
    "version" : "0.0.0",
    "content_scripts" : [
        {
            "matches" : ["<all_urls>"],
            "js" : ["content-script.js"]
        }
    ],
    "permissions" : ["*://*.localhost/*"]
}

and its content-script.js code :

(() => {

    'use strict';

    const xhr = new XMLHttpRequest();

    xhr.open('POST', 'http://localhost:8080');
    xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');

    xhr.addEventListener('readystatechange', () => {
        if (xhr.readyState === XMLHttpRequest.DONE){
            if (xhr.status === 200) console.log('OK');
            else console.error('an error has occured : ' + xhr.status);
        }
    });

    xhr.send(JSON.stringify({dataName: 'some data here'}));

})();

What I don't understand is that it works. The extension do the request to http://localhost:8080 and Firefox didn't block it because the manifest allows it, however the server (http://locahost:8080) didn't give his permission.

Cl00e9ment
  • 423
  • 5
  • 16

1 Answers1

4

Short version: CORS is a protocol for controlling the behavior of the browser, not the server. And your use of the addon permissions setting bypasses CORS.

If you look at your CORS code you'll see that it doesn't do anything to reject requests; it just sets headers on the response. Those headers will instruct the browser whether or not the client can read the response, but the response will be sent in any case.

This fact can be obscured by certain requests that force CORS preflights. In that case, the browser first sends a special OPTIONS request, and the headers attached to that response can keep the browser from sending the real request. This is a backwards-compatibility mechanism that doesn't apply to all requests. (See this answer for a longer explanation.)

That's what's happening in your example. Your POST is of a type that requires a preflight check under CORS. So in the regular version, the browser sends a preflight check, sees the response headers, and doesn't bother to send the real request. But if it had been a different kind of POST it would have sent the request directly, and the server would have executed it.

In the addon version, you specifically allowed this domain in your permissions setting. This bypasses CORS:

The extra privileges include: XMLHttpRequest and fetch access to those origins without cross-origin restrictions (even for requests made from content scripts).

So in this case the preflight isn't required and the request is sent directly.

If you want to reject requests on the server that come from certain domains (or protect against CSRF more generally), there will be other settings for that. What they are depends on your web framework.

Kevin Christopher Henry
  • 37,093
  • 5
  • 98
  • 87
  • Thanks a lot for this detailed explanation. Is this behaviour a security issue? Indeed, if I write an evil addon wich do a request to my server when you browse your mail website. The cookies of the this website will be sent to my server. Should I conclude that a xhr sent by a content script will not include the website cookies? – Cl00e9ment Oct 27 '18 at 20:46
  • @Cl00e9ment: I don't know anything about Firefox addons so you should probably ask that specific question separately. But generally, yes, addons have expanded capabilities that allow them to do evil things. That's why the user has to explicitly choose to install them, and agree to give them specific permissions. – Kevin Christopher Henry Oct 27 '18 at 21:22
  • 1
    I just tested that and no, when a content script do a xhr, the website cookies aren't sent. That explains why the addons can bypass the same origin policy. – Cl00e9ment Oct 27 '18 at 21:35