Requests: El encabezado de autorización de la sesión no se envía al redireccionar

Creado en 28 dic. 2015  ·  35Comentarios  ·  Fuente: psf/requests

Estoy usando solicitudes para acceder a developer-api.nest.com y establecer un encabezado de autorización con un token de portador. En algunas solicitudes, esa API responde con una redirección 307. Cuando eso sucede, todavía necesito que se envíe el encabezado de autorización en la solicitud posterior. Intenté usar requests.get() además de una sesión.

Supongo que podría solucionar esto no permitiendo redirecciones, detectando el 307 y luego emitiendo la nueva solicitud yo mismo, pero me pregunto si esto es un error. ¿Debo esperar que el encabezado de Autorización se envíe a todas las solicitudes realizadas dentro del contexto de una sesión?

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

Comentario más útil

Hay dos soluciones alternativas específicas de Nest.

Una es pasar el parámetro auth con el access_token en lugar de usar el encabezado Authorization. Encontré esto en https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Otra es guardar un diccionario con los encabezados que usaría, no seguir las redirecciones y luego hacer una segunda solicitud pasando los encabezados nuevamente:

    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)

Todos 35 comentarios

¿A dónde está la redirección?

Ah, un dominio diferente. firebase-apiserver03-tah01-iad01.dapi.production.nest.com

Sí, eso es algo deliberado: somos muy agresivos al eliminar los encabezados de autorización cuando se redirige a un nuevo host. Esta es una característica de seguridad para lidiar con CVE 2014-1829 , que fue causada por la persistencia de encabezados en redireccionamientos fuera del host.

Sin embargo, desde cierta perspectiva, todavía tenemos un error aquí, porque estableces el encabezado Authorization en Session , no en la solicitud. En principio, lo que esto significa es "No me importa a dónde vaya la redirección, agregue el encabezado". Todavía creo que preferiría tener este enfoque, que al menos asegura que no estemos abiertos a ninguna forma de ataque, incluso si hace que esta instancia específica sea algo más complicada. Sin embargo, estoy abierto a estar convencido de que estamos siendo demasiado paranoicos aquí.

Sin embargo, estoy abierto a estar convencido de que estamos siendo demasiado paranoicos aquí.

Estoy menos abierto a estar convencido pero dispuesto a escuchar.

Dicho esto, se podría escribir un mecanismo de autenticación por separado para conservar dichos encabezados en los dominios _allowed_, lo que nos obligaría a trabajar en rebuild_auth .

No discutiré con la seguridad de cómo funciona ahora. Sin embargo, sería bueno tener algún mecanismo para optar por el comportamiento "inseguro". Quizás estableciendo un dominio base para conservar esos encabezados (nest.com, en este caso) o quizás una lista de dominios a los que está bien enviarlos.

Sí, eso es mucho más complejo de lo que el núcleo de solicitudes proporcionará jamás. Es por eso que me pregunto si una clase / controlador de autenticación independiente podría funcionar mejor para este tipo de cosas. Sin embargo, no estoy convencido de que funcione porque estoy bastante seguro de que no llamamos incondicionalmente a prepare_auth .

No funcionará en el modelo estándar porque no llamamos incondicionalmente a prepare_auth . Sin embargo, se podría usar un adaptador de transporte para cumplir con esta función, incluso si es un uso ligeramente inusual de esa API.

Sin embargo, creo que un TA es absolutamente incorrecto recomendar aquí.

  • Si se proporciona autenticación a una sesión, debe enviarse para cada solicitud que realice la sesión.
  • Quizás deberíamos eliminar session.auth . No es particularmente útil.

Si se proporciona autenticación a una sesión, debe enviarse para cada solicitud que realice la sesión.

Fundamentalmente no estoy de acuerdo. Las sesiones no se usan para un solo dominio, si lo fueran, no tendría ningún problema con esto.

Quizás deberíamos eliminar session.auth. No es particularmente útil.

Creo que es útil. Sin embargo, creo que sería mejor si no se permitiera asignarle una tupla. Prefiero ver una clase de autenticación que especifique para qué dominios usarla. Podríamos simplemente adoptar el AuthHandler de request-toolbelt, que permite a las personas especificar las credenciales de un dominio cuando utilizan solicitudes. Esto proporciona una forma un poco más segura de manejar la autenticación basada en sesión. Sin embargo, la desventaja es que requiere que los usuarios opten por ese tipo de autenticación.

También necesito que esto se solucione para poder hacer que los encabezados persistan para las redirecciones.

