Site qui a subi une injection SQL la semaine dernière, et j'ai reproduit les failles

WRInaute occasionnel
Bonjour,

La semaine dernière, l'un des sites que je gère a subi une injection SQL. Il y a environ 4-5 ans, je suis passé aux requêtes préparées (mysqli/pdo), mais il reste encore quelques codes qui contiennent parfois des $variable directement dans la commande query.

Du coup, j'ai corrigé le problème en passant ce code (oublié) en requête préparée et j'ai installé Modsecurity qui sert justement à filtrer les requêtes comme ça

Au début de cette semaine, j'ai reproduit toutes les failles d'injection, donc pour ce faire, j'ai imaginé une query comme celle-ci en php, elle est un peu arrangée pour qu'elle soit simple à comprendre :

PHP:
query("SELECT username FROM accounts WHERE username = '$user' $test LIMIT 1");

Du coup, à la place de $test, j'ai testé cette liste :


Test 1 :
SQL:
UNION SELECT schema_name FROM information_schema.schemata
(ça retourne la liste des tables de la base de données. Ce genre de commande peut retourner la liste des tables, bases de données, les colonnes d'une table...)


Test 2 :
SQL:
UNION SELECT column_name FROM information_schema.columns WHERE table_name = 'accounts');
(ça retourne les noms des colonnes de la table accounts)


Test 3 :
SQL:
UNION SELECT CONCAT(username, ' ', password, ' ', confirmation) FROM accounts
(ça retourne le pseudo, le mot de passe, et le statut de confirmation)


Test 4 :
- Du code JavaScript ou PHP (<script>, <?php) injecté est impossible à générer, il y a des erreurs PHP.


Test 5 :
SQL:
OR 1=1
SQL:
OR id=1
(avec OR id=1 il se connecte sur l'id 1 sans mot de passe)


Test 6 :
- Faire attention à LOAD_FILE() qui sert à afficher le contenu d'un fichier sur le serveur. Faire aussi attention à INTO OUTFILE qui permet d'écrire un fichier à partir d'une commande SQL. Chez moi, les deux ne fonctionnent pas, surtout avec l'utilisateur du compte


Test 7 :
- Les commandes qu'un hacker peut ajouter en préfixe sont : UNION, SELECT, OR, AND, ORDER, LIMIT, LOAD_FILE(), INTO OUTFILE ....


Les commandes les plus dangereuses d'un hack SQL, ce sont surtout celles du Test 3 et du Test 6


J'ai questionné ChatGPT pour savoir ce que ça générait comme code une fois que la commande est envoyée avec $_POST ($_POST est un exemple), les réponses :

1) ❌ Query avec variable directement injectée (VULNÉRABLE)
Exemple de code:
PHP:
$id = $_GET['id'];

$query = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $query);
On envoie :
?id=1 OR 1=1

Il génère directement :
SQL:
SELECT * FROM users WHERE id = 1 OR 1=1


2) ⚠️ Query avec mysqli_real_escape_string (PARTIELLEMENT SÉCURISÉ)
Exemple:
PHP:
$user = mysqli_real_escape_string($conn, $_POST['user']);

$query = "SELECT * FROM users WHERE username = '$user'";
On envoie :
' OR 1=1 --

Il génère :
SQL:
SELECT * FROM users WHERE username = '\' OR 1=1 --'


3) ✅ Requête préparée (SÉCURISÉ)
Exemple:
PHP:
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['user']);
$stmt->execute();

$result = $stmt->get_result();
On envoie :
' OR 1=1 --

Il génère :
SQL:
SELECT * FROM users WHERE username = "' OR 1=1 --"



Pour sécuriser tout ça, j'ai dû désactiver display_errors dans php.ini, c'est une habitude que je dois prendre
Code:
display_errors = Off

Pour afficher les erreurs, il suffit ensuite d'ajouter ceci tout en haut de chaque fichier php en mode dev :
Code:
ini_set('display_errors', 1);

Il faut le mettre à 0 (désactivé) en prod.

Le désactiver est important, ça permet d'éviter d'aider les hackeurs qui lisent les erreurs.

J'ai aussi appris à utiliser Modsecurity qui permet de sécuriser les requêtes SQL injectables. Il était simple à mettre en oeuvre. Le plus difficile c'est qu'il est sévére au début, et il faut lui mettre des exceptions dont c'était pas facile à comprendre, mais ça va c'est pas plus difficile que de réparer une voiture.

En testant tout ça, ça m'a permis de comprendre comment fonctionnent les queries avec les GET, POST, etc., ce qui améliore mon expérience ainsi que ma compréhension du fonctionnement de SQL et des hacks par injection SQL sur de nombreux sites.

Pour ceux qui n'ont jamais testé ni reproduit les injections SQL et qui lisent ceci, j'espère que j'ai bien expliqué.
 
Dernière édition:
WRInaute passionné
En quoi l'option 2 (avec mysqli_real_escape_string()) est "partiellement" sécurisée ?

Bof, GPT dit "Si le charset de la connexion n’est pas correctement défini (utf8mb4 par exemple), il peut y avoir des contournements".
Dans mon cas je force bien utf8mb4 à la connexion, donc ça doit suffire.
 
WRInaute impliqué
La documentation de mysqli_real_escape_string() indique que le jeu de caractère peut également être défini au niveau du serveur. J'ai également tendance à systématiquement utiliser mysqli_set_charset() juste après mysqli_connect(), mais c'est pour ne pas dépendre de la configuration du charset au niveau du serveur. Les deux sont fiables du point de vue de mysqli_real_escape_string().

Si je comprends bien, ce qui pourrait potentiellement poser soucis, serait l'emploi de requêtes SQL de type "SET CHARACTER SET utf8mb4" qui modifie le charset de la connexion, mais sans que cela puisse être pris en compte par mysqli_real_escape_string(). Ça ne me paraît pas être une hypothèse courante.

Il y a potentiellement un autre soucis avec pour les requêtes utilisant l'opérateur LIKE, puisque les caractères % et _ ne sont pas échappés par mysqli_real_escape_string(). Mais je ne sais pas comment c'est géré par les requêtes préparées, et je ne vois pas quel risque d'injection cela pourrait représenter.
 

➡️ Offre MyRankingMetrics ⬅️

pré-audit SEO gratuit avec RM Tech (+ avis d'expert)
coaching offert aux clients (avec Olivier Duffez ou Fabien Faceries)

Voir les détails ici

coaching SEO
Discussions similaires
Haut