Requests: L'en-tête d'autorisation de la session n'est pas envoyé lors de la redirection

Créé le 28 déc. 2015  ·  35Commentaires  ·  Source: psf/requests

J'utilise des demandes pour frapper developer-api.nest.com et définir un en-tête d'autorisation avec un jeton de porteur. Sur certaines demandes, cette API répond avec une redirection 307. Lorsque cela se produit, j'ai toujours besoin que l'en-tête d'autorisation soit envoyé lors de la demande suivante. J'ai essayé d'utiliser requests.get() ainsi qu'une session.

Je suppose que je pourrais contourner ce problème en n'autorisant pas les redirections, en détectant le 307 et en émettant moi-même la nouvelle requête, mais je me demande s'il s'agit d'un bogue. Dois-je m'attendre à ce que l'en-tête Authorization soit envoyé sur toutes les demandes effectuées dans le cadre d'une session?

In [41]: s = requests.Session()

In [42]: s.headers
Out[42]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0'}

In [43]: s.headers['Authorization'] = "Bearer <snip>"

In [45]: s.get("https://developer-api.nest.com/devices/thermostats/")
Out[45]: <Response [401]>

In [46]: s.get("https://developer-api.nest.com/devices/thermostats/")
Out[46]: <Response [200]>

In [49]: Out[45].history
Out[49]: [<Response [307]>]

In [50]: Out[46].history
Out[50]: []

In [51]: Out[45].request.headers
Out[51]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0'}

In [52]: Out[46].request.headers
Out[52]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0', 'Authorization': 'Bearer <snip>'}
Bug

Commentaire le plus utile

Il existe deux solutions de contournement spécifiques à Nest.

La première consiste à passer le paramètre auth avec access_token plutôt que d'utiliser l'en-tête Authorization. J'ai trouvé cela sur https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Une autre consiste à enregistrer un dictionnaire avec les en-têtes que vous utiliseriez, à ne pas suivre les redirections, puis à faire une deuxième demande en passant à nouveau les en-têtes:

    headers = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
    initial_response = requests.get('https://developer-api.nest.com', headers=headers, allow_redirects=False)
    if initial_response.status_code == 307:
        api_response = requests.get(initial_response.headers['Location'], headers=headers, allow_redirects=False)

Tous les 35 commentaires

Où est la redirection?

Ah, un domaine différent. firebase-apiserver03-tah01-iad01.dapi.production.nest.com

Oui, donc c'est un peu délibéré: nous sommes très agressifs avec la suppression des en-têtes d'autorisation lorsqu'ils sont redirigés vers un nouvel hôte. Il s'agit d'une fonctionnalité de sécurité pour gérer la CVE 2014-1829 , qui a été causée par la persistance des en-têtes sur les redirections hors hôte.

Cependant, d'un certain point de vue, nous avons toujours un bogue ici, car vous définissez l'en-tête Authorization sur Session , pas sur la requête. En principe, ce que cela signifie est "Je me fiche de savoir où va la redirection, ajoutez l'en-tête". Je pense toujours que je préférerais cette approche, qui garantit au moins que nous ne sommes pas ouverts à toute forme d'attaque, même si cela rend cette instance spécifique un peu plus délicate. Cependant, je suis ouvert à la conviction que nous sommes trop paranoïaques ici.

Cependant, je suis ouvert à la conviction que nous sommes trop paranoïaques ici.

Je suis moins ouvert à être convaincu mais prêt à écouter.

Cela dit, un mécanisme d'authentification séparé pourrait être écrit pour conserver ces en-têtes dans les domaines _autorisés_, ce qui nous obligerait à effectuer un travail dans rebuild_auth .

Je ne contesterai pas la sécurité de la façon dont cela fonctionne actuellement. Ce serait bien d'avoir un mécanisme pour opter pour le comportement «dangereux». Peut-être définir un domaine de base pour conserver ces en-têtes vers (nest.com, dans ce cas) ou peut-être une liste de domaines auxquels les envoyer.

