Avoir plusieurs applications frontales communiquant avec un backend commun via des API est désormais un modèle de développement Web typique. Mais qui est responsable de la vérification des entrées utilisateur malveillantes lors de l’envoi de requêtes aux API ? En fin de compte, le fait de ne pas nettoyer les données sur le frontend peut permettre des attaques de script intersite via des API.

La forte dépendance aux API Web comme principal moyen d’échange de données dans les applications Web et mobiles a des implications majeures en matière de sécurité pour prévenir les attaques par injection telles que les scripts intersites (XSS). Avec l’évolution des limites architecturales et des responsabilités, il est trop facile de supposer que la corvée de nettoyage des entrées des utilisateurs est gérée ailleurs – espérons-le sur le serveur. Voyons comment cela peut rendre les applications vulnérables à XSS via des appels d’API.

La forte dépendance aux API Web comme principal moyen d’échange de données dans les applications Web et mobiles a des implications majeures en matière de sécurité pour prévenir les attaques par injection telles que les scripts intersites (XSS). Avec l’évolution des limites architecturales et des responsabilités, il est trop facile de supposer que la corvée de nettoyage des entrées des utilisateurs est gérée ailleurs – espérons-le sur le serveur. Voyons comment cela peut rendre les applications vulnérables à XSS via des appels d’API.

Avant qu’il y ait des API, il y avait côté serveur

Le Web moderne est passé d’applications Web monolithiques uniques à des systèmes multicouches plus sophistiqués avec une séparation claire entre le front et le backend. À l’époque des sites Web principalement statiques, une application Web était suffisamment interactive lorsqu’elle comportait des formulaires permettant au backend de traiter les entrées dynamiques soumises par l’utilisateur. Inutile de dire que les perceptions et les attentes de ce qui rend un site Web dynamique ont radicalement changé depuis lors.

Les applications générées uniquement côté serveur ne pouvaient pas fournir ce qui est attendu aujourd’hui, comme des pages Web avec des animations fluides, une entrée dynamique avec des réponses instantanées et des fonctionnalités pratiques telles que la saisie semi-automatique dans les barres de recherche ou l’ouverture de pages préchargées en un clin d’œil. Parce qu’ils manquaient d’une séparation claire entre le front et le backend, ils étaient également sujets à des problèmes si plusieurs personnes travaillaient simultanément sur différentes parties de l’application et gênaient généralement les workflows de développement efficaces.

D’un autre côté, le fait d’avoir tout le traitement côté serveur limitait la surface d’attaque globale, en particulier pour les scripts intersites (XSS) . Le navigateur de l’utilisateur n’affichait que ce qu’il recevait du serveur et soumettait ce que l’utilisateur avait mis, de sorte que vous pourriez (du moins en théorie) effectuer toutes les validations et désinfections des entrées sur le serveur pour centraliser votre protection XSS. Mais la pression était forte pour rendre les applications Web plus réactives et interactives, nécessitant plus de traitement côté client, ce qui signifie que le navigateur devrait générer et exécuter beaucoup de code indépendamment du serveur.

Applications côté client, XSS côté client

Cue les applications monopage (SPA) et les frameworks nécessaires à leur création, tels que React et Vue. De par leur conception, les SPA sont des applications autonomes qui s’exécutent dans le navigateur sans nécessiter de traitement côté serveur. Vous pouvez les servir comme n’importe quelle page Web statique. Ils communiquent avec le backend via son interface de programmation d’application (API), qui présente de nombreux avantages par rapport à l’ancienne approche.

Par exemple, vous pouvez développer indépendamment du code front et backend sans attendre que l’autre équipe ait terminé. Il vous suffit de vous mettre d’accord en phase de conception sur le type et la structure des données que vous souhaitez envoyer ou recevoir et vous pouvez ensuite tester et développer des composants front et backend de manière indépendante. Ceci est extrêmement pratique et efficace, mais s’accompagne d’une mise en garde majeure liée à la sécurité. Le fardeau de la désinfection des entrées pour empêcher les attaques de script intersite ne repose plus sur les développeurs backend – c’est maintenant l’équipe frontend qui doit le faire.

En général, lorsque vous envoyez des données au client dans des API modernes, il n’est pas nécessaire de les nettoyer contre les scripts intersites sur le backend. Les données sont généralement codées en JSON, ce qui ne permettra pas le rendu du HTML (du moins dans les API correctement conçues). De plus, à ma connaissance, il n’y a pas de fonction JavaScript native dans les navigateurs qui vous permette de décoder les entités HTML, donc même si le backend vous a envoyé une chaîne encodée par une entité, vous ne pouvez rien faire avec en plus d’afficher sur le directement la page, sauf si vous avez eu recours à des solutions de contournement ou à des solutions tierces.

