Comment exploiter les tables d'un serveur MySQL non sécurisé

Temps de lecture estimé : 4 minutes

En tant que base de données, MySQL est un composant sensible d’une pile technique, il est donc important de le protéger des attaques. Le chiffrement au repos (dit aussi « data at rest » ou « Transparent Data Encryption ») est l’un de ces mécanismes de protection, permettant de se prémunir d’un vol de disque.

Nous verrons bientôt comment mettre en place ce chiffrement au repos d’un serveur MySQL, mais avant cela, voyons plutôt pourquoi cette protection est importante. Pour ce faire, nous allons nous mettre dans la peau d’un pirate informatique attaquant un serveur MySQL 8.


Bien, nous avons réussi à siphonner le disque dur de notre cible, par quoi on commence ? Base de données ou pas, ce sont toujours des fichiers sur un disque. Ici, MySQL se trouve dans le répertoire /var/lib/mysql, chaque base de données se trouve sous /var/lib/mysql/madatabase et chaque table sous /var/lib/mysql/madatabase/table.ibd.

Cet .ibd est un binaire en clair, tentons d’exploiter une table :

$ strings utilisateurs.ibd
Ginfimum
supremum
DQ8x
"]l@
^ia=p
c!?E3
eTbE
rHJ8
k'"wP
Hwzq
C`O)'
`rr^
;,4y>
=9^K
z=E`
VgC K
infimum
supremum
HuffCarolina2022-06-13 12:48:34
LindseyGenevieve2022-06-12 12:48:34
*StokesZachariah2022-06-16 12:48:34
7OneillRamon2022-06-14 12:48:34
DHowardRemington2022-06-16 12:48:34
QHerringKyra2022-06-11 12:48:34
^HillPayten2022-06-17 12:48:34
kCordovaKatrina2022-06-17 12:48:34
xLamBraedon2022-06-14 12:48:34
HooperRylie2022-06-14 12:48:34
NavarroAyanna2022-06-11 12:48:34
SweeneyRaul2022-06-13 12:48:34

C’est pas mal, mais ce n’est pas suffisant. Seules les chaînes de caractères s’affichent et notre table contient sûrement d’autres types.

Pour mener à bien ce piratage, le fichier seul ne suffit pas, nous allons devoir monter un serveur MySQL de toute pièce. Pour cela, nous devons en savoir plus sur la table à reconstruire. L’outil ibd2sdi va nous aider à extraire les informations essentielles.

$ ibd2sdi utilisateurs.ibd | jq ".[1].object.dd_object.columns[:-2][]" > colonnes_utilisateurs.json
$ jq "with_entries(select(.key | in({"name":1, "ordinal_position":1, "column_type_utf8":1})))" colonnes_utilisateurs.json
{
  "name": "id",
  "ordinal_position": 1,
  "column_type_utf8": "int"
}
{
  "name": "nom",
  "ordinal_position": 2,
  "column_type_utf8": "varchar(50)"
}
{
  "name": "prenom",
  "ordinal_position": 3,
  "column_type_utf8": "varchar(50)"
}
{
  "name": "maj_at",
  "ordinal_position": 4,
  "column_type_utf8": "varchar(50)"
}
{
  "name": "creation_at",
  "ordinal_position": 5,
  "column_type_utf8": "datetime"
}
{
  "name": "creditLimit",
  "ordinal_position": 6,
  "column_type_utf8": "decimal(10,2)"
}

Ces infos nous permettent de déduire la structure suivante :

create table utilisateurs (
    id int,
    nom varchar(50),
    prenom varchar(50),
    maj_at varchar(50),
    creation_at datetime,
    creditLimit decimal(10, 2)
);

Pour être au plus près de notre cible, prenons au passage la version du serveur :

$ ibd2sdi utilisateurs.ibd | jq ".[1].object.mysqld_version_id"
80029

Créons donc la table utilisateurs sur un serveur MySQL 8.0.29 :

$ mysql madatabase
mysql> create table utilisateurs (
    id int,
    nom varchar(50),
    prenom varchar(50),
    maj_at varchar(50),
    creation_at datetime,
    creditLimit decimal(10, 2)
);

Cette création de table crée également le fichier tablespace associé ; supprimons-le pour le remplacer par celui de notre cible:

mysql> alter table utilisateurs discard tablespace;
$ cp [source]/utilisateurs.ibd /var/lib/mysql/madatabase/
$ chown mysql: /var/lib/mysql/madatabase/utilisateurs.ibd

Importons finalement le tablespace de remplacement :

mysql> alter table utilisateurs import tablespace;
mysql> select * from utilisateurs;
+----+---------+-----------+---------------------+---------------------+-------------+
| id | nom     | prenom    | maj_at              | creation_at         | creditLimit |
+----+---------+-----------+---------------------+---------------------+-------------+
|  1 | Huff    | Carolina  | 2022-06-09 13:28:05 | 2022-06-17 13:28:05 |     9999.50 |
|  2 | Lindsey | Genevieve | 2022-06-16 13:28:05 | 2022-06-17 13:28:05 |      741.20 |
|  3 | Stokes  | Zachariah | 2022-06-09 13:28:05 | 2022-06-17 13:28:05 |       32.30 |
|  4 | Oneill  | Ramon     | 2022-06-16 13:28:05 | 2022-06-17 13:28:05 |      200.10 |
|  5 | Howard  | Remington | 2022-06-17 13:28:05 | 2022-06-17 13:28:05 |       66.15 |
|  6 | Herring | Kyra      | 2022-06-10 13:28:05 | 2022-06-17 13:28:05 |       25.25 |
|  7 | Hill    | Payten    | 2022-06-10 13:28:05 | 2022-06-17 13:28:05 |       53.00 |
|  8 | Cordova | Katrina   | 2022-06-14 13:28:05 | 2022-06-17 13:28:05 |        7.98 |
|  9 | Lam     | Braedon   | 2022-06-13 13:28:05 | 2022-06-17 13:28:05 |       81.17 |
| 10 | Hooper  | Rylie     | 2022-06-16 13:28:05 | 2022-06-17 13:28:05 |      352.40 |
| 11 | Navarro | Ayanna    | 2022-06-11 13:28:05 | 2022-06-17 13:28:05 |       50.80 |
| 12 | Sweeney | Raul      | 2022-06-13 13:28:05 | 2022-06-17 13:28:05 |        0.00 |
+----+---------+-----------+---------------------+---------------------+-------------+
12 rows in set (0.01 sec)

Succès ! Au final, il est assez facile de recomposer une donnée d’un serveur en clair pour peu qu’on ait un accès au fichier. C’est pour cette raison que la prochaine fois, nous verrons comment mettre en place le chiffrement at rest sur un serveur MySQL 8 pour nous prémunir de cette situation.

Comme d’habitude, vous pouvez retrouver le code pour essayer par vous-même sur le dépôt d’exemple.


Sources :


    Ce billet vous a plu ? Partagez-le sur les réseaux…


    … Ou inscrivez-vous à la newsletter pour ne manquer aucun article (Si vous ne voyez pas le formulaire, désactivez temporairement uBlock).

    Voir aussi