Skip to main content

Injection de code en Python : exemples et prévention

Écrit par
Headshot of Lucien Chemaly

Lucien Chemaly

feature python code injection

6 décembre 2023

0 minutes de lecture

À mesure que les logiciels occupent une place toujours plus importante dans notre vie professionnelle et personnelle, la nécessité de protéger les informations et les systèmes contre les attaques malveillantes s’accroît dans les mêmes proportions. L’un des risques majeurs auxquels les développeurs Python doivent faire face est celui de l’injection de code, une cyberattaque sophistiquée aux conséquences souvent dévastatrices.

L’injection de code est un problème répandu qui touche tous les langages de programmation et toutes les plateformes. Pourtant, dans les applications Python, ses manifestations peuvent être particulièrement subtiles et dangereuses.

Parmi les langages les plus utilisés pour le développement web, l’analyse de données et l’automatisation, Python offre un vaste éventail de fonctionnalités et de bibliothèques qui peuvent être aussi utiles que risquées. Il permet aux développeurs de créer des systèmes robustes et efficaces, mais offre aussi de nombreuses possibilités aux acteurs malveillants d’exploiter des vulnérabilités lorsque les règles de codage sécurisé ne sont pas strictement respectées.

La prolifération et l’utilisation généralisée des composants et packages open source rendent la prévention des injections de code en Python encore plus difficile. Ces ressources facilement accessibles accélèrent le développement, mais peuvent dissimuler des failles de sécurité exploitables pour injecter du code.

Dans cet article, vous découvrirez les dangers et l’importance des règles de codage sécurisé, notamment en ce qui concerne les vulnérabilités d’injection de code et leurs manifestations dans les applications Python. En comprenant la nature de l’injection de code et en adoptant les bonnes pratiques de codage sécurisé, vous contribuerez à un écosystème numérique plus sûr et protégerez vos applications contre d’éventuelles intrusions.

Qu’est-ce que l’injection de code ?

L’injection de code est une attaque furtive qui consiste à insérer du code malveillant dans un système logiciel afin de lui faire exécuter des commandes non prévues. En exploitant des vulnérabilités, un attaquant peut injecter du code nuisible et provoquer de graves conséquences, comme un accès non autorisé aux données, une fraude financière ou la prise de contrôle complète du système.

Ces vulnérabilités surviennent souvent lorsqu’une application gère mal les entrées utilisateur. Par exemple, l’utilisation non sécurisée de fonctions comme eval() en Python, sans validation appropriée, peut entraîner une injection de code. Il en va de même pour la génération de code à partir d’entrées utilisateur sans contrôles suffisants, l’utilisation de code tiers sans vérification de sécurité ou la présence de vulnérabilités dans la configuration de frameworks web ou de bases de données.

Comprendre l’injection de code est essentiel pour toute personne travaillant dans le développement logiciel ou la sécurité. Examinons donc en détail les vulnérabilités courantes auxquelles vous pourriez être confronté.

Vulnérabilités courantes à l’origine d’injections de code

Les vulnérabilités à l’origine d’injections de code constituent un enjeu majeur du développement logiciel. Il est essentiel de les comprendre et de les corriger pour créer des systèmes sécurisés. Dans les sections suivantes, vous découvrirez quelques-unes des principales sources d’injection de code et comment vous en protéger.

Exploitation d’entrées contrôlées par l’utilisateur

Lorsque les entrées utilisateur sont utilisées directement sans validation, un attaquant peut saisir du code Python et l’application l’exécute. Prenons cet exemple :

user_input = input("Enter your username: ")
query = "SELECT * FROM users WHERE username = '" + user_input + "';"
execute_query(query)  # This can be exploited

Pour exploiter ce code, un attaquant pourrait saisir quelque chose comme ' OR '1'='1'; DROP TABLE users;. Le point-virgule (;) termine la requête initiale, et le code SQL qui suit devient une nouvelle requête susceptible de supprimer la table users de la base de données.

Utilisation non sécurisée de eval() et des fonctions associées

Des fonctions comme eval() peuvent être exploitées si un utilisateur est autorisé à saisir des expressions arbitraires, comme celle-ci :

user_input = input("Enter expression: ")
result = eval(user_input)  # Unsafe

Dans cet exemple, un attaquant peut saisir __import__('os').system('any arbitrary command') pour exécuter des commandes système arbitraires. Il pourrait, par exemple, saisir une commande comme rm -rf /, qui tente de supprimer tous les fichiers du répertoire racine du système où se trouve le code.

Absence de validation et d’assainissement des entrées

Sans validation appropriée, un attaquant peut saisir le chemin d’accès à n’importe quel fichier, que l’application lira comme suit :

user_input = input("Enter filename: ")
with open(user_input, 'r') as file:  # Vulnerable to directory traversal
    content = file.read()

Ici, un attaquant peut saisir un chemin comme /etc/passwd pour lire des fichiers système sensibles.

Risques liés à la construction dynamique de code

La construction dynamique de chaînes de commandes sans validation appropriée peut entraîner des vulnérabilités d’injection de commandes. Prenons cet exemple :

import os

directory = input("Enter the directory to list: ")
command = f"ls {directory}"  # Vulnerable to Command Injection
os.system(command)

Dans cet exemple, un attaquant pourrait saisir une chaîne de répertoire comme ; cat /etc/passwd pour afficher le contenu du fichier des mots de passe du système ou, pire encore, exécuter d’autres commandes malveillantes. Le point-virgule (;) permet à l’attaquant d’enchaîner les commandes et d’en exécuter arbitrairement sur le système hôte.

Désérialisation non sécurisée

Une désérialisation non sécurisée peut se produire lorsque des données non fiables sont désérialisées sans validation ni assainissement adéquats. Un attaquant peut exploiter cette faille pour exécuter du code arbitraire. Prenons cet exemple :

import pickle
serialized_data = input("Enter serialized data: ")
deserialized_data = pickle.loads(serialized_data.encode('latin1'))  # Unsafe deserialization

Pour exploiter ce code, un attaquant pourrait créer un objet sérialisé qui, lors de sa désérialisation, exécute du code arbitraire, par exemple en ouvrant un shell inversé. Ce type d’attaque peut toucher des applications courantes, comme un panier d’achat en ligne. Imaginez que vous en utilisiez un et que votre panier soit enregistré sous la forme d’un code spécial. Si l’application ne vérifie pas soigneusement que ce code est sûr lorsqu’elle le recharge, un attaquant peut envoyer un code trompeur qui ressemble à un panier, mais qui exécute secrètement des actions malveillantes. Lorsque l’application exécute sans le savoir ce code dangereux, elle ouvre la voie à l’attaquant. Celui-ci peut voler vos données, manipuler l’application ou même prendre le contrôle de l’ensemble du système, ce qui constitue un risque de sécurité majeur.

Atténuer les vulnérabilités d’injection de code

Comprendre les vulnérabilités est une première étape, mais il est essentiel d’apprendre à les atténuer efficacement. Voici des mesures de protection contre les vulnérabilités évoquées précédemment :

Protéger les entrées contrôlées par l’utilisateur

Évitez d’exécuter directement les entrées utilisateur. Si nécessaire, appliquez des listes d’autorisation strictes qui n’acceptent que des formats d’entrée définis :

ALLOWED_COMMANDS = ["start", "stop", "restart"]
user_input = input("Enter your command: ")
if user_input in ALLOWED_COMMANDS:
    exec(user_input)
else:
    print("Invalid command.")

Utiliser eval() et les fonctions associées en toute sécurité

Utilisez des solutions plus sûres, comme literal_eval(), ou évitez complètement d’utiliser eval(). Si vous ne pouvez pas vous en passer, veillez à assainir les entrées :

from ast import literal_eval
user_input = input("Enter expression: ")
try:
    result = literal_eval(user_input)
except ValueError:
    print("Invalid expression.")

Valider et assainir les entrées

Utilisez des expressions régulières ou d’autres techniques de validation pour n’accepter que des noms de fichiers ou des chemins valides :

import re
user_input = input("Enter filename: ")
if re.match("^[a-zA-Z0-9_\-/]+\.txt$", user_input):
    with open(user_input, 'r') as file:
        content = file.read()
else:
    print("Invalid filename.")

Par exemple, avec literal_eval(), l’entrée 2 + 3 est évaluée comme la valeur numérique 5, tandis qu’une entrée comme __import__('os').system('rm -rf /') déclenche une ValueError au lieu d’exécuter la commande. Cela atténue efficacement les risques de sécurité associés à l’utilisation de eval() et offre une méthode plus sûre pour évaluer des expressions composées de littéraux.

Encadrer la construction dynamique de code

Utilisez des requêtes paramétrées ou des instructions préparées pour prévenir les injections SQL :

import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
input_username = input("Enter username: ")
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (input_username,))

Désérialiser en toute sécurité

Ne désérialisez que des données fiables. Si vous devez désérialiser des entrées utilisateur, utilisez des formats plus sûrs comme JSON ou procédez à une validation approfondie avant la désérialisation :

