Attaques DoS : principes, types d'attaques, exploitations et bonnes pratiques sécurité

En 5 ans, le nombre d’attaques par déni de service (ou attaques DoS) a presque doublé. La résultante en est la paralysie de dizaines de millions de plateformes web et la perte de milliers voire de millions d’euros par les organisations victimes.

En effet, des entreprises comme Amazon ou GitHub ont déjà été impactées par ce type d’attaques. On peut également citer une des attaques les plus connues, MIRAI, qui a utilisé un botnet de près de cent mille machines détournées pour rendre indisponible les services de l’entreprise Dyn en 2016.

Les conséquences d’un déni de service peuvent aller d’un simple ralentissement d’une plateforme à l’indisponibilité totale d’un système d’information. Et la continuité des services étant un enjeu vital, il est important de prendre en compte et de prévenir ce risque.

Mais alors qu’est-ce qu’une attaque DoS, et comment s’en protéger ? C’est ce que nous allons voir dans la suite de cet article. Nous présenterons notamment les différences entre un déni de service (DoS) et un déni de service distribué (DDoS). Nous détaillerons également les différents types d’attaques avec quelques exemples concrets et expliciterons les principes et modalités de tests DoS lors d’un test d’intrusion.

En quoi consiste une attaque par déni de service (DoS) ?

Une attaque DoS (pour Denial of Service) est une attaque informatique qui a pour but de ralentir ou de rendre indisponible un serveur ou une ressource en général.

Les motivations à l’origine de ces attaques sont diverses. Dans la plupart des cas, les attaques par déni de service sont réalisées dans le but d’extorquer la victime et la motivation est donc financière. Cependant les attaquants peuvent être également motivés par des convictions idéologiques, politiques ou personnelles.

Il existe plusieurs vecteurs pour réaliser ce type d’attaque mais il est important dans un premier temps, de comprendre la différence entre un DoS et un DDoS.

Quelle est la différence entre attaques DoS et DDoS ?

La différence principale entre un DoS et un déni de service distribué, aussi appelé DDoS (Distributed Denial of Service), repose sur le fait que pour une attaque DoS, la victime est attaquée par un unique système.

À contrario, lors d’une attaque DDoS, l’attaquant utilise une multitude de systèmes pour attaquer sa victime. Parce que l’attaque provient de plusieurs sources à la fois, le DDoS est donc généralement plus rapide, plus difficile à bloquer qu’un DoS et retracer sa source s’avère difficile.

Conduire des tests DDoS dans le cadre d’un pentest n’a que peu d’intérêt, car il sera toujours possible de faire tomber un service si l’on y consacre les moyens nécessaires.

En revanche, réaliser des tests DoS permet d’identifier des vulnérabilités au niveau des configurations ou de l’applicatif. Et dans ces cas de figure, des correctifs peuvent être implémentés. Nous reviendrons sur ce point un peu plus loin dans l’article.

Quels sont les différents types d’attaques DoS ?

Il existe plusieurs manières d’effectuer un déni de service. Nous pouvons les classer en différentes catégories. Attention, cette liste n’est pas exhaustive !

Lors d’une attaque Teardrop, l’attaquant envoie des lourds paquets fragmentés en de nombreux morceaux à la victime.

Cependant, l’attaquant aura anormalement fragmenté ces paquets ce qui aura pour effet de déstabiliser le système cible lorsque celui-ci essayera de réassembler les fragments en paquets. Cette attaque exploite donc le protocole IP.

Une attaque SYN Flood consiste à envoyer plusieurs demandes de connexion à un serveur mais sans lui répondre lorsque celui-ci essaye de nous contacter en retour pour compléter le « handshake » entre le serveur et le client (ici l’attaquant).

Ainsi, n’arrivant pas à compléter toutes les demandes d’handshake initiées par l’attaquant, le serveur sera trop sollicité pour se connecter à d’autres utilisateurs légitimes. Il sera donc ralenti voire deviendra inaccessible. Cette attaque exploite donc le protocole TCP.

Le but d’une attaque volumétrique est de surcharger la bande passante de la victime. On peut par exemple penser à une attaque qui exploite le protocole ICMP en envoyant une grande quantité de requêtes « echo » ICMP à la victime.