@jtherrmann Si se trata de un encabezado de autenticación, la forma más sencilla de solucionar el problema es establecer un controlador de autenticación a nivel de sesión que simplemente siempre coloque el encabezado que desea en la solicitud.

¿Se ha dado algún progreso o consideración adicional a esto?
Me encuentro con el mismo problema.

@ethanroy No hay consideración adicional más allá de mi sugerencia de usar un controlador de autenticación a nivel de sesión, en el comentario directamente encima del suyo.

Relacionado: si una sesión redirige y elimina la autenticación, llamar a get nuevamente vuelve a aplicar la autenticación y usa la redirección en caché. Así que toca dos veces y entra. ¿Comportamiento previsto?

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

<Response [403]>

>>> s.get(url)

<Response [200]>

@GregBakker Sí, ish. Es una confluencia de comportamientos previstos. Sin embargo, este error señala que el 403 original no debería ocurrir.

@Lukasa cuando dice "la forma más fácil de solucionar el problema es establecer un controlador de autenticación a nivel de sesión", ¿es algo que funciona hoy? Según lo que veo en el código, la respuesta es no, pero su redacción me hace preguntarme si me estoy perdiendo algo. Estás hablando de configurar el atributo de autenticación de sesión, ¿verdad?

Sí, eso debería funcionar.

@jwineinger entonces, ¿cómo terminaste solucionando este problema? todavía parece comportarse igual.

Hay dos soluciones alternativas específicas de Nest.

Una es pasar el parámetro auth con el access_token en lugar de usar el encabezado Authorization. Encontré esto en https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Otra es guardar un diccionario con los encabezados que usaría, no seguir las redirecciones y luego hacer una segunda solicitud pasando los encabezados nuevamente:

    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)

Encontré este mismo problema y lo solucioné anulando el método rebuild_auth en una implementación 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é pasa con la solución de @ gabriel-loo? ¿Preocupaciones de seguridad?

@ j08lue sí. Por favor lea el hilo. Hay CVE asociados con no eliminar la autenticación antes de seguir redireccionamientos arbitrarios a un nuevo dominio. Piense en el problema de esta manera:

Estoy haciendo solicitudes a api.github.com y un atacante logra hacerme seguir una redirección a another-domain.com que ellos controlan y paso mi token con acceso de escritura a mis repositorios (incluidas las solicitudes) y luego puede aparecer como si estuviera confirmando solicitudes cuando, de hecho, están haciendo esas confirmaciones a través de la API. Pueden incluir código en las solicitudes que debilitará su postura de seguridad y posiblemente le dañará activamente. Eso es lo que podría suceder cuando envía incondicionalmente sus credenciales de autenticación en cada redireccionamiento.

Aun así, digamos que la redirección no es maliciosa, ¿se siente realmente cómodo al filtrar sus credenciales para un servicio a otra empresa o servicio? El servicio original puede almacenar datos confidenciales para usted, sus clientes o cualquier otra cosa. Incluso si el nuevo dominio al que ha sido redirigido no usa sus credenciales, pero potencialmente las registra como datos inesperados, alguien que los ataque y pueda recuperar esos registros puede usar sus credenciales contra el dominio original si puede averiguar dónde ellos pertenecen. ¿Estás realmente dispuesto a correr ese riesgo?

Gracias por la ilustración, @ sigmavirus24. Si, en última instancia, esta preocupación prohíbe el reenvío de encabezados confidenciales a los redireccionamientos, ¿por qué sigue abierto este hilo? No pude pensar en un error más apropiado que el que obtiene (403), por lo que no es necesario actuar aquí, ¿verdad? ¿O qué tenías en mente , @Lukasa?

Recientemente tuve este problema cuando trabajaba con una API no pública. Las preocupaciones de seguridad tienen mucho sentido como razón para eliminar la autenticación en las redirecciones. Creo que una solución como la de @ gabriel-loo es algo que la gente puede considerar si creen que se encuentran en un entorno lo suficientemente seguro para hacerlo. O el controlador de nivel de sesión. O busque otra forma de solucionarlo omitiendo la redirección por completo como se sugirió anteriormente, si es posible. Entonces, de acuerdo con la vista, esto no es realmente un error.

Sin embargo, pasé más tiempo del que probablemente necesitaba confundir acerca de por qué un puñado de otros clientes HTTP que no son de Python _did_ pasaron el encabezado de autenticación y estaban funcionando bien cuando no era así. Una sugerencia: sería bueno emitir una advertencia a través de warnings aquí para que sea más claro para las personas que llaman cuando el encabezado está presente y se está quitando. Me imagino que es raro que esto sea algo sobre lo que una persona que llama _no_ querría ser advertida.