Ouais, c'est beaucoup plus de complexité que le cœur des demandes ne le fournira jamais. C'est pourquoi je me demande si une classe / un gestionnaire Auth séparé pourrait mieux fonctionner pour ce genre de chose. Je ne suis pas convaincu que cela fonctionnera car je suis assez certain que nous n'appelons pas inconditionnellement prepare_auth .

Cela ne fonctionnera pas dans le modèle standard car nous n'appelons pas inconditionnellement prepare_auth . Cependant, un adaptateur de transport peut être utilisé pour remplir ce rôle, même s'il s'agit d'une utilisation légèrement inhabituelle de cette API.

Je pense qu'un TA est absolument la mauvaise chose à recommander ici.

  • Si l'authentification est fournie à une session, elle doit être envoyée pour chaque demande effectuée par la session.
  • Peut-être devrions-nous supprimer session.auth . Ce n'est pas particulièrement utile.

Si l'authentification est fournie à une session, elle doit être envoyée pour chaque demande effectuée par la session.

Je suis fondamentalement en désaccord. Les sessions ne sont pas utilisées pour un seul domaine, si elles l'étaient, cela ne poserait aucun problème.

Peut-être devrions-nous supprimer session.auth. Ce n'est pas particulièrement utile.

Je pense que c'est utile. Je pense que ce serait mieux si l'attribution d'un tuple n'était pas autorisée. Je préfère voir une classe Auth qui spécifie les domaines pour lesquels l'utiliser. Nous pourrions simplement adopter le AuthHandler de

J'ai également besoin que cela soit corrigé afin que je puisse faire en sorte que les en-têtes persistent pour les redirections.

@jtherrmann S'il s'agit d'un en-tête d'authentification, le moyen le plus simple de contourner le problème consiste à définir un gestionnaire d'authentification au niveau de la session qui place simplement toujours l'en-tête souhaité sur la demande.

Y a-t-il eu des progrès ou une considération supplémentaire à cet égard?
Je rencontre le même problème.

@ethanroy Aucune considération supplémentaire au-delà de ma suggestion d'utiliser un gestionnaire d'authentification au niveau de la session, dans le commentaire directement au-dessus du vôtre.

Connexes: si une session redirige et supprime l'auth, appeler à nouveau get réapplique l'auth et utilise la redirection mise en cache. Alors frappez deux fois et vous entrez. Comportement prévu?

>>> s = requests.Session()
>>> s.headers.update({"Authorization": "Token {}".format(API_TOKEN)})
>>> s.get(url)

<Response [403]>

>>> s.get(url)

<Response [200]>

@GregBakker Oui, ish. C'est une confluence de comportements prévus. Cependant, ce bogue note que le 403 original ne devrait pas se produire.

@Lukasa quand vous dites que "la façon la plus simple de contourner le problème est de définir un gestionnaire d'authentification au niveau de la session", est-ce quelque chose qui fonctionne aujourd'hui? D'après ce que je vois dans le code, la réponse est non, mais votre formulation me fait me demander si je manque quelque chose. Vous parlez de définir l'attribut d'authentification de session, non?

Ouais, ça devrait marcher.

@jwineinger alors comment avez-vous réussi à contourner ce problème? il semble toujours se comporter de la même manière.

Il existe deux solutions de contournement spécifiques à Nest.

La première consiste à passer le paramètre auth avec access_token plutôt que d'utiliser l'en-tête Authorization. J'ai trouvé cela sur https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Une autre consiste à enregistrer un dictionnaire avec les en-têtes que vous utiliseriez, à ne pas suivre les redirections, puis à faire une deuxième demande en passant à nouveau les en-têtes:

    headers = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
    initial_response = requests.get('https://developer-api.nest.com', headers=headers, allow_redirects=False)
    if initial_response.status_code == 307:
        api_response = requests.get(initial_response.headers['Location'], headers=headers, allow_redirects=False)

J'ai rencontré ce même problème et je l'ai contourné en remplaçant la méthode rebuild_auth dans une implémentation requests.Session :

from requests import Session

class CustomSession(Session):
    def rebuild_auth(self, prepared_request, response):
        return

s = CustomSession()
s.get(url, auth=("username", "password"))

@ sigmavirus24 qu'est-ce qui ne va pas avec la solution de @ gabriel-loo? Problèmes de sécurité?

@ j08lue oui. Veuillez lire le fil de discussion. Il existe des CVE associés à la non- suppression de l'authentification avant de suivre des redirections arbitraires vers un nouveau domaine. Pensez au problème de cette façon:

Je fais des requêtes à api.github.com et un attaquant parvient à me faire suivre une redirection vers another-domain.com qu'il contrôle et je passe mon token avec un accès en écriture à mes référentiels (y compris les requêtes) puis il peut apparaître comme si je fais des commits sur des demandes alors qu'en fait ils font ces commits via l'API. Ils peuvent inclure du code dans les demandes qui affaiblira sa posture de sécurité et vous nuira peut-être activement. C'est ce qui peut arriver lorsque vous envoyez sans condition vos informations d'authentification à chaque redirection.

Même dans ce cas, disons que la redirection n'est pas malveillante, êtes-vous réellement à l'aise de divulguer vos informations d'identification pour un service à une autre entreprise ou service? Le service d'origine peut stocker des données confidentielles pour vous, vos clients ou autre chose. Même si le nouveau domaine vers lequel vous avez été redirigé n'utilise pas vos informations d'identification mais les enregistre potentiellement en tant que données inattendues, une personne qui les attaque et peut récupérer ces journaux peut ensuite utiliser vos informations d'identification contre le domaine d'origine si elle peut comprendre où ils appartiennent. Êtes-vous vraiment prêt à prendre ce risque?

Merci pour l'illustration, @ sigmavirus24. Si ce problème empêche en fin de compte de transférer des en-têtes sensibles vers des redirections, alors pourquoi ce thread est-il toujours ouvert? Je ne pouvais pas penser à une erreur plus appropriée que celle que vous obtenez (403), donc il n'y a pas besoin de bogue d'action ici, n'est-ce pas? Ou qu'avez -vous en tête , @Lukasa?

J'ai récemment rencontré ce problème en travaillant avec une API non publique. Les problèmes de sécurité ont tout à fait un sens comme raison pour supprimer l'authentification sur les redirections. Je pense qu'une solution comme celle de @ gabriel-loo est quelque chose que les gens peuvent envisager s'ils pensent être dans un environnement suffisamment sûr pour le faire. Ou le gestionnaire de niveau session. Ou trouvez un autre moyen de contourner le problème en ignorant complètement la redirection comme suggéré ci-dessus, si cela est possible. Donc, conformément à la vue, ce n'est pas vraiment un bogue.

Cependant, j'ai brûlé plus de temps que ce dont j'avais probablement besoin pour comprendre pourquoi une poignée d'autres clients HTTP non Python _did_ transmettaient l'en-tête d'authentification et fonctionnaient bien alors que ce n'était pas le cas. Une suggestion: il pourrait être intéressant d'émettre un avertissement via warnings ici pour que les appelants soient plus clairs lorsque l'en-tête est présent et en cours de suppression. J'imagine qu'il est rare que ce soit quelque chose dont un appelant ne voudrait _pas_ être averti.