Ainsi, la bande passante sera surchargée par ces requêtes et le réseau ne pourra laisser passer que quelques requêtes légitimes voire aucune.

Les attaques de la couche application consistent à exploiter certains protocoles de la couche 7 du modèle OSI comme le protocole HTTP.

C’est ce type d’attaque que nous avons l’habitude d’utiliser lors de tests d’intrusion d’applications web.

Modalités des Tests DoS lors d’un test d’intrusion

La réussite d’un déni de service dépend des moyens plus ou moins importants mis en place par l’attaquant. Cependant certains dénis de service peuvent être limités voire corrigés définitivement. Et c’est sur ce type d’attaques que nous nous focalisons lors d’un test d’intrusion.

Chercher ces cas précis de déni de service a un intérêt pour nos clients car cela permet, après corrections, d’éviter qu’un seul attaquant avec une unique machine, quelques outils et des connaissances techniques peu poussées réussisse à faire tomber le système.

Nous verrons dans la suite de cet article des exemples concrets de vulnérabilités exploitées pour réussir des attaques Dos. Mais avant cela, quelques précisions sur les tests DoS. Pour réaliser ce type de tests dans les meilleures conditions dans le cadre d’un pentest, il est important de prendre en compte plusieurs aspects :

  • Si le pentest ne peut pas être réalisé sur un environnement de test, il est possible de conduire les tests DoS sur une plage horaire bien précise où un nombre limité d’utilisateur sera impacté en cas d’indisponibilité de l’application.
  • La communication entre les pentesters et l’équipe technique cliente doit être la plus efficace possible afin d’être réactif en cas d’incident et ainsi limiter l’impact.
  • Enfin, si les pentesters s’aperçoivent que l’attaque DoS ralentit rapidement le service ciblé, pas besoin d’aller plus loin au risque de déstabiliser encore plus le système. La vulnérabilité est identifiée, et le service aura été impacté pendant seulement un temps minime.

Vulnérabilités exploitables pour réaliser des attaques DoS

Restons dans le cadre de pentests d’applications web, avec dans le scope la réalisation de tests DoS. Comme mentionné plus haut, ces tests sont réalisés sur des plages horaires spécifiques afin de ne pas impacter l’activité de nos clients.

Généralement, une attaque DoS identifiée provient de 3 vulnérabilités :

  • Une mauvaise configuration du serveur web
  • Une mauvaise implémentation d’une fonctionnalité
  • Un composant vulnérable

Pour chaque vulnérabilité, des attaques différentes pourront être utilisées afin de réaliser un déni de service. Voyons cela plus en détail.

Considérons un serveur Apache fraichement installé et hébergeant une simple page statique :

Si la version du serveur est ancienne, nous pouvons peut-être facilement faire un déni de service sur la plateforme, d’autant plus si la configuration serveur n’a pas été touchée.

Pour ce faire, nous pouvons utiliser un outil appelé « slowhttptest » et qui permet de réaliser plusieurs types d’attaques de déni de service. Pour cet exemple, nous allons effectuer une attaque appelée « slowloris » qui appartient à la catégorie des attaques de la couche application.

Principe et fonctionnement d’une attaque Slowloris

Le principe d’un slowloris est d’envoyer des requêtes HTTP spécialement formées afin de garder le plus de connexions ouvertes possibles avec le serveur victime. Ainsi, si le serveur ne gère pas correctement ses connexions, il ne pourra plus établir de nouvelles connexions avec des utilisateurs légitimes et le déni de service sera effectif.

Ainsi, avec une simple commande, le serveur n’est plus accessible :

Il est impossible de se protéger complètement de ce type de DoS. Cependant, il est possible de minimiser drastiquement les conséquences d’une telle attaque sur un serveur Apache.

Comment contrer ce type d’attaque DoS ?

La solution la plus simple est d’installer un module Apache, appelé « reqtimeout », qui est désormais installé par défaut depuis la version 2.2.15.

Ce module permet de définir facilement les délais maximums de réception des requêtes et les taux minimums de transfert de données. Si les délais ou les taux ne sont pas respectés, alors le serveur fermera les connexions concernées.

Ci-dessous la configuration par défaut du module :

RequestReadTimeout header=20-40,minrate=500
RequestReadTimeout body=10,minrate=500