@tlantz normalmente eso parecería bastante razonable. Las solicitudes como proyecto (así como urllib3, una de sus dependencias) ha causado una gran cantidad de ira cuando emite cualquier tipo de advertencia, ya sea a través del módulo de advertencias o mediante el registro. Además, el módulo de advertencias es para cosas en las que la gente debería actuar, por ejemplo, no usar una versión de Python que haya sido compilada con una versión reciente de OpenSSL.

En la mayoría de los casos, este comportamiento no es tan problemático como, por ejemplo, no poder verificar un certificado para una conexión TLS. Eso obviamente no le ayuda a usted ni a nadie que haya expresado su frustración genuina y válida sobre este tema. Con eso en mente, me pregunto si no sería mejor intentar registrar esto en el nivel DEBUG . Si alguien está usando el registro (generalmente una práctica decente) y habilita ese nivel, se mostrará para ellos. Además, dado lo poco que se registran las solicitudes, esto será bastante prominente como un registro de depuración. ¿Le parece una compensación justa?

Sí, eso parece una compensación totalmente justa. Razonar alrededor de warnings tiene sentido para mí. Creo que para cuando has estado desconcertado durante 30 minutos aproximadamente, generalmente estás agregando logging alrededor de tus propias cosas de todos modos en DEBUG , así que creo que un mensaje de DEBUG afectaría al 95% de los casos en los que las personas están atrapadas tratando de averiguar qué no funciona.

Utilizo una sesión para mantener el encabezado de autorización, pero no se envía en un redireccionamiento
solicitudes (2.18.4)

Un compañero de trabajo y yo pasamos al menos un par de horas depurando un problema directamente relacionado con este comportamiento. Mi caso de uso es redirigir una llamada a la API de api.my-example-site.org a www.api.my-example-site.org . Los encabezados se eliminaron en la redirección.

Si este es el comportamiento previsto (o si no se cambiará en un futuro próximo), ¿podemos al menos agregarlo a la documentación? Leí y releí los documentos tratando de averiguar qué estaba haciendo incorrectamente, e incluso leí todo el código en la clase Request . Si hubiera visto una advertencia sobre este comportamiento en la documentación, habría solucionado mi problema en un par de minutos (que es el tiempo que tardó en encontrar este hilo). Sin embargo, quizás estábamos leyendo en la parte incorrecta de la documentación.

Hola @ndmeiri , tenemos una llamada sobre esto en la guía de inicio rápido para solicitudes bajo el encabezado Encabezados personalizados . Si cree que hay un lugar mejor para poner esto, estaremos encantados de revisar cualquier sugerencia que tenga. Sin embargo, preferiría que lo moviéramos a un tema separado o relaciones públicas, ya que no está directamente relacionado con este ticket. ¡Gracias!

Hola @nateprewitt , ¡gracias por señalar la sección de encabezados personalizados! Evidentemente, no había pensado en comprobar esa parte de la documentación.

Creo que sería útil incluir también la llamada, o una referencia a la llamada, en la sección de autenticación . Aunque actualmente estoy bastante ocupado, consideraré abrir un PR cuando las cosas se calmen para actualizar los documentos.

Si este es el comportamiento previsto

@ndmeiri Sí, su comportamiento es el de no filtrar sus credenciales de autenticación confidenciales a fuentes potencialmente no confiables. (Para ser claro)

Parece que los "dominios de confianza" de # 4983 ya no están en la implementación de Session.py.

En la situación en la que estoy haciendo solicitudes a una URL que _ sé_ redirige a una URL particular diferente pero segura, y quiero habilitar la redirección con la persistencia del encabezado de Autorización, ¿cómo puedo lograrlo, por favor?

¿Cómo podría lograr eso por favor?

Puede parchear el método rebuild_auth . Esto funciona para mi: https://github.com/DHI-GRAS/earthdata-download/blob/master/earthdata_download/download.py#L27 -L49

@ j08lue ¡Gracias! Antes de que llegara su comentario, solucioné el problema estableciendo allow_redirects en False , y agregando código para seguir explícitamente las pocas redirecciones específicas que se esperan en mi caso de uso. Esta es una situación a corto plazo para mí, así que espero que sea una solución temporal adecuada, pero es bueno saber que hay una mejor manera de hacerlo a largo plazo si es necesario.

¿Fue útil esta página
0 / 5 - 0 calificaciones