De failles de sécurité en faille de sécurité le monde du web a du faire face et trouver des moyens d’éviter, au mieux, que de nouvelles failles apparaissent. Cela passe d’abord par le fait de réagir vite et de faire évoluer rapidement les composants (Langages, Frameworks, librairies etc.) mais aussi par le fait de contrôler l’origine des éléments exécutable sur un site : c’est justement l’objectif des CORS.
Présentation
Quand on parle des CORS, Cross-Origin Resource Sharing, on parle des « [mode de] partage des ressources entre origines multiples ». On peut aussi évoquer « les mécanisme de contrôle du partage des ressources inter-sites ».
Etat des lieux des requêtes HTTP utilisant les CORS depuis les pages du navigateur (donc côté client) :
- Les polices web via
@font-faceen CSS, - Les textures WebGL,
- Les frames dessinées sur un canevas avec
drawImage, - Les feuilles de style,
- Les scripts,
- et lors de l’utilisation des API
XMLHttpRequestou Fetch
Pour des raisons de sécurités, les requêtes issus de scripts définissent par défaut un mode de partage des ressources entre origine multiples restreint uniquement au site d’origine de la demande : en clair, un site « monsite.com » utilisant un objet de script pour récupérer des données ne pourra pas envoyer une requête sur « lesitedunautre.fr ». On dit qu’est respecté la règle d’origine unique.
Ainsi,
Developer.mozilla.org, mdn web docsXMLHttpRequestet l’API Fetch respectent la règle d’origine unique. Cela signifie qu’une application web qui utilise ces API peut uniquement émettre des requêtes vers la même origine que celle à partir de laquelle l’application a été chargée, sauf si des en-têtes CORS sont utilisés
Les Entetes HTTP
Pour comprendre comment peut s’opérer une restriction qui sécuriserait l’accès à une ressource, il faut d’abord voir comment sont organisé les requete HTTP.
Requêtes simple
Dans le cas d’une requête POST par exemple une partie « Body » contiendra les données envoyée tandis qu’une partie « Header comprendre que les requêtes HTTP émanant de l’utilisateur ont un ensemble de paramètre qui peuvent être spécifiés dont parmi elle :
//Requete du navigateur
Origin: http://monsite.com
//Aucune implémentation : c'est le navigateur qui fournit l'information
C’est l’entête Origine qui va permettre au serveur d’identifier l’origine de la requête HTTP.
Côté serveur, une requête HTTP est renvoyé pour informé le navigateur. Parmi les nombreuses entêtes existantes, on va retrouver
//Reponse du serveur
Access-Control-Allow-Origin: * OU http://monsite.com
//Implementation PHP :
if (isset($_SERVER['HTTP_ORIGIN'])) {
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}"); //ou *
}
//Implémenatation Node.js (Express)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
});
L’entête spécifie quel domaine est autorisé à accéder à la ressource. * signifie tous, mais il est possible que de spécifié un ou des noms de domaine particulier.
Requêtes précédées de requêtes préliminaires (preflight)
Dans le cas de certaines requêtes HTTP qui ne respectent pas les normes « standards » (notamment celles ayant une méthode PUT/DELETE/PATCH ou celles envoyant du json), le navigateur est contraint d’envoyer une requête « préliminaire » (ou preflight).
C’est la requête de méthode « OPTION » : elle aura comme rôle de demander une autorisation explicite pour la requête à venir. Dans le cas d’une requête attendant
//Requete du navigateur
OPTIONS /resources/post-here/ HTTP/1.1
(...)
Origin: http://monsite.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
//Aucune implémentation : c'est le navigateur qui fournit l'information
Les requêtes préliminaires contiennent un ensemble d’entêtes « Acces-Control-Request-abcdef » qui permettent de spécifier des informations concernant la requête à venir.
En retour le serveur renvoi une entête contenant plusieurs informations dont :
Access-Control-Allow-Origin: http://monsite.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Max-Age: 86400
//Implementation PHP :
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS')
{
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
/*if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers:
{$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}")*/
header('Access-Control-Max-Age: 86400'); // cache for 1 day
exit(0);
}
Il y précise les méthodes autorisées, le header non-standard autorisé et le temps en seconde (ici 24h) pendant laquelle cette requête préliminaire est valable (ainsi elle ne devra plus être obligatoire pour la même requête qui suivra). A savoir que chaque navigateur à sa propre valeur « Access-Control-Max-Age » qui prendra le dessus si elle est plus courte, à des fins de sécurité).
Mise en garde
Les requêtes préliminaires n’ayant pas eu initialement la possibilité d’être redirigé, il se peut donc que dans ce cas le navigateur affiche un message d’erreur. Il faudra donc dans ce cas qu’elle soit dirigée directement vers le serveur final ou que, sur le serveur appelé, soit modifié le comportement pour éviter la requête préliminaire)
Requête avec authentification
Il est possible, pour obtenir un cookie côté serveur par exemple, d’indiquer une requête avec authentification.
//L'objet XmlHttpRequest proposera la propriété withCredentials à mettre à true.
xmlHttpRequest.withCredentials = true;
Le serveur devra par contre automatiquement renvoyer ces éléments dans la requete pour que le navigateur accepte d’utiliser ses données :
Access-Control-Allow-Origin: http://monsite.com ici * est interdit dans ce cas
Access-Control-Allow-Credentials: true
//Implementation PHP :
if (isset($_SERVER['HTTP_ORIGIN']))
{
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
}
Une question de sécurité
Contrôler avec finesse les headers permettra d’autoriser ou non l’accès à certaine ressources et ainsi d’assurer la sécurité du site : les failles CSRF, qui consiste notamment à faire utiliser un lien « hors du site source » pour qu’un utilisateur avec des droits importants intervienne de manière dangereuses sur le site source, pourraient ainsi être empêcher.
Sources
- https://fetch.spec.whatwg.org/#http-cors-protocol
- https://developer.mozilla.org/fr/docs/Web/HTTP/CORS
- https://stackoverflow.com/questions/8719276/cross-origin-request-headerscors-with-php-headers