Exploitation d'une faille LFI (Local File Inclusion) et bonnes pratiques sécurité

Lorsque nous visitons un site web, il est courant de pouvoir parcourir différentes pages. Chaque page peut être représentée par un fichier sur le serveur. Afin de déterminer quel fichier fournir, l’application a besoin que le client spécifie le fichier qui l’intéresse. Pour cela, il arrive que la page souhaitée par l’utilisateur soit présente dans un paramètre de la requête.

Par exemple un site peut avoir les pages suivantes :

  • https://example.com/index.php?page=about.html pour voir des informations utiles.
  • https://example.com/index.php?page=contact.html pour voir un formulaire de contact.
  • https://example.com/index.php?page=articles.html pour voir les articles proposés.

Effectivement, comme nous pouvons le voir, le paramètre « page » joue un rôle essentiel dans le contrôle du fichier qui sera affiché. Par conséquent, il est tout à fait légitime de se demander quelles conséquences pourraient découler d’une modification de cette valeur pour une autre. Et c’est précisément ce que tenterait un attaquant, et cela est connu sous le nom de « LFI » pour « Local File Inclusion ».

Qu’est-ce qu’une faille LFI (Local File Inclusion) ?

Une faille LFI consiste à exploiter une fonctionnalité proposée par l’application pour inclure un autre fichier qui se trouve sur le système exécutant l’application. Dans certains cas, cette vulnérabilité peut être combinée avec une attaque appelée « Path Traversal », qui vise à naviguer vers les répertoires parents pour pouvoir inclure n’importe quel fichier du système.

Selon la manière dont le fichier est inclus, un attaquant peut exploiter cette vulnérabilité pour exécuter du code sur le serveur, particulièrement si le contenu du fichier est interprété.

Il peut également piéger d’autres utilisateurs s’il parvient à contrôler le contenu d’un fichier sur le serveur, par exemple en utilisant une attaque de type XSS (Cross-Site Scripting). En outre, il peut simplement obtenir des informations sensibles, telles que le code source de l’application ou des fichiers de logs.

Cas concret d’exploitation d’une faille LFI avec lecture d’un fichier de logs

En tant qu’attaquant nous allons alors essayer de contrôler ce paramètre et observer comment l’application réagit. Par exemple, au lieu de lire le fichier « about.html » nous allons essayer de lire « ./about.html ».

Dans certains cas, il se peut que l’application détecte la présence de caractères spéciaux et empêche la requête, ce qui limiterait ce que l’on peut faire. Heureusement pour nous ce n’est pas le cas et l’application fonctionne toujours.

Nous allons essayer d’accéder à « /etc/passwd », car ce fichier est généralement présent sur les serveurs Linux et est accessible en lecture par tout le monde par défaut, ce qui en fait un bon candidat pour nos tests.

Malheureusement cela ne fonctionne pas. Nous pouvons supposer que l’application préfixe notre paramètre par le nom d’un dossier, par exemple « pages », ce qui signifie que l’application essaierait de lire le fichier « pages/etc/passwd » qui n’existe pas.

Cependant, nous essayons une autre approche pour remonter successivement dans les dossiers parents jusqu’à la racine pour lire le fichier qui nous intéresse. En effet, nous utilisons « ../../../../../../etc/passwd » comme paramètre, et cette fois-ci, cela fonctionne.

https://example.com/index.php?page=../../../../../../etc/passwd nous affiche alors le contenu du fichier /etc/passwd.

À plusieurs reprises, nous avons rencontré des applications présentant cette vulnérabilité, en plus d’autres fonctionnalités telles que la gestion des utilisateurs et de l’authentification.

Ces plateformes permettaient notamment à un utilisateur de réinitialiser le mot de passe de son compte. Et, lorsqu’un utilisateur effectue une réinitialisation de mot de passe, l’application envoie un lien par e-mail à l’utilisateur avec la forme suivante :

https://example.com/reset.php?token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm

Le problème réside dans le fait que lorsque l’utilisateur clique sur ce lien, le serveur enregistre la requête dans un fichier de logs. Si un attaquant parvient à accéder à ce fichier, il pourrait alors réutiliser ce token pour réinitialiser le compte de l’utilisateur avant même que ce dernier puisse le faire.

Le fichier de logs se trouve généralement dans /var/log/nginx/access.log et si nous décidons de le consulter, nous pourrions observer quelque chose de similaire à ce qui suit :

172.17.0.1 - - [07/Apr/2023:13:58:59 +0000] "GET /reset.php?token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm HTTP/1.1" 200 0 "-" "curl/7.85.0" "-"

Ainsi, un attaquant n’a plus qu’à automatiser l’accès à ce fichier en utilisant la vulnérabilité pour voler un compte dès qu’une nouvelle ligne apparaît.

Comment se protéger d’une faille LFI ?

Il existe différentes solutions pour se protéger d’une faille LFI. Si vous avez connaissance à l’avance de toutes les pages de votre site et que vous ne voyez pas d’inconvénient à maintenir une liste des pages autorisées, vous pouvez envisager de vérifier si le paramètre permettant l’accès au fichier appartient à cette liste.

Si vos fichiers ne sont pas situés dans des sous-dossiers, vous pouvez envisager d’utiliser une fonction pour extraire une partie du paramètre, telle que la fonction « basename » en PHP, par exemple. Une autre approche consiste simplement à interdire la présence de slash (« / ») dans le paramètre.

En revanche, si vous souhaitez utiliser des sous-dossiers, la solution devient plus complexe. Une approche serait de déterminer le véritable chemin du fichier sur le disque en utilisant une fonction comme « realpath » en PHP, puis de vérifier si ce chemin commence effectivement par le dossier où se trouvent toutes vos pages. Cela permettrait de s’assurer que les fichiers inclus proviennent bien du répertoire autorisé, tout en permettant l’utilisation de sous-dossiers de manière sécurisée.

En principe, l’application d’une de ces solutions devrait éliminer complètement la vulnérabilité. Mais il est possible d’aller encore plus loin en n’ayant pas de secret présent dans les fichiers de logs.

En effet, il est possible de mettre le secret dans une partie de l’URL qui ne sera pas envoyée au serveur, en l’ajoutant après un dièse (#) en fin d’URL.

https://example.com/reset.php#token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm

Cependant, avec cette méthode, le serveur ne reçoit plus directement le token. Pour le transmettre au serveur, il faudra mettre en place de la logique côté client avec du JavaScript pour récupérer cette partie de l’URL et envoyer le token dans le corps d’une requête (type POST par exemple). De cette manière, le token ne sera pas présent dans le fichier de logs du serveur.

Une autre option serait de configurer votre serveur de manière à modifier ce qui est enregistré dans les fichiers de logs, soit par route spécifique, soit pour l’ensemble du serveur.

Auteur : Arnaud Pascal – Pentester @Vaadata