Les concepteurs de frameworks comme React ont remarqué ce problème, donc ces frameworks frontaux nettoieront la sortie par défaut. Il est donc extrêmement difficile (mais pas impossible) d’introduire une vulnérabilité XSS dans une application React. Nous en avons parlé dans un article de blog sur les scripts intersites dans les applications React et avons conclu que vous êtes assez en sécurité tant que vous n’essayez pas de faire quoi que ce soit qui sorte de l’ordinaire.

Trop de créativité nuit à la sécurité

Inévitablement, vous rencontrerez parfois des cas où vous devrez résoudre un problème spécifique, et soit vous ne connaissez pas assez bien le framework pour le résoudre, soit la méthode intégrée manque de certaines fonctionnalités cruciales dont vous avez besoin – et vous commencez alors à faire preuve de créativité. Bien que la créativité soit un bon trait à avoir, elle est rarement recommandée lorsque vous souhaitez éviter les vulnérabilités de script intersite (ou toute vulnérabilité, d’ailleurs). Un exemple pourrait être la mise en œuvre d’une fonctionnalité d’une manière qui donne à un utilisateur le contrôle sur les propriétés d’un élément React. En fonction de l’élément spécifique et des types et structures de données que vos utilisateurs sont autorisés à transmettre à l’application, cela peut créer des vulnérabilités XSS même dans des applications React autrement sécurisées.

Il en va de même pour les redirections. La façon dont les redirections fonctionnent dans React est un peu maladroite et dépend fortement du routeur que vous utilisez, jusqu’à la version spécifique. Dans la vraie vie, il est peu probable que les développeurs apprennent tous les coins et recoins d’une bibliothèque ou d’un framework pour effectuer des redirections exactement comme un architecte de framework l’a spécifié dans un document de 6 pages (avec une liste détaillée des avantages). Surtout s’ils savent qu’ils devront tout réécrire dès que la prochaine itération de la bibliothèque de routeurs sera publiée. Ainsi, à la place, les développeurs pourraient très bien décider d’utiliser document.location comme tout le monde, et je ne peux pas vraiment dire que je les blâme.

Il y a cependant quelques problèmes avec cette approche. Tout d’abord, il s’accompagne de quelques pénalités de performance car au lieu de charger un nouvel itinéraire, le navigateur naviguera vers une page comme d’habitude et rechargera la page entière. Ainsi, vous pourriez avoir l’intention de ne rediriger les utilisateurs que vers une page locale – mais vous pourriez plutôt créer un moyen de les rediriger vers un autre site Web ou (pire encore) une URL arbitraire, conduisant à une vulnérabilité XSS.

Et encore une fois, alors que dans le passé, la prévention des redirections malveillantes relevait généralement de la responsabilité des développeurs backend, l’utilisation accrue des API et des applications côté client a fermement déplacé cette responsabilité vers le frontend. Ou, comme le dit l’ OWASP :

Les frameworks JavaScript, les applications d’une seule page et les API qui incluent dynamiquement des données contrôlables par un attaquant sur une page sont vulnérables à DOM XSS.Idéalement, l’application n’enverrait pas de données contrôlables par l’attaquant à des API JavaScript non sécurisées.

Plus d’interfaces, plus d’opportunités XSS

Un autre avantage de séparer le frontend du backend et de les faire communiquer via des API est que les frontends ne sont plus liés à une technologie ou même à une plate-forme spécifique, comme le Web ou le mobile. Tant qu’elle peut communiquer avec votre API backend, n’importe quelle application sur n’importe quelle plate-forme peut utiliser vos données et vos fonctionnalités backend.

Supposons que vous souhaitiez développer une application mobile basée sur votre site Web mais plus facile à utiliser et apportant certaines fonctionnalités qui ne sont disponibles que dans une application, telles qu’une navigation plus rapide, un accès facile aux fichiers sur votre appareil ou une authentification dans l’application avec un lecteur d’empreintes digitales. Cela pourrait être très attrayant et augmenter l’adoption de votre service. Cependant, si vous écriviez votre application Web en utilisant l’ancienne approche, vous n’auriez aucun moyen facile de récupérer vos données car elles seraient probablement mélangées entre des morceaux aléatoires de HTML et dispersées dans des fichiers.