@tlantz normalement, cela semble assez raisonnable. Les requêtes en tant que projet (ainsi que urllib3, l'une de ses dépendances) ont provoqué une grande colère lorsqu'il émet une sorte d'avertissement que ce soit via le module d'avertissement ou via la journalisation. De plus, le module d'avertissement est destiné aux choses sur lesquelles les gens devraient agir, par exemple, ne pas utiliser une version de Python qui a été compilée avec une version récente d'OpenSSL.

Dans la plupart des cas, ce comportement n'est pas aussi problématique que, par exemple, l'impossibilité de vérifier un certificat pour une connexion TLS. Cela ne vous aide évidemment pas, ni à quiconque a exprimé sa frustration sincère et valable sur cette question. Dans cet esprit, je me demande s'il ne serait pas préférable d'essayer d'enregistrer cela au niveau DEBUG . Si quelqu'un utilise la journalisation (généralement une pratique décente) et active ce niveau, il apparaîtra pour lui. De plus, étant donné le peu de journaux des requêtes, cela sera assez important en tant que journal de débogage. Cela vous semble-t-il un compromis équitable?

Ouais, cela semble être un compromis totalement équitable. Raisonner autour de warnings sens pour moi. Je pense qu'au moment où vous avez été perplexe pendant environ 30 minutes, vous ajoutez généralement logging autour de vos propres affaires de toute façon à DEBUG , donc je pense qu'un message DEBUG toucherait 95% des cas où les gens sont coincés à essayer de comprendre ce qui ne fonctionne pas.

J'utilise une session pour contenir l'en-tête d'autorisation, mais il n'est pas envoyé dans une redirection
demandes (2.18.4)

Un collègue et moi avons passé au moins quelques heures à déboguer un problème directement lié à ce comportement. Mon cas d'utilisation redirige un appel API de api.my-example-site.org vers www.api.my-example-site.org . Les en-têtes ont été supprimés lors de la redirection.

S'il s'agit d'un comportement prévu (ou s'il ne sera pas modifié dans un proche avenir), pouvons-nous au moins l'ajouter à la documentation? J'ai lu et relu les documents en essayant de comprendre ce que je faisais de manière incorrecte, et j'ai même lu tout le code de la classe Request . Si j'avais vu un avertissement à propos de ce comportement dans la documentation, j'aurais résolu mon problème en quelques minutes (ce qui est le temps qu'il a fallu après avoir trouvé ce fil). Peut-être que nous lisions la mauvaise partie de la documentation, cependant.

Bonjour @ndmeiri , nous avons un appel à ce sujet dans le guide de démarrage rapide des demandes sous la rubrique En- têtes personnalisés . Si vous pensez qu'il y a un meilleur endroit pour mettre cela, nous serons heureux de revoir vos suggestions. Je préférerais que nous passions cela à un problème ou à un PR séparé, car ce n'est pas directement lié à ce ticket. Merci!

Salut @nateprewitt , merci d'avoir signalé la section des en-têtes personnalisés! Evidemment, je n'avais pas pensé à vérifier cette partie de la documentation.

Je pense qu'il serait utile d'inclure également l'appel, ou une référence à l'appel, dans la section sur l' authentification . Bien que je sois actuellement assez occupé, j'envisagerai d'ouvrir un PR lorsque les choses se calmeront pour mettre à jour la documentation.

Si tel est le comportement prévu

@ndmeiri Oui, il s'agit d'un comportement destiné à ne pas divulguer vos informations d'authentification sensibles à des sources potentiellement non fiables. (Juste pour être clair)

Il semble que les "domaines de confiance" de # 4983 ne soient plus dans l'implémentation de sessions.py.

Dans le cas où je fais des demandes à une URL que je _know_ redirige vers une URL particulière différente mais sûre, et que je souhaite activer la redirection avec la persistance de l'en-tête d'autorisation, comment pourrais-je y parvenir s'il vous plaît?

comment pourrais-je y parvenir s'il vous plaît?

Vous pouvez patcher la méthode rebuild_auth . Cela fonctionne pour moi: https://github.com/DHI-GRAS/earthdata-download/blob/master/earthdata_download/download.py#L27 -L49

@ j08lue Merci! Avant que votre commentaire ne soit transmis, j'ai contourné le problème en définissant allow_redirects sur False et en ajoutant du code pour suivre explicitement les quelques redirections spécifiques attendues dans mon cas d'utilisation. C'est une situation à court terme pour moi, alors j'espère que c'est une solution temporaire adéquate, mais c'est formidable de savoir qu'il existe une meilleure façon de le faire à long terme si nécessaire.

Cette page vous a été utile?
0 / 5 - 0 notes