Pour cette configuration, une fois la connexion réalisée, le serveur laisse au client un maximum de 20 secondes pour envoyer les premiers headers de la requête. Et pour chaque 500 octets de données header reçues, le serveur augmentera le délai de base d’une seconde mais jamais au-delà du délai maximum, ici 40 secondes.

Idem pour la deuxième ligne, qui sert à analyser le corps de la requête. Suite à la connexion, le serveur laisse au client un maximum de 10 secondes pour envoyer les premières données. Pour chaque paquet de données envoyé supérieur à 500 octets, le délai sera augmenté d’une seconde sans limite de délais supérieur.

Par ailleurs, cette deuxième ligne permet de se protéger d’un autre type d’attaque : le slow body ou « R-U-Dead-Yet ».

On peut bien entendu considérablement baisser les valeurs de délais et ajouter une limite supérieure sur le paramètre « body ». Mais cela peut les utilisateurs légitimes. Il convient donc de changer la configuration par défaut en fonction de ses propres besoins.

Dans notre exemple, nous n’avons qu’une simple page statique. Nous pouvons donc baisser drastiquement les valeurs :

RequestReadTimeout header=1-2,minrate=500

Si nous lançons une attaque slowloris avec ou sans le module, nous obtenons :

Sans Reqtimeout
Avec Reqtimeout

L’outil slowhttptest produit à la fin des tests un graphique résumant l’attaque. Sur le premier graphique, on peut voir que la ligne verte représentant la disponibilité du serveur plonge à 0.

Ainsi, sans le module reqtimeout, le serveur est indisponible pendant toute la durée de l’attaque. Avec le module, l’attaque a pratiquement été arrêtée car le serveur a fini par fermer toutes les connexions illégitimes.

Parfois, une vulnérabilité permettant à un attaquant de réaliser une attaque DoS provient tout simplement d’une erreur, d’un oubli au moment de l’implémentation d’une fonctionnalité spécifique sur une application web.

Par exemple, on peut penser à une fonctionnalité d’export qui génère une très grande quantité de données et qu’il est possible de lancer à l’aide d’un simple bouton.

Si le développeur responsable de cette fonctionnalité n’a pas développé de protections, un utilisateur malveillant pourrait très bien cliquer sur le bouton un très grand nombre de fois ou lancer des milliers de requêtes d’export afin de lancer plusieurs processus d’export en parallèle.

Cela aura pour effet de ralentir voire d’arrêter totalement l’application qui demandera trop de ressources au serveur.

Dans ce cas de figure, pour corriger le déni de service, on pourra, par exemple, implémenter en back une vérification pour savoir si un export est déjà en cours, et si c’est le cas, l’application ne pourra pas en lancer un nouveau tant que l’actuel export n’est pas terminé.

En plus d’une mauvaise implémentation, un DoS peut également provenir d’une technologie ou d’un langage particulier comme GraphQL ou XML.

Attaques DoS sur une API GraphQL

Concernant GraphQL, qui est un langage de requête pour API, il est possible de réaliser un DoS de différentes manières en fonction de la technologie utilisée et de la configuration.

Par exemple, considérons le schéma GraphQL suivant :

