Requests: Der Autorisierungsheader der Sitzung wird bei der Umleitung nicht gesendet

Erstellt am 28. Dez. 2015  ·  35Kommentare  ·  Quelle: psf/requests

Ich verwende Anfragen, um auf developer-api.nest.com zu klicken und einen Autorisierungsheader mit einem Inhaber-Token festzulegen. Bei einigen Anforderungen antwortet diese API mit einer 307-Umleitung. In diesem Fall muss der Autorisierungsheader bei der nachfolgenden Anforderung noch gesendet werden. Ich habe versucht, requests.get() sowie eine Sitzung zu verwenden.

Ich denke, ich könnte das umgehen, indem ich keine Weiterleitungen zulasse, den 307 erkenne und dann die neue Anfrage selbst ausstelle, aber ich frage mich, ob dies ein Fehler ist. Sollte ich damit rechnen, dass der Autorisierungsheader für alle Anforderungen gesendet wird, die im Rahmen einer Sitzung gestellt werden?

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

Hilfreichster Kommentar

Es gibt zwei Nest-spezifische Problemumgehungen.

Eine besteht darin, den Parameter auth mit dem access_token zu übergeben, anstatt den Authorization-Header zu verwenden. Ich habe dies unter https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23 gefunden

Eine andere Möglichkeit besteht darin, ein Wörterbuch mit den von Ihnen verwendeten Headern zu speichern, keine Weiterleitungen zu befolgen und dann eine zweite Anforderung zu stellen, die erneut in den Headern übergeben wird:

    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)

Alle 35 Kommentare

Wohin führt die Weiterleitung?

Ah, eine andere Domäne. firebase-apiserver03-tah01-iad01.dapi.production.nest.com

Ja, das ist etwas absichtlich: Wir sind sehr aggressiv beim Entfernen von Autorisierungsheadern, wenn wir auf einen neuen Host umgeleitet werden. Dies ist eine Sicherheitsfunktion für CVE 2014-1829 , die dadurch verursacht wurde, dass wir Header bei Weiterleitungen außerhalb des Hosts beibehalten haben.

Aus einer bestimmten Perspektive haben wir hier jedoch immer noch einen Fehler, da Sie den Autorisierungsheader auf Session und nicht auf die Anforderung setzen. Im Prinzip bedeutet dies "Es ist mir egal, wohin die Umleitung geht, fügen Sie den Header hinzu". Ich denke immer noch, ich hätte lieber diesen Ansatz, der zumindest sicherstellt, dass wir für keine Form von Angriffen offen sind, auch wenn dies diese spezielle Instanz etwas schwieriger macht. Ich bin jedoch offen dafür, überzeugt zu sein, dass wir hier zu paranoid sind.

Ich bin jedoch offen dafür, überzeugt zu sein, dass wir hier zu paranoid sind.

Ich bin weniger offen für Überzeugung, aber bereit zuzuhören.

Das heißt, ein separater Auth-Mechanismus könnte geschrieben werden, um solche Header über _allowed_ Domänen hinweg beizubehalten, was es erforderlich machen würde, dass wir einige Arbeiten in rebuild_auth ausführen.

Ich werde nicht mit der Sicherheit streiten, wie es jetzt funktioniert. Es wäre jedoch schön, einen Mechanismus zu haben, um sich für das "unsichere" Verhalten zu entscheiden. Legen Sie möglicherweise eine Basisdomäne fest, in der diese Header beibehalten werden sollen (in diesem Fall nest.com), oder eine Liste der Domänen, an die sie gesendet werden können.

Ja, das ist weitaus komplexer, als der Kern der Anfragen jemals bieten wird. Aus diesem Grund frage ich mich, ob eine separate Auth-Klasse / ein separater Auth-Handler für diese Art von Dingen am besten geeignet ist. Ich bin jedoch nicht davon überzeugt, dass es funktionieren wird, da ich ziemlich sicher bin, dass wir prepare_auth nicht unbedingt anrufen.

Im Standardmodell funktioniert dies nicht, da wir prepare_auth nicht unbedingt aufrufen. Ein Transportadapter könnte jedoch verwendet werden, um diese Rolle zu erfüllen, selbst wenn es sich um eine etwas ungewöhnliche Verwendung dieser API handelt.

