Nous exposons une API que les partenaires ne peuvent utiliser que sur les domaines qu'ils ont enregistrés chez nous. Son contenu est en partie public (mais de préférence pour être affiché uniquement sur les domaines que nous connaissons), mais est principalement privé pour nos utilisateurs. Alors:
Pour déterminer ce qui est affiché, notre utilisateur doit être connecté avec nous, mais cela est géré séparément.
Pour déterminer où les données sont affichées, une clé API publique est utilisée pour limiter l'accès aux domaines que nous connaissons, et surtout pour garantir que les données des utilisateurs privés ne sont pas vulnérables à CSRF .
Cette clé API est en effet visible par n'importe qui, nous n'authentifions pas notre partenaire d'une autre manière, et nous n'avons pas besoin de REFERER . Pourtant, il est sécurisé:
Lorsque notre get-csrf-token.js?apiKey=abc123
est demandé:
Recherchez la clé abc123
dans la base de données et obtenez une liste de domaines valides pour cette clé.
Recherchez le cookie de validation CSRF. S'il n'existe pas, générez une valeur aléatoire sécurisée et placez-la dans un cookie de session HTTP uniquement . Si le cookie existait, obtenez la valeur aléatoire existante.
Créez un jeton CSRF à partir de la clé API et de la valeur aléatoire du cookie, puis signez-le . (Plutôt que de conserver une liste de jetons sur le serveur, nous signons les valeurs. Les deux valeurs seront lisibles dans le jeton signé, c'est très bien.)
Définissez la réponse pour qu'elle ne soit pas mise en cache, ajoutez le cookie et renvoyez un script comme:
var apiConfig = apiConfig || {};
if(document.domain === 'expected-domain.com'
|| document.domain === 'www.expected-domain.com') {
apiConfig.csrfToken = 'API key, random value, signature';
// Invoke a callback if the partner wants us to
if(typeof apiConfig.fnInit !== 'undefined') {
apiConfig.fnInit();
}
} else {
alert('This site is not authorised for this API key.');
}
Remarques:
Ce qui précède n'empêche pas un script côté serveur de simuler une requête, mais garantit uniquement que le domaine correspond si un navigateur le demande.
La même politique d'origine pour JavaScript garantit qu'un navigateur ne peut pas utiliser XHR (Ajax) pour charger puis inspecter la source JavaScript. Au lieu de cela, un navigateur ordinaire ne peut le charger qu'en utilisant <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">
(ou un équivalent dynamique), puis exécutera le code. Bien entendu, votre serveur ne doit pas prendre en charge le partage de ressources inter-origines ni JSONP pour le JavaScript généré.
Un script de navigateur peut modifier la valeur de document.domain
avant de charger le script ci-dessus. Mais la même politique d'origine ne permet de raccourcir le domaine en supprimant des préfixes, comme la réécriture subdomain.example.com
juste example.com
, ou myblog.wordpress.com
à wordpress.com
, ou dans certains navigateurs , même bbc.co.uk
à co.uk
.
Si le fichier JavaScript est récupéré à l'aide d'un script côté serveur, le serveur recevra également le cookie. Cependant, un serveur tiers ne peut pas obliger le navigateur d'un utilisateur à associer ce cookie à notre domaine. Par conséquent, un jeton CSRF et un cookie de validation qui ont été récupérés à l'aide d'un script côté serveur ne peuvent être utilisés que par des appels côté serveur ultérieurs, pas dans un navigateur. Cependant, ces appels côté serveur n'incluront jamais le cookie utilisateur et ne peuvent donc récupérer que des données publiques. Ce sont les mêmes données qu'un script côté serveur pourrait extraire directement du site Web du partenaire.
Lorsqu'un utilisateur se connecte, définissez un cookie utilisateur de la manière que vous souhaitez. (L'utilisateur s'est peut-être déjà connecté avant la demande de JavaScript.)
Toutes les demandes d'API ultérieures au serveur (y compris les demandes GET et JSONP) doivent inclure le jeton CSRF, le cookie de validation CSRF et (s'il est connecté) le cookie utilisateur. Le serveur peut maintenant déterminer si la demande doit être approuvée:
La présence d'un jeton CSRF valide garantit que le JavaScript a été chargé à partir du domaine attendu, s'il est chargé par un navigateur.
La présence du jeton CSRF sans le cookie de validation indique une falsification.
La présence à la fois du jeton CSRF et du cookie de validation CSRF ne garantit rien: il peut s'agir d'une fausse requête côté serveur ou d'une requête valide d'un navigateur. (Il ne peut pas s'agir d'une demande d'un navigateur effectuée à partir d'un domaine non pris en charge.)
La présence du cookie utilisateur garantit que l'utilisateur est connecté, mais ne garantit pas que l'utilisateur est membre du partenaire donné, ni que l'utilisateur consulte le bon site Web.
La présence du cookie utilisateur sans le cookie de validation CSRF indique une falsification.
La présence du cookie utilisateur garantit que la demande en cours est effectuée via un navigateur. (En supposant qu'un utilisateur n'entre pas ses informations d'identification sur un site Web inconnu, et en supposant que nous ne nous soucions pas des utilisateurs qui utilisent leurs propres informations d'identification pour faire une demande côté serveur.) Si nous avons également le cookie de validation CSRF, alors ce cookie de validation CSRF était également reçu à l'aide d'un navigateur. Ensuite, si nous avons également un jeton CSRF avec une signature valide, etle nombre aléatoire dans le cookie de validation CSRF correspond à celui de ce jeton CSRF, puis le JavaScript pour ce jeton a également été reçu lors de la même demande antérieure au cours de laquelle le cookie CSRF a été défini, donc également à l'aide d'un navigateur. Cela implique également que le code JavaScript ci-dessus a été exécuté avant la définition du jeton et qu'à ce moment-là, le domaine était valide pour la clé API donnée.
Donc: le serveur peut désormais utiliser en toute sécurité la clé API du jeton signé.
Si à un moment donné le serveur ne fait pas confiance à la demande, un 403 interdit est renvoyé. Le widget peut répondre à cela en affichant un avertissement à l'utilisateur.
Il n'est pas nécessaire de signer le cookie de validation CSRF, car nous le comparons au jeton CSRF signé. Ne pas signer le cookie rend chaque requête HTTP plus courte et la validation du serveur un peu plus rapide.
Le jeton CSRF généré est valable indéfiniment, mais uniquement en combinaison avec le cookie de validation, donc efficacement jusqu'à la fermeture du navigateur.
Nous pourrions limiter la durée de vie de la signature du jeton. Nous pourrions supprimer le cookie de validation CSRF lorsque l'utilisateur se déconnecte, pour répondre à la recommandation OWASP . Et pour ne pas partager le nombre aléatoire par utilisateur entre plusieurs partenaires, on pourrait ajouter la clé API au nom du cookie. Mais même dans ce cas, il n'est pas facile d'actualiser le cookie de validation CSRF lorsqu'un nouveau jeton est demandé, car les utilisateurs peuvent naviguer sur le même site dans plusieurs fenêtres, partageant un seul cookie (qui, lors de l'actualisation, serait mis à jour dans toutes les fenêtres, après quoi le Le jeton JavaScript dans les autres fenêtres ne correspondrait plus à ce cookie unique).
Pour ceux qui utilisent OAuth, voir également OAuth et Widgets côté client , dont j'ai eu l'idée JavaScript. Pour une utilisation côté serveur de l'API, dans laquelle nous ne pouvons pas nous fier au code JavaScript pour limiter le domaine, nous utilisons des clés secrètes au lieu des clés API publiques.