2010-07-12 20 views
42

J'utilise des locals de thread pour stocker l'utilisateur actuel et demander des objets. De cette façon, je peux avoir un accès facile à la demande de n'importe où dans le programme (par exemple des formulaires dynamiques) sans avoir à les transmettre.Pourquoi l'utilisation des locales de thread dans Django est-elle mauvaise?

Pour mettre en œuvre le stockage fil de la population locale dans un middleware, j'ai suivi un tutoriel sur le site Django: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=18

Ce document a depuis été modifié pour suggérer d'éviter cette technique: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=20

De l'article D'un point de vue de la conception, les threadlocals sont essentiellement des variables globales, et sont soumis à tous les problèmes habituels de portabilité et de prédiction. que les variables globales impliquent généralement.

Plus important encore, d'un point de vue sécurité, les threadlocals représentent un risque énorme. En fournissant un magasin de données qui expose l'état des autres threads, vous fournissez à un thread de votre serveur Web un moyen de modifier potentiellement l'état d'un autre thread dans le système. Si les données threadlocal contiennent des descriptions d'utilisateurs ou d'autres données liées à l'authentification, ces données peuvent être utilisées comme base d'une attaque qui accorde l'accès à un utilisateur non autorisé ou expose les détails privés d'un utilisateur. Bien qu'il soit possible de construire un système threadlocal qui soit à l'abri de ce type d'attaque, il est beaucoup plus facile d'être sur la défensive et de construire un système qui n'est pas sujet à une telle vulnérabilité en premier lieu. Je comprends pourquoi les variables globales peuvent être mauvaises, mais dans ce cas, je cours mon propre code sur mon propre serveur, donc je ne vois pas quel danger deux variables globales posent. Est-ce que quelqu'un peut expliquer le problème de sécurité en cause? J'ai demandé à beaucoup de gens comment ils pourraient pirater mon application s'ils lisaient cet article et savent que j'utilise des sections locales, mais personne n'a été capable de me le dire. Je commence à soupçonner qu'il s'agit d'un avis tenu par des puristes qui se dédoublent les cheveux et qui aiment transmettre des objets de manière explicite.

+2

Par ailleurs - avez-vous l'exemple original? Il est supprimé maintenant et je veux l'utiliser ... – rslite

+2

Cet extrait est assez similaire à la page supprimée: http://djangosnippets.org/snippets/2179/ – hekevintran

+0

Middleware GlobalRequest: https://djangosnippets.org/snippets/2853 –

Répondre

39

Je ne suis pas du tout d'accord. TLS est extrêmement utile. Il doit être utilisé avec précaution, tout comme les globes doivent être utilisés avec précaution; mais dire qu'il ne devrait pas être utilisé du tout est aussi ridicule que de dire que les globals ne devraient jamais être utilisés.

Par exemple, je stocke la demande actuellement active dans TLS. Cela le rend accessible à partir de ma classe de journalisation, sans avoir à passer la requête à travers chaque interface - y compris beaucoup qui ne se soucient pas du tout de Django. Il me permet de faire des entrées de journal de n'importe où dans le code; l'enregistreur génère une sortie vers une table de base de données, et si une requête est active lorsqu'un journal est créé, il enregistre des éléments tels que l'utilisateur actif et ce qui a été demandé.

Si vous ne voulez pas qu'un thread ait la capacité de modifier les données TLS d'un autre thread, définissez votre TLS pour l'interdire, ce qui nécessite probablement l'utilisation d'une classe TLS native.Je ne trouve pas cet argument convaincant, cependant; Si un attaquant peut exécuter du code Python arbitraire en tant que backend, votre système est déjà fatalement compromis - il pourrait modifier n'importe quoi pour l'exécuter plus tard en tant qu'utilisateur différent, par exemple.

De toute évidence, vous souhaiterez effacer tout TLS à la fin d'une requête; dans Django, cela signifie effacer dans process_response et process_exception dans une classe middleware.

+0

Merci pour votre confirmation. Je me sens maintenant moins fou =). Si un attaquant peut lire les données threadlocal, alors il doit pouvoir SSH dans ma machine de toute façon. – hekevintran

+2

Pas nécessairement SSH, mais au moins d'avoir un certain contrôle sur le backend Python. Tout l'argument semble assez artificiel, de toute façon. –

+1

Je suis curieux de savoir exactement ce que vous voulez dire par "effacer tout TLS à la fin d'une requête". Effacer comment? Supprimer l'objet 'local' lui-même, ou juste l'attribut sur l'objet' local' dans lequel vous avez stocké la session? Bien que, pour être honnête, je ne suis même pas sûr s'il y a une différence pertinente entre ceux-ci. – CoreDumpError

10

Malgré le fait que vous puissiez mélanger des données provenant de différents utilisateurs, les locals de threads doivent être évités car ils masquent une dépendance. Si vous passez des arguments à une méthode, vous voyez et savez ce que vous passez. Mais un thread local est quelque chose comme un canal caché en arrière-plan et vous vous demandez peut-être qu'une méthode ne fonctionne pas correctement dans certains cas.

Il y a des cas où les locals de fil sont un bon choix, mais ils devraient être utilisés rarement et soigneusement!

+4

Mais ce n'est pas un problème car je sais que les locals de threads sont utilisés partout où j'appelle get_current_user(). Je n'ai de toute façon que deux variables dans les threads locaux. – hekevintran

9

Un exemple rapide sur la façon de créer un middleware TLS compatible avec le dernier Django 1.10:

# coding: utf-8 
# Copyright (c) Alexandre Syenchuk (alexpirine), 2016 

try: 
    from threading import local 
except ImportError: 
    from django.utils._threading_local import local 

_thread_locals = local() 

def get_current_request(): 
    return getattr(_thread_locals, 'request', None) 

def get_current_user(): 
    request = get_current_request() 
    if request: 
     return getattr(request, 'user', None) 

class ThreadLocalMiddleware(object): 
    def __init__(self, get_response): 
     self.get_response = get_response 

    def __call__(self, request): 
     _thread_locals.request = request 
     return self.get_response(request) 
+1

Cela ne fonctionne pas lorsque vous utilisez UWSGI. 'local()' est partagé –

+0

Pouvez-vous m'expliquer davantage, @JavierBuzzi? Voulez-vous dire que 'local()' est partagé entre threads lorsque vous utilisez UWSGI? Avez-vous une explication pour pourquoi est-ce? – alexpirine

+0

En raison de la façon dont UWSGI fonctionne, il partage tout ce qu'il y a entre tous les threads pour accélérer les choses. https://www.pythonanywhere.com/forums/topic/710/ - c'est assez facile à tester: pip installer uwsgi et le tester par vous-même - en note, je devais créer un middleware qui crée un cache à le début de la requête et l'effacer à la fin de la requête ... juste pour être sûr - sinon je recevais toutes sortes d'erreurs en l'exécutant dans uwsgi –