Schéma GraphQL (source : PortSwigger https://portswigger.net/)

Il est possible, dans ce cas, d’appeler l’objet « BlogPost » avec la query « getBlogPost » et ses champs : « id », « image », etc. GraphQL permet également de créer des « fragments » qui sont un ensemble de champs choisis par l’utilisateur et qui peuvent être utilisés plusieurs fois dans plusieurs requêtes, ce qui permet à l’utilisateur de ne pas réécrire à chaque fois toute la requête et tous les champs.

Cependant, un utilisateur malveillant peut utiliser ce principe de fragments afin de créer une boucle infinie.

En effet, il lui suffit de créer deux fragments qui s’appellent entre eux. Ainsi, s’il fait une requête pour appeler le premier fragment, celui-ci appellera le deuxième fragment qui appellera de nouveau à son tour le premier fragment et ainsi de suite, ce qui ralentira l’application voire l’arrêtera totalement.

Ci-dessous, l’implémentation d’une telle attaque :

Fragments GraphQL menant à un DoS

On peut voir sur cette implémentation que le fragment A appelle le fragment B, et que le fragment B appelle le fragment A, formant ainsi une boucle. Dans la query, on fait appel au fragment A, ce qui lancera la boucle infinie.

Pour corriger ce comportement, il est recommandé de ne pas utiliser des implémentations de GraphQL qui autorisent la récursivité des fragments.

En général, pour contrer des attaques DoS sur une API GraphQL, il est recommandé d’utiliser des implémentations de GraphQL qui supportent les fonctionnalités « Query Depth limit » et « Query Cost Analysis », qui permettent de définir la limite d’imbrication de champs et de calculer le coup pour chaque appel de champ et ainsi de bloquer les requêtes qui dépassent ces limites.

Attaques DoS de type « XMl Bomb »

Enfin, les applications utilisant XML pour communiquer entre le client et le serveur peuvent également être vulnérables à une attaque DoS appelée « XML bomb » ou « Billion Laughs Attack ».

Le principe de cette attaque est de créer plusieurs entités qui référencent chacune, plusieurs fois, l’entité précédente jusqu’à arriver à une entité d’origine qui est un string quelconque.

L’exemple d’implémentation de cette attaque la plus connue est le suivant :

Implémentation XML bomb

On peut voir sur l’image ci-dessus que l’entité « lol9 » appelle 10 fois l’entité « lol8 » qui appelle elle-même 10 fois l’entité « lol7 » et ainsi de suite jusqu’à arriver à l’entité « lol » qui est le string « lol ».

Une fois parsé, le bloc XML final contiendra donc 109 de string « lol » ce qui équivaut à 1 milliard de « lol », d’où le nom « Billion Laughs Attack ».

Cette opération demande énormément de mémoire au serveur, ce qui ralentira l’application, voire l’arrêtera.

Pour se prémunir d’une telle attaque, il est possible de désactiver l’utilisation des entités externes et d’éventuellement définir une limite sur l’utilisation des ressources utilisées par le parser XML.

En plus d’une mauvaise configuration serveur ou d’une mauvaise implémentation, il est également possible de réaliser des attaques DoS grâce à des composants vulnérables utilisés par l’application testée.

En 2023, plusieurs milliers de CVE concernant des DoS ont été publiées.

Par exemple, la vulnérabilité CVE-2023-20863 impacte le très utilisé framework Java « Spring » sur les versions antérieurs à 5.2.24 release, 5.3.27 et 6.0.8. Cette vulnérabilité permet de réaliser un déni de service en utilisant une expression SpEL (Spring Expression Language) spécifique.

Pour se protéger de ce type de DoS, il est important de mettre à jour régulièrement les composants utilisés et de mettre en place un processus de gestion des correctifs afin de :

  • Supprimer les composants non utilisés.
  • Régulièrement faire l’inventaire des versions de tous les composants. utilisés, et de leurs dépendances, donc des composants qu’ils utilisent.
  • Continuellement être au courant des dernières vulnérabilités majeures et mettre à jour les composants concernés.
  • N’utiliser que des composants de sources fiables et officielles.
  • Monitorer les composants qui ne sont plus maintenus ou qui ne possèdent pas de correctifs pour d’anciennes versions.

Comment se protéger des attaques DoS ?

De manière plus globale, pour se protéger des dénis de service et limiter leur impact, voici une liste non exhaustive des solutions à mettre en place :

  • Implémenter un « rate limiting » : Il est possible de restreindre l’accès au serveur à une ou plusieurs IP si celle-ci sont considérées comme malveillantes.
  • Utiliser un ou plusieurs load balancers : Un load balancer permet notamment de rediriger le trafic entrant de manière égale vers plusieurs serveurs afin de ne pas surcharger un seul serveur.
  • Implémenter un WAF : Un WAF ou « pare-feu applicatif » permet d’établir des règles qui pourront reconnaître et bloquer le trafic malveillant avant que celui-ci n’atteigne le serveur.
  • Monitorer le trafic réseau : Analyser constamment le trafic réseau permet de détecter rapidement une d’attaque par déni de service. La réaction face à une telle attaque pourra être rapide et les dégâts seront donc limités.
  • Garder à jour les composants : Avoir des composants à jour empêche les attaquants d’exploiter des vulnérabilités connues sur certaines versions de composants qui déboucheraient sur un déni de service.

Auteur : Lorenzo CARTE – Pentester @Vaadata