Desarrollo y Mantenimiento de Sistemas Informáticos

4º. 1er cuatrimestre. Itinerario de Sistemas de la Información. Grado en Ingeniería Informática. ULL


GH Org   - GH Template Org   - GitHub Classroom   - Discussions   - Teams   - Campus ULL   - Students Activity   - Chat   - Google Meet

Cross-Origin Resource Sharing CORS: Second Part

CORS is a security mechanism that allows a web page from one domain or Origin to access a resource with a different domain (a cross-domain request).

CORS is a relaxation of the same-origin policy implemented in modern browsers. Without features like CORS, websites are restricted to accessing resources from the same origin through what is known as same-origin policy.

Origin

Origin includes the combination of protocol, domain, and port. This means

are actually different origins and thus impacted by same-origin policy.

In a similar way,

are also different origins. The path or query parameters are ignored when considering the origin.

The reason for The Same-Origin Policy

You, like many websites, may use cookies to keep track of authentication or session info. Those cookies are bounded to a certain domain when they are created. On every HTTP call to that domain, the browser will attach the cookies that were created for that domain. This is on every HTTP call, which could be for static images, HTML pages, or even AJAX calls.

This means when you log into https://examplebank.com, a cookie is stored for https://examplebank.com. If that bank is a single-page React app, they may have created a REST API at https://examplebank.com/api for the SPA to communicate via AJAX.

  1. Let’s say you browse to a malicious website https://evilunicorns.com while logged into https://examplebank.com.
  2. Without same-origin policy, that hacker website could make authenticated malicious AJAX calls to https://examplebank.com/api to POST /withdraw even though the hacker website doesn’t have direct access to the bank’s cookies.

This is due to the browser behavior of automatically attaching any cookies bounded to https://examplebank.com for any HTTP calls to that domain, including AJAX calls from https://evilunicorns.com to https://examplebank.com.

By restricting HTTP calls to only ones to the same origin (i.e. the browser tab’s domain), same-origin policy closes some hacker backdoors such as around Cross-Site Request Forgery (CSRF) (Although not all. Mechanisms like CSRF tokens are still necessary).

The Reasons for Cross-Origin Resource Sharing

There are legitimate reasons for a website to make cross-origin HTTP requests:

How CORS works

This is how a simple CORS request works:

  1. A browser tab open to https://www.mydomain.com initiates AJAX request GET https://api.mydomain.com/widgets

  2. Along with adding headers like Host, the browser automatically adds the Origin Request Header for cross-origin requests:

  GET /widgets/ HTTP/1.1
  Host: api.mydomain.com
  Origin: https://www.mydomain.com
  [Rest of request...]
  1. The server checks the Origin request header. If the Origin value is allowed, it sets the Access-Control-Allow-Origin to the value in the request header Origin.
     HTTP/1.1 200 OK  
     Access-Control-Allow-Origin: https://www.mydomain.com  
     Content-Type: application/json
     [Rest of response...]  
  1. When the browser receives the response, the browser checks the Access-Control-Allow-Origin header to see if it matches the origin of the tab. If not, the response is blocked. The check passes such as in this example if either the Access-Control-Allow-Origin matches the single origin exactly or contains the wildcard * operator.
    • A server that responds Access-Control-Allow-Origin: * allows all origins which can be a large security risk.
    • Only use * if your application absolutely requires it such as creating an open/public API.

The CORS npm module

If you want to avoid the blocking, the server that hosts the resource needs to have CORS enabled. What you can do on the client side (and probably what you are thinking of) is set the mode of fetch to CORS (although this is the default setting I believe):

fetch(request, {mode: 'cors'});

The mode option specifies the mode you want to use for the request, e.g., cors, no-cors, or same-origin.

However this still requires the server to enable CORS as well, and allow your domain to request the resource.

In Express we can use the module cors

$ npm install cors

If inside the app we use this middleware:

var express = require('express')
var cors = require('cors')
var app = express()

app.use(cors())

app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

To enable CORS for a Single Route we do:

var express = require('express')
var cors = require('cors')
var app = express()

app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

We can configure CORS:

var express = require('express')
var cors = require('cors')
var app = express()

var corsOptions = {
  origin: 'https://example.com',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only example.com.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

The origin option used in this example configures the Access-Control-Allow-Origin CORS header. Possible values:

Ejemplo: server en ULL-MII-SYTWS-1920/food-lookup-demo

Para entender mejor esta sección Véase la rama/branch: 10-crguezl-master-cors-01 del repositorio ULL-MII-SYTWS-1920/food-lookup-demo

Si en Client-js cambiamos el fetch para solicitar al server en 3001 que es donde escucha nuestro servidor:

function search(query, cb) {
  return fetch(`https://localhost:3001/api/food?q=${query}`, {
    accept: "application/json"
  })
    .then(checkStatus)
    .then(parseJSON)
    .then(cb);
}

Obtenemos una respuesta similar a esta:

Access to fetch at https://localhost:3001/api/food?q=r from origin https://localhost:3000 has been blocked by CORS policy:

No Access-Control-Allow-Origin header is present on the requested resource.

If an opaque response serves your needs, set the request’s mode to no-cors to fetch the resource with CORS disabled.

localhost/:1 Uncaught (in promise) TypeError: Failed to fetch

Usando el middleware cors arreglamos el problema:

const express = require("express");
const fs = require("fs");
const sqlite = require("sql.js");
const cors = require("cors");
const filebuffer = fs.readFileSync("db/usda-nnd.sqlite3");

const db = new sqlite.Database(filebuffer);

const app = express();

app.set("port", process.env.PORT || 3001);

// Express only serves static assets in production
if (process.env.NODE_ENV === "production") {
  app.use(express.static("client/build"));
}

const COLUMNS = [
  "carbohydrate_g",
  "protein_g",
  "fa_sat_g",
  "fa_mono_g",
  "fa_poly_g",
  "kcal",
  "description"
];

const corsOptions = {
  origin: 'https://localhost:3000',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.get("/api/food", cors(corsOptions), (req, res) => {
  const param = req.query.q;

  if (!param) {
    res.json({
      error: "Missing required parameter `q`"
    });
    return;
  }

  // WARNING: Not for production use! The following statement
  // is not protected against SQL injections.
  const r = db.exec(
    `
    select ${COLUMNS.join(", ")} from entries
    where description like '%${param}%'
    limit 100
  `
  );

  if (r[0]) {
    res.json(
      r[0].values.map(entry => {
        const e = {};
        COLUMNS.forEach((c, idx) => {
          // combine fat columns
          if (c.match(/^fa_/)) {
            e.fat_g = e.fat_g || 0.0;
            e.fat_g = (parseFloat(e.fat_g, 10) +
              parseFloat(entry[idx], 10)).toFixed(2);
          } else {
            e[c] = entry[idx];
          }
        });
        return e;
      })
    );
  } else {
    res.json([]);
  }
});

app.listen(app.get("port"), () => {
  console.log(`Find the server at: https://localhost:${app.get("port")}/`); // eslint-disable-line no-console
});

Cómo hacer un Ataque CSRF

CORS references