import json
serialized_data = input("Enter serialized data: ")
try:
    deserialized_data = json.loads(serialized_data)
except json.JSONDecodeError:
    print("Invalid data.")

Appliquer des contrôles d’accès stricts

Appliquer le principe du moindre privilège garantit que les utilisateurs et les processus disposent uniquement des accès — ou autorisations — nécessaires à leurs tâches. Cela réduit la surface d’attaque en limitant les actions possibles pour un attaquant qui exploite une vulnérabilité.

Veillez à mettre en place des contrôles d’accès stricts pour empêcher tout accès non autorisé aux zones sensibles de l’application. Dans la section suivante, vous découvrirez d’autres mesures qui peuvent renforcer la sécurité de votre base de code.

Règles de codage sécurisé en Python

Nous avons vu les moyens les plus efficaces d’atténuer les vulnérabilités d’injection de code, mais écrire du code sécurisé ne se limite pas à cela. Il faut aussi adopter un ensemble de bonnes pratiques et de règles pour créer des applications robustes et sécurisées. Lorsque vous écrivez du code Python, pensez à :

Utiliser des outils de sécurité pour développeurs afin de détecter et corriger les problèmes

Snyk propose gratuitement des outils de sécurité Python qui vous aident à détecter et à corriger les vulnérabilités dans votre code applicatif, vos dépendances open source, vos conteneurs, vos configurations IaC et bien plus encore. Vous pouvez intégrer Snyk à vos dépôts Git, à vos workflows CI/CD, à vos outils en ligne de commande et à vos IDE pour détecter et corriger les vulnérabilités.

Par exemple, Snyk détecte les dépendances open source vulnérables de votre application et vous montre comment les corriger facilement. Voici une liste de codes Python vulnérables dans des packages pip répertoriés dans la Snyk Vulnerability Database :

Base de données des vulnérabilités Snyk affichant une liste de vulnérabilités de paquets pip, notamment des paquets malveillants, des conditions de concurrence et des paramètres par défaut non sécurisés.

L’intégration d’outils comme Snyk à votre workflow de développement ajoute une couche de protection et contribue à rendre le processus de développement d’applications plus robuste et plus sécurisé. Snyk propose des articles utiles pour intégrer ses outils à votre workflow :

L’analyse de sécurité régulière avec Snyk offre de nombreux avantages, comme l’accès à une base de données de vulnérabilités à jour et à des outils de sécurité collaboratifs conçus pour le travail en équipe. Ses bénéfices vont au-delà de la simple protection de votre code : ils améliorent également l’ensemble de l’expérience de développement. Grâce à ses fonctionnalités adaptées à Python et à son approche axée sur la prévention des injections de code, Snyk transforme votre manière d’aborder la sécurité dans vos pratiques de programmation et devient une ressource précieuse pour produire des logiciels sécurisés et de grande qualité.

Adopter des pratiques de journalisation sécurisées

Évitez de consigner des données utilisateur sensibles ou des informations système qui pourraient être utiles à un attaquant. Veillez à utiliser des frameworks de journalisation compatibles avec des configurations sécurisées.

Instaurer un environnement de codage sécurisé

Voici d’autres stratégies que vous pouvez mettre en œuvre pour créer un environnement de codage sécurisé :

  • Examens réguliers du code et audits de sécurité : faites régulièrement examiner le code par des experts en sécurité pour détecter les vulnérabilités au plus tôt.

  • Maintenir les logiciels et les bibliothèques à jour : utilisez toujours les dernières versions des bibliothèques et des frameworks, qui intègrent souvent des correctifs de sécurité.

  • Encourager une culture axée sur la sécurité : faites de la sécurité une priorité et veillez à ce que tous les membres de l’équipe soient formés aux pratiques de codage sécurisé.

Pour conclure

Dans cet article, vous avez découvert les risques liés à l’injection de code en Python et l’importance de respecter les règles de codage sécurisé. L’injection de code peut prendre de nombreuses formes, mais les bonnes connaissances et des outils comme Snyk vous permettent d’en réduire les risques. N’attendez pas qu’un problème compromette votre travail. Intégrez les outils de sécurité Snyk à votre processus de développement Python pour rendre votre code plus sûr et plus efficace.

Détectez et corrigez les vulnérabilités Python

Sécurisez vos applications grâce à l’analyse des vulnérabilités de Snyk et à ses conseils de correction. 

Aucune carte bancaire requise.

Ou inscrivez-vous avec Azure AD Docker ID Bitbucket

En utilisant Snyk, vous acceptez de respecter nos politiques, notamment nos Conditions d’utilisation et notre Politique de confidentialité.

Publié dans: