Skip to main content

Les dangers de assert en Python

Écrit par
Headshot of Dhruv Patel

Dhruv Patel

hero assert in python

18 août 2022

0 minutes de lecture

Il existe de nombreuses façons de trouver des bugs dans du code Python : le débogueur intégré (pdb), une bonne dose de tests unitaires, un débogueur dans un IDE comme Pycharm ou Visual Studio, des instructions try/catch, des instructions if/else, des assert statements ou encore la méthode éprouvée qui consiste à parsemer chaque recoin de votre base de code d’instructions print(), comme si cette pratique n’allait jamais passer de mode.

Les instructions assert nous aident à repérer rapidement les bugs et sont bien moins intrusives qu’une profusion d’instructions print. Mais contrairement aux instructions print, les instructions assert peuvent présenter des risques inattendus !

Cet article explique comment utiliser les assertions en toute sécurité et ce qui peut les rendre dangereuses. À la fin de votre lecture, vous saurez utiliser assert de manière optimale sans vous exposer involontairement à des problèmes de sécurité.

Utiliser les instructions assert en Python

Utiliser assert est simple ! Les instructions assert sont des instructions simples, ce qui signifie qu’elles peuvent tenir sur une seule ligne de code Python.

Voici un exemple simple d’instruction assert :

assert expression

Dans l’instruction ci-dessus, vous vérifiez que l’expression renvoie True, comme lors d’un contrôle booléen.

Si l’expression renvoie False, une exception est déclenchée. Par exemple :

>>> assert 'hi' == 'there'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Pour faciliter l’analyse d’une trace de pile, vous pouvez spécifier un message d’assertion qui sera inclus dans l’AssertionError. Cela peut être utile dans les scénarios de débogage complexes.

>>> assert 'hi' == 'there', 'Values do not match!'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: Values do not match!

La variable intégrée __debug__ de Python doit être définie sur True pour que les instructions assert soient exécutées. Si elle est définie sur False, les instructions assert ne s’exécuteront pas pendant l’exécution du programme. C’est un problème sérieux, car l’exécution de Python en mode optimisé (courante dans les environnements de production, où l’on définit l’option de ligne de commande PYTHONOPTIMIZE sur -O) définit la variable __debug__ sur False et désactive ainsi les instructions assert, ce qui peut introduire involontairement des vulnérabilités dans votre projet. Nous examinerons cela plus en détail ci-dessous.

Utilisé correctement, assert est un outil de débogage puissant. Cependant, plus son utilisation est complexe, plus le risque d’introduire des vulnérabilités de sécurité pendant le développement augmente.

En bref — Les instructions assert sont extrêmement utiles pour déboguer des applications Python, mais ne peuvent pas servir à la gestion normale des erreurs ni au contrôle du flux d’exécution, car elles peuvent :

  • Interrompre l’application en déclenchant une exception, ce qui peut provoquer des interruptions de service en production.

  • Provoquer des effets secondaires inattendus (pouvant inclure des vulnérabilités) dans votre application lorsque le mode de débogage est désactivé et que l’environnement d’exécution Python ignore silencieusement les instructions assert.

Utiliser assert en toute sécurité

Voyons comment utiliser les instructions d’assertion pour déboguer un programme en examinant un exemple d’application qui calcule des intérêts simples.

Commencez par installer une version stable de Python. Ouvrez ensuite un dossier et créez un fichier Python nommé safe_assert_example.py. Vous pouvez utiliser n’importe quel éditeur de code pris en charge.

Ajoutez le code suivant à safe_assert_example.py :

def simpleInterest(p, t, r):
print('The principal is', p)
assert isinstance(p, int) and p > 0, "Principal must be a positive integer"
print('The time period is', t)
assert isinstance(t, int) and t > 0, "Time must be a positive integer"
print('The rate of interest is', r)
assert isinstance(r, int) and r > 0, "Rate of interest must be a positive integer"
simpleInterest = (p * t * r) / 100
print('The Simple Interest is', simpleInterest)
return simpleInterest

simpleInterest(3, 5, 8)

Ce programme définit la fonction simpleInterest et calcule des intérêts simples à partir du principal, de la durée et du taux.

Pour calculer correctement les intérêts, vous devez saisir des entiers positifs pour le principal, la durée et le taux. Cet exemple utilise des instructions assert pour vérifier que les valeurs p, t et r sont des entiers positifs. Le programme renvoie un message d’erreur explicite lorsqu’une valeur n’est pas un entier positif.

À vous de jouer ! Exécutez le programme à l’aide de la commande suivante :

python safe_assert_example.py

Vous devriez obtenir le résultat ci-dessous, avec des intérêts simples de 1.2 :

PS E:\Sample\Extra\Assert Demo> python safe_assert_example.py
The principal is 3
The time period is 5
The rate of interest is 8
The Simple Interest is 1.2

Comme vous pouvez le constater, le programme s’exécute sans problème et ne renvoie aucun message d’erreur.

Voyons maintenant ce qui se passe si vous exécutez le programme avec une entrée non valide. Remplacez la valeur de t par la chaîne "time" au lieu de 5 :

simpleInterest(3, "time", 8)

Exécutez à nouveau le programme à l’aide de cette commande :

python safe_assert_example.py

Cette fois, vous devriez voir l’erreur suivante :

PS E: \Sample\Extra\Assert Demo» python safe assert example.py
The principal is 3
The time period is time
Traceback (most recent call last):
    File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8)
    File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 5, in simpleInterest assert isinstance(t, int) and t > 0, "Time must be a positive integer"
AssertionError: Time must be a positive integer

Ce résultat inclut une AssertionError. Cette exception montre que l’instruction assert fonctionne comme prévu : elle a bien détecté un argument de fonction non valide.

Dans cet exemple, l’assertion nous a aidés à déboguer le programme et en a interrompu l’exécution dès qu’elle a échoué.

Rappel : l’exécution du programme en mode optimisé (en définissant l’option de ligne de commande PYTHONOPTIMIZE sur -O) désactive silencieusement les instructions assert. Voyons maintenant l’impact de ce comportement sur l’application.

En conservant "`time=" comme valeur de t, exécutez le programme de calcul des intérêts simples en mode optimisé à l’aide de la commande suivante :

python -O safe_assert_example.py

Le résultat est le suivant :

PS E:\Sample\Extra\Assert Demo› python -0 safe assert example.py
The principal is 3
The time period is time
The rate of interest is 8
Traceback (most recent call last):
    File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8)
    File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 8, in simpleInterest simpleInterest = (p * t * r)/100
TypeError: unsupported operand type(s) for /: 'str' and 'int'

Remarquez que, même si l’argument de la fonction n’est pas valide, le résultat ci-dessus ne contient pas l’AssertionError attendue. Python renvoie plutôt une TypeError, car vous avez essayé de multiplier des variables de type entier et chaîne de caractères. Cela s’explique par le fait que les instructions assert n’ont pas été exécutées et nous indique que nous devrions envisager d’autres solutions, comme :

  • Ajouter des annotations de type pour détecter les appels incorrects à simpleInterest avant l’exécution du code.

  • Utiliser des instructions if plutôt que des assertions pour vérifier les types à l’exécution.

Utiliser assert de façon dangereuse

Les instructions assert doivent servir uniquement aux tests et au débogage, et non aux environnements de production. Comme elles ne s’exécutent que lorsque la variable __debug__ est définie sur True, l’interpréteur Python peut les désactiver.

Comme le montre l’exemple ci-dessus, l’exécution de Python en mode optimisé avec -O empêche les instructions assert de fonctionner, car cette option définit __debug__ sur False.

Par conséquent, il est dangereux d’utiliser des instructions assert pour toute tâche devant s’exécuter en production. Par exemple, n’utilisez jamais d’assertions pour valider des jetons ou des entrées utilisateur. Ces instructions ne s’exécuteront pas en production : les jetons et les entrées utilisateur ne seront donc pas validés, ce qui risque d’introduire des vulnérabilités dans votre application.

Pour illustrer la gravité de l’utilisation d’assertions à des fins de validation en production, prenons l’exemple d’une fonction d’autorisation qui reçoit un tableau d’utilisateurs et leur permet de poursuivre en fonction de leur niveau d’accès. Le programme utilise d’abord assert pour vérifier que la liste ou le tableau n’est pas nul et qu’il s’agit bien d’une instance de liste. Ensuite, il accorde un accès complet à l’application aux utilisateurs ayant le rôle d’administrateur. Si un rôle admin figure dans la liste des utilisateurs, aucune erreur d’assertion ne sera déclenchée. En revanche, si aucun rôle admin n’y figure, une erreur sera déclenchée.

Collez ensuite ce code dans un fichier nommé unsafe_assert_example.py :

def authorize_admin_user(user_roles):
    assert isinstance(user_roles,list) and user_roles != [], "No user roles found"

    assert 'admin' in user_roles, "No admin role found."
    print("You have full access to the application.")

authorize_admin_user(['admin','user'])

La fonction authorize_admin_user vérifie les rôles de l’utilisateur. Si celui-ci est admin, elle indique qu’il dispose d’un accès complet à l’application. En général, les rôles de l’utilisateur sont chargés depuis la base de données ; si admin en fait partie, l’utilisateur obtient un accès complet à l’application. Sinon, l’assertion échoue et déclenche une exception.

La dernière ligne du fichier appelle la fonction authorize_admin_user en lui transmettant une liste contenant deux rôles : admin et user.

Run python unsafe_assert_example.py:

$ python unsafe_assert_example.py
You have full access to the application

Comme prévu, l’utilisateur obtient un accès complet à l’application.

Appelons la fonction avec un rôle utilisateur qui ne comprend pas le rôle admin. Modifiez la dernière ligne de code de unsafe_assert_example.py comme suit :

authorize_admin_user(['user'])

Exécutez ensuite le programme et observez le résultat :

$ python unsafe_assert_example.py
Traceback (most recent call last):
  File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 6, in <module>
    authorize_admin_user(['user'])
  File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 3, in authorize_admin_user
    assert 'admin' in user_roles, "No admin role found."
AssertionError: No admin role found.

Comme prévu, la fonction déclenche une AssertionError, car admin ne figure pas dans la liste des rôles.

Exécutez maintenant le même programme avec l’optimisation activée :

python -O unsafe_assert_example.py

Le résultat correspondra à l’image ci-dessous. Bien qu’aucun rôle admin ne figure dans la liste, l’autorisation admin a été accordée, car l’assertion ne s’est pas exécutée.

PS E:\Sample\Extra\Assert Demo> python -0 unsafe_assert_example.py
You have full access to the application
You have full access to the application
PS E:\Sample\Extra\Assert Demo>

De plus, si vous modifiez l’appel de la fonction pour lui transmettre une liste d’entiers et exécutez le programme avec la même optimisation, vous constaterez encore que les instructions assert doivent servir au débogage, et non à la validation.

Remplacez l’appel de la fonction par authorize_admin_user([1,2]) et exécutez le programme à l’aide de la même commande : python -O unsafe_assert_example.py.

Le résultat du programme reste le même.

Dans notre exemple de calcul des intérêts simples, les conséquences de la non-exécution des instructions assert étaient limitées. Même si nous n’avons pas obtenu le message d’erreur attendu, le message d’erreur de Python nous a permis de comprendre le problème. Plus important encore, nous n’avons introduit aucune vulnérabilité, car nous utilisions les instructions assert à des fins de débogage sans risque.

Dans cet exemple, nous avons utilisé assert de façon dangereuse et rendu notre application Python vulnérable en accordant des droits d’administrateur à un utilisateur qui n’était pas administrateur.

Si vous souhaitez approfondir ces exemples, vous trouverez tout le code de cet article dans ce dépôt.

À faire et à ne pas faire avec assert

Assert aide les développeurs à déboguer facilement du code Python en écrivant moins de lignes. Cependant, une utilisation inappropriée peut introduire involontairement des problèmes de sécurité dans vos applications.

Désactiver les assertions en production peut avoir des conséquences désastreuses. Cette pratique peut créer diverses portes dérobées et points de rupture dans l’application, dont des acteurs malveillants peuvent tirer parti.

Les assertions doivent avant tout servir à isoler la cause première d’un bug et ne doivent jamais être utilisées pour la gestion standard des erreurs. Elles peuvent être utiles, mais il est essentiel de les réserver au débogage et aux tests, et de ne pas s’en servir pour contrôler le flux logique d’un programme ou d’une application. Utilisez-les uniquement dans des situations sans risque. Pour remplacer assert, les tests unitaires, les débogueurs, les conditions et les validateurs sont de meilleures options, plus sûres.

Maintenant que vous savez utiliser assert en toute sécurité, consultez l’aide-mémoire sur les bonnes pratiques Python pour en savoir plus sur la sécurisation de vos applications sur toutes vos plateformes et dans tous vos environnements.

Autres ressources sur le développement et la sécurité en Python

Vous pouvez également essayer gratuitement notre outil de vérification du code Python en ligne pour voir comment le moteur Snyk Code analyse votre code à la recherche de problèmes de sécurité et de qualité.

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.