Avoir une API backend vous permet de profiter des avantages du développement front et backend séparés, quelle que soit la technologie frontend – mais il y a un problème de sécurité. Comme mentionné précédemment, avec l’ancienne approche d’une seule application monolithique, vous pouvez simplement empêcher XSS d’être centralisé sur le backend et en finir. Désormais, la désinfection doit être effectuée individuellement pour chaque application frontale. De plus, l’impact d’une vulnérabilité exploitée avec succès variera en fonction de la technologie frontale et des fonctionnalités exposées par une API.

Suivi des utilisateurs mobiles via XSS

Examinons un exemple spécifique de XSS dans une application mobile hybride et voyons l’impact potentiel, y compris l’exécution de code à distance (RCE) dans le pire des cas. Bien que RCE sur un appareil mobile ne soit pas aussi mauvais que sur un ordinateur (en raison du sandboxing et des faibles privilèges d’application), il peut toujours permettre le vol de données personnelles, telles que les informations d’identification et les fichiers locaux, et peut-être même d’autres actions, en fonction des autorisations de l’application et des fonctionnalités exposées.

Il existe un excellent article de blog qui explique l’un de ces problèmes, en l’illustrant avec un exemple d’application Android écrite en JavaScript à l’aide du framework Capacitor. Entre autres choses, Capacitor vous permet d’exécuter une application Web moderne sur Android en utilisant WebViews pour afficher des pages. Vous pouvez considérer une WebView comme une iframe mais des applications mobiles – pas un navigateur intégré à part entière, mais toujours un moyen d’intégrer des pages Web dans une application mobile.

L’avantage est que vous pouvez accéder à des API puissantes, natives et spécifiques à la plate-forme via une interface JavaScript. Et, apparemment, Capacitor expose automatiquement certaines de ces fonctionnalités natives via un Capacitor.Pluginsobjet accessible au code JavaScript dans une WebView.

Imaginons donc qu’il existe une WebView affichant des recettes de gâteaux créées par l’utilisateur, et chaque recette peut être récupérée via une API. Très pratique, mais disons que le titre de la recette est défini de manière non sécurisée, comme l’utilisation de innerHTML, ce qui entraîne une vulnérabilité de script intersite.

Et bien que ce titre ne semble pas particulièrement appétissant, il permet à un attaquant d’exécuter du code dans le contexte de WebView en raison du code HTML et JavaScript injecté. Chaque fois qu’il s’exécute, par exemple lorsqu’il est affiché dans le flux d’un autre utilisateur, il obtiendra la position actuelle de cet utilisateur et l’enverra à un serveur contrôlé par l’attaquant. En termes d’impact, cela ne peut guère être pire que cela.

Le titre a été correctement récupéré par une API telle que fournie par le serveur d’applications, de sorte que la vulnérabilité a été causée uniquement par cette application frontale spécifique. Dans d’autres circonstances, le fait qu’une chaîne brute non codée ait été fournie via l’API JSON peut ne pas être un problème de sécurité. Si la même application Web a été écrite en tant qu’application à page unique et qu’elle a récupéré la chaîne fournie via la même API, mais en utilisant une implémentation frontale plus sécurisée ou des valeurs d’encodage sécurisées par défaut, elle n’a peut-être pas eu cette vulnérabilité.

Les applications distribuées nécessitent une désinfection des entrées distribuée

La conclusion est qu’il existe de nombreuses bonnes raisons d’envoyer des données via une API et de les récupérer côté client. Cette capacité est ce qui a permis un développement rapide piloté par API sur les plateformes Web et mobiles. Mais du côté de la sécurité, les développeurs doivent être parfaitement conscients que cette méthodologie transfère la responsabilité d’empêcher XSS des développeurs backend aux développeurs et aux responsables de chaque application frontale individuelle qui utilise les données de l’API – chacune, que ce soit sur ordinateur, mobile, Web ou toute autre plate-forme suivante. Chaque plate-forme a des besoins et des objectifs différents, de sorte que les vulnérabilités de sécurité frontales peuvent entraîner toutes sortes de problèmes, même aussi graves que l’exécution de code arbitraire ou le suivi d’utilisateurs individuels.

Vous ne pouvez plus espérer empêcher les vulnérabilités XSS dans une seule ligne de code sur le serveur et en finir avec. Au lieu de cela, vous devez vous assurer que chaque composant individuel et chaque interface de votre application moderne est sécurisé et prend soin de nettoyer les entrées des utilisateurs si nécessaire.