Ich denke, ein TA ist hier absolut falsch zu empfehlen.

  • Wenn für eine Sitzung eine Authentifizierung bereitgestellt wird, sollte diese für jede Anforderung dieser Sitzung gesendet werden.
  • Vielleicht sollten wir session.auth entfernen. Es ist nicht besonders nützlich.

Wenn für eine Sitzung eine Authentifizierung bereitgestellt wird, sollte diese für jede Anforderung dieser Sitzung gesendet werden.

Ich bin grundsätzlich anderer Meinung. Sitzungen werden nicht für eine einzelne Domain verwendet. Wenn dies der Fall wäre, hätte ich kein Problem damit.

Vielleicht sollten wir session.auth entfernen. Es ist nicht besonders nützlich.

Ich denke es ist nützlich. Ich denke, es wäre besser, wenn es nicht erlaubt wäre, ihm ein Tupel zuzuweisen. Ich würde lieber eine Auth-Klasse sehen, die angibt, für welche Domänen sie verwendet werden soll. Wir könnten einfach den AuthHandler aus dem Anforderungs-Toolbelt übernehmen, mit der Verwendung von Anforderungen Anmeldeinformationen für eine Domain angeben können. Dies bietet eine etwas sicherere Möglichkeit zur Behandlung der sitzungsbasierten Authentifizierung. Der Nachteil ist jedoch, dass Benutzer sich für diese Art der Authentifizierung anmelden müssen.

Ich muss dies auch beheben, damit Header für Weiterleitungen bestehen bleiben.

@jtherrmann Wenn dies ein Auth-Header ist, können Sie das Problem am einfachsten

Wurden diesbezüglich Fortschritte oder zusätzliche Überlegungen angestellt?
Ich bin auf das gleiche Problem gestoßen.

@ethanroy Keine zusätzliche Überlegung über meinen Vorschlag hinaus, einen

Verwandte Themen: Wenn eine Sitzung die Authentifizierung umleitet und entfernt, wird beim erneuten Aufrufen von get die Authentifizierung erneut angewendet und die zwischengespeicherte Umleitung verwendet. Also zweimal klopfen und einsteigen. Absichtliches Verhalten?

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

<Response [403]>

>>> s.get(url)

<Response [200]>

@ GregBakker Ja, ish. Es ist ein Zusammenfluss von beabsichtigten Verhaltensweisen. Dieser Fehler weist jedoch darauf hin, dass der ursprüngliche 403 nicht auftreten sollte.

@Lukasa Wenn Sie sagen "Der einfachste Weg, um das Problem zu Authentifizierungshandler auf Sitzungsebene festzulegen", funktioniert das heute? Basierend auf dem, was ich im Code sehe, lautet die Antwort nein, aber Ihre Formulierung lässt mich fragen, ob mir etwas fehlt. Sie sprechen über das Festlegen des Sitzungsauthentifizierungsattributs, richtig?

Ja, das sollte funktionieren.

@jwineinger Also , wie bist du zu diesem Problem gekommen? es scheint sich immer noch genauso zu verhalten.

Es gibt zwei Nest-spezifische Problemumgehungen.

Eine besteht darin, den Parameter auth mit dem access_token zu übergeben, anstatt den Authorization-Header zu verwenden. Ich habe dies unter https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23 gefunden

Eine andere Möglichkeit besteht darin, ein Wörterbuch mit den von Ihnen verwendeten Headern zu speichern, keine Weiterleitungen zu befolgen und dann eine zweite Anforderung zu stellen, die erneut in den Headern übergeben wird:

    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)

Ich bin auf dasselbe Problem gestoßen und habe es umgangen, indem ich die rebuild_auth -Methode in einer benutzerdefinierten requests.Session -Implementierung überschrieben habe:

from requests import Session

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

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

@ sigmavirus24 Was ist mit der Lösung von @ gabriel-loo falsch? Sicherheitsbedenken?

@ j08lue ja. Bitte lesen Sie den Thread. Es gibt CVEs, die damit verbunden sind, die Authentifizierung nicht zu entfernen, bevor willkürliche Weiterleitungen zu einer neuen Domäne durchgeführt werden. Stellen Sie sich das Problem folgendermaßen vor:

