Fiche pratique des bonnes pratiques de sécurité en Python
Frank Fischer
27 septembre 2021
0 minutes de lecture
En 2019, Snyk a publié sa première fiche pratique sur Python. Depuis, de nombreux aspects de la sécurité Python ont évolué. Forts de notre expérience en tant qu’entreprise spécialisée dans la sécurité des développeurs et des bonnes pratiques propres à Python, nous avons compilé cette fiche pratique mise à jour pour vous aider à sécuriser votre code Python. Avant d’aller plus loin, je tiens à remercier tout particulièrement Chibo et Daniel pour leur aide dans la préparation de cette fiche !
Téléchargez la fiche pratique des bonnes pratiques de sécurité en Python 2021
Voici les conseils de sécurité Python que nous allons aborder :
Assainissez toujours les données externes
Analysez votre code
Soyez prudent lorsque vous téléchargez des packages
Vérifiez les licences de vos dépendances
N’utilisez pas la version de Python fournie par le système
Tirez parti des environnements virtuels Python
Définissez
DEBUG = Falseen productionSoyez prudent avec le formatage des chaînes
Sérialisez et désérialisez avec la plus grande prudence
Utilisez les annotations de type Python
Avant de commencer, une précision importante : les données de Snyk sur l’écosystème Python, ainsi que les recherches universitaires, montrent que Python n’est ni plus ni moins sécurisé que les autres langages largement utilisés. Cette fiche pratique s’adresse spécifiquement aux adeptes de Python. Nous vous recommandons de consulter toutes nos autres fiches pratiques de sécurité pour découvrir comment rester en sécurité dans d’autres écosystèmes.
1. Assainissez toujours les données externes
Les données externes constituent un vecteur d’attaque pour toute application : elles peuvent servir à mener des attaques par injection, XSS ou déni de service (DOS). Pour assurer la sécurité de Python, il convient de toujours assainir les données provenant de sources externes (en supprimant les informations sensibles), qu’elles proviennent d’un formulaire utilisateur, de l’extraction de données d’un site Web ou d’une requête de base de données. Assainissez-les dès leur arrivée dans l’application afin d’éviter toute manipulation non sécurisée. Vous réduisez ainsi le risque que votre application traite accidentellement des données sensibles non assainies.
Pour commencer, il est toujours préférable de vérifier le format attendu des données en entrée plutôt que d’essayer de gérer les exceptions. Nous vous recommandons également d’utiliser des bibliothèques bien maintenues pour l’assainissement. En voici deux :
schema est « une bibliothèque permettant de valider des structures de données Python, telles que celles obtenues à partir de fichiers de configuration, de formulaires, de services externes ou de l’analyse de la ligne de commande, puis converties depuis JSON/YAML (ou un autre format) en types de données Python ».
bleach est « une bibliothèque d’assainissement HTML basée sur une liste d’éléments autorisés, qui échappe ou supprime les balises et les attributs ».
Les principaux frameworks disposent de leurs propres fonctions d’assainissement, comme flask.escape() de Flask ou django.utils.html.escape() de Django. Ces fonctions visent à sécuriser les entrées HTML potentiellement malveillantes, telles que :
Cette approche a toutefois ses limites : les bibliothèques ne peuvent pas tout faire. Elles sont spécialisées dans leur domaine. Pensez à lire cet article jusqu’au bout pour en savoir plus sur la manipulation d’autres formats de données, comme XML, qui peut également contenir des données malveillantes.
Une autre solution courante consiste à confier le rendu HTML à des moteurs de templates comme Jinja. Il offre de nombreuses fonctionnalités, notamment l’échappement automatique pour empêcher les attaques XSS grâce à MarkupSafe.
L’assainissement permet également d’empêcher l’utilisation de données comme des commandes. L’injection SQL en est un exemple courant. Au lieu de concaténer des chaînes et des variables pour générer une requête SQL, il est préférable d’utiliser des paramètres nommés afin d’indiquer à la base de données ce qui doit être traité comme une commande et ce qui doit être considéré comme une donnée.
Mieux encore, utilisez un outil de mappage objet-relationnel (ORM), comme sqlalchemy, qui permettrait d’écrire la requête de l’exemple ainsi :
Vous obtenez ainsi un code plus lisible, ainsi que des optimisations ORM comme la mise en cache, et davantage de sécurité et de performances !
Pour en savoir plus, consultez notre fiche pratique sur l’injection SQL.
2. Analysez votre code
Les développeurs disposent de nombreux outils d’analyse statique du code pour assurer la sécurité Python. Passons en revue trois niveaux d’outils.
Tout d’abord, les linters. Depuis des décennies, PEP8 sert de guide de style pour Python. Différents outils sont disponibles (et intégrés aux IDE) pour vérifier le respect de ce guide, comme pep8, pylint, flake8, entre autres.
Ensuite, des outils comme bandit transforment le code en arbre syntaxique abstrait (AST) et l’analysent pour détecter les problèmes de sécurité courants. Ils vont plus loin que les linters classiques, qui opèrent au niveau syntaxique. Cependant, bandit reste limité par sa représentation intermédiaire et ses performances. Par exemple, il ne détecte pas les problèmes liés au flux de données (appelés analyse de taint), qui peuvent entraîner de graves failles (comme les injections SQL ou les attaques XSS).
Enfin, les outils de tests de sécurité des applications statiques (SAST), comme Snyk Code, réalisent une analyse sémantique et prennent même en compte les problèmes complexes touchant plusieurs fichiers. Contrairement à d’autres outils de cette catégorie, Snyk Code est conçu pour les développeurs : il analyse rapidement le code et s’intègre à l’IDE (ou directement à votre ligne de commande). Snyk Code explique ses résultats très précis et vous aide à corriger vos problèmes de sécurité Python, notamment à l’aide d’exemples. Et pour couronner le tout, sa prise en main est simple et son utilisation est gratuite pour les projets open source (avec un nombre limité de tests non OSS).
3. Soyez prudent lorsque vous téléchargez des packages
L’installation de packages est simple, mais ceux-ci peuvent aussi facilement introduire des vulnérabilités de sécurité Python. Les développeurs utilisent généralement le programme d’installation standard des packages Python (pip), qui s’appuie sur le Python Package Index (PyPI). Il est donc important de comprendre comment les packages sont ajoutés à PyPI.
PyPI a mis en place une procédure pour signaler les problèmes de sécurité. Lorsqu’une personne signale un package malveillant ou un problème au sein de PyPI, celui-ci est pris en charge. Toutefois, les packages ajoutés à PyPI ne sont pas examinés : on ne peut raisonnablement pas demander cela aux bénévoles qui assurent la maintenance de PyPI.
Il est donc prudent de partir du principe que PyPI contient des packages malveillants et d’agir en conséquence. Vous pouvez notamment vous renseigner sur le package que vous souhaitez installer et vérifier soigneusement son nom (un package dont le nom comporte une faute de frappe courante dans celui d’un package populaire pourrait exécuter du code malveillant). Avant de télécharger un package, vérifiez-le sur Snyk Advisor.

Une recherche rapide d’un package dans Snyk Advisor vous donne de nombreuses informations à son sujet : soutien de la communauté, historique des bugs et des correctifs, et bien plus encore. Snyk Advisor affiche également la commande d’installation en haut de la page de résultats. Pour éviter le typosquattage, prenez l’habitude de copier-coller l’orthographe indiquée. Snyk Advisor vous aide à déterminer si un package est fiable. Vous pouvez consulter l’historique des problèmes de sécurité et le délai nécessaire à leur résolution.
Autre bonne pratique : utiliser des environnements virtuels pour isoler les projets les uns des autres. Utilisez également pip freeze ou une commande équivalente pour consigner les modifications de l’environnement dans la liste des dépendances.
Pour maintenir des références à jour, Snyk Open Source s’appuie sur une base de données de vulnérabilités de premier plan qui répertorie les problèmes de sécurité et leurs correctifs potentiels. Snyk Open Source analyse les dépendances à partir des exigences et vous fournit des informations exploitables sur les vulnérabilités détectées dans les dépendances directes et transitives, ainsi que des conseils pour les corriger sans attendre.
4. Vérifiez les licences de vos dépendances
Avant d’utiliser un projet open source, il est important de comprendre les conditions de sa licence. Les projets open source sont gratuits et librement accessibles, mais leur utilisation peut tout de même être soumise à des conditions. Celles-ci précisent généralement comment le logiciel peut être utilisé, si les modifications que vous y apportez doivent être rendues publiques et d’autres exigences similaires. Familiarisez-vous avec les licences open source applicables aux projets que vous utilisez, afin de ne pas vous exposer à des risques juridiques.
Si le projet adopte une licence plus restrictive que prévu (GPL, SSPL, etc.), vous risquez de vous retrouver dans une impasse : vous devrez soit respecter les conditions de la licence, soit cesser d’utiliser le projet. Par ailleurs, si vous devez modifier un projet sans licence, vous pourriez enfreindre le droit d’auteur.
Pour garantir la pérennité de votre projet et éviter de vous exposer à des risques juridiques et de sécurité Python inutiles, analysez les licences et les vulnérabilités des dépendances de votre projet, puis corrigez les problèmes détectés.
Snyk Open Source vous aide à gérer la conformité des licences open source. Cette solution offre aux développeurs une visibilité de bout en bout et une gouvernance flexible.
5. N’utilisez pas la version de Python fournie par le système
La plupart des systèmes POSIX sont fournis avec une version de Python. Le problème, c’est que la plupart de ces distributions intégrées ne sont pas à jour.
Veillez donc à utiliser la dernière version de Python disponible pour votre système, ainsi que les conteneurs officiels conçus pour exécuter Python, et à les maintenir à jour. Snyk est là pour vous aider. Analysez vos conteneurs pour repérer les mises à jour nécessaires avec Snyk Container et vérifiez vos dépendances avec Snyk Open Source.
6. Utilisez des environnements virtuels Python
Python permet de répartir le développement d’applications dans des environnements virtuels. Un environnement virtuel isole l’interpréteur Python, les bibliothèques et les scripts qui y sont installés. Ainsi, au lieu d’utiliser une version globale de Python et des dépendances globales pour tous vos projets, vous pouvez créer des environnements virtuels propres à chaque projet, avec leurs propres versions de Python et de ses dépendances !
La plupart des IDE, des interfaces en ligne de commande et des tableaux de bord, comme Anaconda Navigator, proposent des fonctions intégrées pour basculer entre les environnements virtuels.
Conseil de pro : depuis Python 3.5, l’utilisation de venv est recommandée ; depuis la version 3.6, pyvenv est obsolète.
Les environnements virtuels facilitent le développement, le packaging et le déploiement d’applications Python sécurisées. Leur utilisation est vivement recommandée. Consultez la documentation Python sur venv pour en savoir plus.
7. Définissez DEBUG = False en production
Dans un environnement de développement, il est utile de disposer de messages d’erreur détaillés. En production, en revanche, vous devez éviter toute fuite d’informations susceptibles d’aider un attaquant à en savoir plus sur votre environnement, vos bibliothèques ou votre code.
Par défaut, le débogage est activé dans la plupart des frameworks. Django, par exemple, l’active dans le fichier settings.py. En production, veillez à définir False pour désactiver le débogage et éviter de divulguer aux attaquants des informations sensibles sur votre application.
Conseil de pro : lors du déploiement en production, configurez votre système de déploiement continu pour vérifier après le déploiement que ce paramètre est bien désactivé.
8. Soyez prudent avec le formatage des chaînes
Malgré l’idée de Python selon laquelle il n’existe qu’une seule façon de faire les choses, le langage propose en réalité quatre méthodes différentes pour formater les chaînes (trois pour les versions antérieures à Python 3.6).
Le formatage des chaînes est devenu progressivement plus souple et plus puissant (les f-strings sont particulièrement intéressantes), mais plus la flexibilité augmente, plus le risque d’exploitation grandit. Les utilisateurs de Python doivent donc réfléchir attentivement à la manière dont ils formatent les chaînes contenant des données fournies par les utilisateurs.
Python intègre un module nommé string. Ce module comprend la classe Template, qui permet de créer des chaînes modèles.
Prenons l’exemple suivant.
Avec le code ci-dessus, la variable greeting est évaluée ainsi : « Hello World, my name is Hayley. »
Ce format de chaîne est un peu contraignant, car il nécessite une instruction d’importation et est moins souple avec les types. Il n’évalue pas non plus les instructions Python comme le font les f-strings. Ces limites font des chaînes modèles un excellent choix pour traiter les entrées des utilisateurs.
Autre remarque rapide sur le formatage des chaînes : soyez particulièrement vigilant avec le SQL brut, comme indiqué plus haut.
9. (Dé)sérialisez avec la plus grande prudence
Python intègre un mécanisme appelé « pickling » pour sérialiser et désérialiser les objets Python, via le module pickle. Ce mécanisme est connu pour être peu sûr : il est donc recommandé de l’utiliser avec beaucoup de prudence et uniquement avec des sources de données fiables.
YAML est devenu de facto la nouvelle norme en matière de sérialisation et de désérialisation. Le package PyYAML permet de sérialiser des types de données personnalisés au format YAML, puis de les désérialiser. Mais PyYAML présente plusieurs vecteurs d’attaque potentiels. Pour sécuriser facilement et efficacement son utilisation, employez yaml.SafeLoader() plutôt que yaml.Loader() comme chargeur.
Cela empêche le chargement de classes personnalisées, tout en prenant en charge les types standard comme les tables de hachage et les tableaux.
Le XML est un autre cas d’usage courant. On utilise souvent des bibliothèques standard, mais elles sont vulnérables aux attaques classiques : attaques par déni de service ou expansion d’entités externes (lorsqu’une source externe est référencée). Le package defusedxml constitue une première ligne de défense efficace. Il protège contre ces problèmes de sécurité courants liés au XML.
Conseil bonus, sans lien avec la sécurité : utilisez les annotations de type Python
Les indications de type ont été introduites avec la version 3.5. Le runtime Python ne les applique pas, mais les outils comme les vérificateurs de type, les IDE, les linters et les outils SAST peuvent tirer parti de précisions supplémentaires de la part des développeurs. Voici un exemple qui illustre cette idée :
Literal[...] a été introduit avec la version 3.8 et n’est pas appliqué par le runtime (dans notre exemple, vous pouvez transmettre n’importe quelle chaîne), mais les vérificateurs de type peuvent désormais détecter que le paramètre ne fait pas partie des valeurs autorisées et vous avertir. C’est une fonctionnalité très utile, et pas seulement pour la sécurité en Python.
Remarque : comme elles ne sont pas appliquées par le runtime, les indications de type ont une utilité limitée en matière de sécurité.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