Ich mache Anfragen an api.github.com und ein Angreifer schafft es, dass ich einer Weiterleitung zu another-domain.com folge, die er kontrolliert, und ich gebe mein Token mit Schreibzugriff auf meine Repositorys (einschließlich Anfragen) weiter kann so aussehen, als würde ich Commits für Anfragen vornehmen, obwohl diese Commits tatsächlich über die API vorgenommen werden. Sie können Code in Anfragen aufnehmen, der die Sicherheitslage schwächt und Ihnen möglicherweise aktiv schadet. Dies kann passieren, wenn Sie Ihre Authentifizierungsdaten bei jeder Umleitung unbedingt senden.

Nehmen wir an, die Weiterleitung ist nicht böswillig. Ist es Ihnen tatsächlich angenehm, Ihre Anmeldeinformationen für einen Dienst an ein anderes Unternehmen oder einen anderen Dienst weiterzugeben? Der ursprüngliche Dienst kann vertrauliche Daten für Sie, Ihre Kunden oder etwas anderes speichern. Selbst wenn die neue Domain, zu der Sie umgeleitet wurden, Ihre Anmeldeinformationen nicht verwendet, sondern sie möglicherweise als unerwartete Daten protokolliert, kann jemand, der sie angreift und diese Protokolle abrufen kann, Ihre Anmeldeinformationen für die ursprüngliche Domain verwenden, wenn er herausfinden kann, wo Sie gehören. Sind Sie wirklich bereit, dieses Risiko einzugehen?

Vielen Dank für die Illustration, @ sigmavirus24. Wenn dieses Problem letztendlich das Weiterleiten vertraulicher Header an Weiterleitungen verbietet, warum ist dieser Thread dann noch offen? Ich könnte mir keinen passenderen Fehler vorstellen als den, den Sie erhalten (403), also gibt es hier keinen Fehler, der zum Handeln erforderlich ist, oder? Oder was hast du gedacht, @Lukasa?

Ich habe dieses Problem kürzlich bei der Arbeit mit einer nicht öffentlichen API festgestellt. Die Sicherheitsbedenken sind als Grund für das Entfernen der Authentifizierung bei Weiterleitungen absolut sinnvoll. Ich denke, eine Lösung wie @ gabriel-loo ist etwas, über das die Leute nachdenken können, wenn sie glauben, dass sie sich in einer Umgebung befinden, die sicher genug ist, um dies zu tun. Oder der Handler auf Sitzungsebene. Oder finden Sie einen anderen Weg, um dies zu umgehen, indem Sie die Umleitung wie oben vorgeschlagen vollständig überspringen, wenn dies möglich ist. Entsprechend der Ansicht ist dies also kein wirklicher Fehler.

Ich habe jedoch mehr Zeit gebrannt, als ich wahrscheinlich brauchte, um zu verwirren, warum eine Handvoll anderer Nicht-Python-HTTP-Clients den Auth-Header weitergegeben haben und einwandfrei funktionierten, wenn dies nicht der Fall war. Ein Vorschlag: Es könnte hilfreich sein, hier eine Warnung über warnings auszugeben, um Anrufern klarer zu machen, wenn der Header vorhanden ist und entfernt wird. Ich würde mir vorstellen, dass es selten vorkommt, dass ein Anrufer nicht gewarnt werden möchte.

@tlantz normalerweise scheint das ziemlich vernünftig. Anfragen als Projekt (sowie urllib3, eine seiner Abhängigkeiten) haben eine erhebliche Menge an Ärger verursacht, wenn eine Warnung ausgegeben wird, sei es über das Warnmodul oder über die Protokollierung. Darüber hinaus ist das Warnmodul für Dinge gedacht, bei denen Benutzer Maßnahmen ergreifen sollten, z. B. wenn sie keine Python-Version verwenden, die gegen eine aktuelle Version von OpenSSL kompiliert wurde.

In den meisten Fällen ist dieses Verhalten nicht so problematisch, wie beispielsweise die Nichtüberprüfung eines Zertifikats für eine TLS-Verbindung. Das hilft Ihnen oder anderen, die ihre echte und berechtigte Frustration zu diesem Thema zum Ausdruck gebracht haben, offensichtlich nicht. Vor diesem Hintergrund frage ich mich, ob es nicht besser wäre, dies auf der Ebene DEBUG zu protokollieren. Wenn jemand die Protokollierung verwendet (im Allgemeinen eine anständige Praxis) und diese Stufe aktiviert, wird sie für ihn angezeigt. Wenn man bedenkt, wie wenig Requests sich selbst protokolliert, wird dies als Debug-Protokoll ziemlich prominent sein. Scheint das ein fairer Kompromiss zu sein?

Ja, das scheint ein völlig fairer Kompromiss zu sein. Das Argumentieren um warnings macht für mich Sinn. Ich denke, wenn Sie ungefähr 30 Minuten lang verwirrt sind, fügen Sie normalerweise logging um Ihre eigenen Sachen bei DEBUG , also denke ich eine DEBUG Nachricht würde 95% der Fälle treffen, in denen Menschen stecken bleiben und versuchen herauszufinden, was nicht funktioniert.

Ich verwende eine Sitzung, um den Autorisierungsheader zu speichern, aber er wird nicht in einer Umleitung gesendet
Anfragen (2.18.4)

Ein Mitarbeiter und ich haben mindestens ein paar Stunden damit verbracht, ein Problem zu beheben, das in direktem Zusammenhang mit diesem Verhalten steht. Mein Anwendungsfall ist das Umleiten eines API-Aufrufs von api.my-example-site.org nach www.api.my-example-site.org . Die Header wurden bei der Umleitung entfernt.

Wenn dies beabsichtigt ist (oder wenn es in naher Zukunft nicht geändert wird), können wir es bitte zumindest der Dokumentation hinzufügen? Ich habe die Dokumente gelesen und erneut gelesen, um herauszufinden, was ich falsch gemacht habe, und ich habe sogar den gesamten Code in der Klasse Request durchgelesen. Wenn ich in der Dokumentation eine Warnung zu diesem Verhalten gesehen hätte, hätte ich mein Problem in ein paar Minuten behoben (dies ist die Zeit, die benötigt wurde, nachdem ich diesen Thread gefunden habe). Vielleicht haben wir jedoch im falschen Teil der Dokumentation gelesen.

Hallo @ndmeiri , wir haben einen Kurzanleitung für Anfragen unter der Überschrift Benutzerdefinierte Kopfzeilen . Wenn Sie der Meinung sind, dass es einen besseren Ort gibt, um dies zu formulieren, prüfen wir gerne Ihre Vorschläge. Ich würde es vorziehen, wenn wir das auf eine separate Ausgabe oder PR verschieben, da es nicht direkt mit diesem Ticket zusammenhängt. Vielen Dank!

Hallo @nateprewitt , danke, dass hast ! Offensichtlich hatte ich nicht daran gedacht, diesen Teil der Dokumentation zu überprüfen.

Ich denke, es wäre hilfreich, den Aufruf oder einen Verweis auf den Aufruf auch in den Abschnitt über die Authentifizierung aufzunehmen . Obwohl ich derzeit ziemlich beschäftigt bin, werde ich in Betracht ziehen, eine PR zu öffnen, wenn sich die Dinge beruhigen, um die Dokumente zu aktualisieren.

Wenn dies beabsichtigtes Verhalten ist

@ndmeiri Ja, es ist beabsichtigt, Ihre vertraulichen Authentifizierungsdaten nicht an potenziell nicht vertrauenswürdige Quellen

Es scheint, dass die "vertrauenswürdigen Domänen" von # 4983 nicht mehr in der Implementierung von session.py enthalten sind.

Wie würde ich dies in der Situation erreichen, in der ich Anforderungen an eine URL stelle, von der ich weiß, dass sie zu einer bestimmten anderen, aber sicheren URL umleitet, und die Umleitung unter Beibehaltung des Autorisierungsheaders aktivieren möchte?

Wie würde ich das bitte erreichen?

Sie können die rebuild_auth -Methode patchen. Dies funktioniert bei mir: https://github.com/DHI-GRAS/earthdata-download/blob/master/earthdata_download/download.py#L27 -L49

@ j08lue Danke! Bevor Ihr Kommentar einging, habe ich das Problem umgangen, indem ich allow_redirects auf False und Code hinzugefügt habe, um den wenigen spezifischen Weiterleitungen, die in meinem Anwendungsfall erwartet werden, explizit zu folgen. Dies ist eine kurzfristige Situation für mich, daher hoffe ich, dass dies eine angemessene vorübergehende Lösung ist, aber es ist gut zu wissen, dass es bei Bedarf längerfristig einen besseren Weg gibt, dies zu tun.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen