Pysa: Cómo evitar problemas de seguridad en el código Python



El 7 de agosto, Facebook presentó Pysa, un analizador estático de código abierto centrado en la seguridad que le ayuda a trabajar con millones de cadenas de Instagram. Se revelan las limitaciones, se abordan las soluciones de diseño y, por supuesto, los medios para ayudar a evitar falsos positivos. Se muestra la situación cuando Pysa es más útil y el código en el que el analizador no es aplicable. Detalles del blog de ingeniería de Facebook debajo del corte.



El año pasado, escribimos sobre cómo creamos Zoncolan , una herramienta de análisis estático que analiza más de 100 millones de líneas de código Hack y ayuda a los ingenieros a prevenir miles de posibles problemas de seguridad. El éxito inspiró el trabajo en Pysa - Python Static Analyzer. El analizador está construido sobre Pyre, la herramienta de verificación de tipos de Python de Facebook. Pysa trabaja con flujo de datos en código. El análisis del flujo de datos es útil porque a menudo los problemas de seguridad y privacidad se modelan como datos que van donde no deberían estar.



Pysa ayuda a identificar muchos tipos de problemas. El analizador comprueba si el código utiliza correctamente determinadas estructuras internas para evitar el acceso o la divulgación de los datos del usuario según las políticas técnicas de privacidad. Además, el analizador detecta problemas comunes de seguridad de aplicaciones web, como XSS e inyección SQL. Al igual que Zoncolan, la nueva herramienta ha ayudado a ampliar los esfuerzos de seguridad de las aplicaciones de Python. Esto es especialmente cierto para Instagram.



Pysa en Instagram



El repositorio de Python más grande de Facebook tiene millones de líneas en los servidores de Instagram. Cuando Pysa se ejecuta en un cambio de código sugerido por el desarrollador, proporciona resultados en aproximadamente una hora, en lugar de las semanas o meses que podría llevar verificar manualmente. Esto le ayuda a encontrar y prevenir un problema lo suficientemente rápido como para que no entre en su código base. Los resultados de las comprobaciones se envían directamente al desarrollador o ingenieros de seguridad, según el tipo de problema y la relación señal-ruido en la situación particular.



Pysa y código abierto



El código fuente de Pysa y muchas definiciones de problemas están abiertos para que otros desarrolladores analicen el código de sus proyectos. Trabajamos con marcos del lado del servidor de código abierto como Django y Tornado , por lo que desde el primer lanzamiento dentro de Facebook, Pysa encuentra problemas de seguridad en los proyectos que utilizan estos marcos. Usar Pysa para marcos que aún no tienen cobertura suele ser tan fácil como agregar algunas líneas de configuración. Solo necesita decirle al analizador de dónde provienen los datos al servidor.



Pysa se ha utilizado para detectar problemas como CVE-2019-19775 en proyectos Python de código abierto. También trabajamos con el proyecto Zulip e incluyó a Pysa en su código base.



¿Cómo funciona?



Pysa está diseñado con lecciones aprendidas de Zoncolan. Utiliza los mismos algoritmos para realizar análisis estáticos e incluso comparte código con Zoncolan. Como Zoncolan, Pysa monitorea el flujo de datos en un programa. El usuario define las fuentes de datos importantes y los sumideros de donde provienen los datos. En las aplicaciones de seguridad, los tipos de fuentes más comunes son los puntos donde los datos controlados por el usuario ingresan a la aplicación, como el diccionario HttpRequest.GET en Django. Los receptores son generalmente mucho más variados y pueden incluir la ejecución de API. Por ejemplo, evaloos.open... Pysa ejecuta iterativamente rondas de análisis para construir resúmenes para determinar qué funciones están devolviendo datos desde la fuente y cuáles tienen parámetros que llegan al destino. Cuando el analizador detecta que la fuente finalmente se está conectando al receptor, informa del problema. La visualización de este proceso es un árbol con un problema en la parte superior y fuentes y flujos en las hojas:







Para realizar un análisis de procedimientos cruzados, para seguir el flujo de datos entre las llamadas a funciones, debe poder asignar las llamadas a las funciones a sus implementaciones. Para hacer esto, necesita usar toda la información disponible en el código, incluidos los tipos estáticos opcionales, si están presentes. Trabajamos con Pyre para averiguar esta información. Si bien Pysa depende en gran medida de Pyre y ambas herramientas comparten el mismo repositorio, es importante tener en cuenta que estos son productos separados con aplicaciones separadas. 



Falsos positivos



Los ingenieros de seguridad son los principales usuarios de Pysa en Facebook. Como cualquier ingeniero que trabaja con herramientas automáticas de detección de errores, tuvimos que averiguar cómo lidiar con los falsos positivos (sin problema, sin señal) y negativos (sin problema, sin señal).



El diseño de Pysa tiene como objetivo evitar pasar por alto los problemas y detectar tantos problemas reales como sea posible. Sin embargo, reducir la cantidad de falsas alarmas puede requerir compensaciones que aumentan la cantidad de alarmas innecesarias. Demasiados falsos positivos provocan fatiga por ansiedad y el riesgo de que los problemas reales se pasen por alto en el ruido. Pysa tiene dos herramientas para eliminar señales no deseadas: desinfectantes y señales.



DesinfectanteEs una herramienta sencilla. Le dice al analizador que no siga el flujo de datos después de que el flujo haya pasado por la función o atributo. Los desinfectantes le permiten codificar el conocimiento de la transformación del dominio que siempre presenta los datos de manera segura y confidencial.



Los signos son más sutiles: son pequeños fragmentos de metadatos que Pysa adjunta a los flujos de datos a medida que realiza el seguimiento. A diferencia de los desinfectantes, las señales no eliminan los problemas de los resultados del análisis. Los atributos y otros metadatos se pueden utilizar para filtrar los resultados después del análisis. Los filtros generalmente se escriben para un par de origen-destino específico para ignorar los problemas cuando los datos ya se han procesado para un tipo específico (pero no todos los tipos) de un destino.



Para comprender en qué situaciones es más útil Pysa, imagine que se ejecuta el siguiente código para cargar un perfil de usuario:



# views/user.py
async def get_profile(request: HttpRequest) -> HttpResponse:
   profile = load_profile(request.GET['user_id'])
   ...
 
# controller/user.py
async def load_profile(user_id: str):
   user = load_user(user_id) # Loads a user safely; no SQL injection
   pictures = load_pictures(user.id)
   ...
 
# model/media.py
async def load_pictures(user_id: str):
   query = f"""
      SELECT *
      FROM pictures
      WHERE user_id = {user_id}
   """
   result = run_query(query)
   ...
 
# model/shared.py
async def run_query(query: str):
   connection = create_sql_connection()
   result = await connection.execute(query)
   ...


Aquí es donde no se puede explotar la posible inyección de SQL en load_pictures: esta función siempre es válida user_iddesde la función load_useren load_profile. Cuando se configura correctamente, Pysa probablemente no informará un problema. Ahora imagine que un ingeniero emprendedor que escribe un código a nivel de controlador se da cuenta de que la obtención de datos de usuario y una imagen al mismo tiempo devuelve resultados más rápido:



# controller/user.py
async def load_profile(user_id: str):
   user, pictures = await asyncio.gather(
       load_user(user_id),
       load_pictures(user_id) # no longer 'user.id'!
   )
   ...


El cambio puede parecer inofensivo, pero en realidad termina fusionando la cadena controlada por el usuario user_idcon el problema de inyección de SQL en load_pictures. En una aplicación con muchas capas entre el punto de entrada y las consultas de la base de datos, es posible que el ingeniero no se dé cuenta de que el usuario controla completamente los datos o de que el problema de inyección está oculto en la función llamada. Ésta es exactamente la situación para la que se escribió el analizador. Cuando un ingeniero propone un cambio similar en Instagram, Pysa descubre que los datos pasan de la entrada impulsada por el usuario a una consulta SQL e informa el problema.



Limitaciones del analizador



Es imposible escribir un analizador estático perfecto . Pysa tiene limitaciones en el alcance, el flujo de datos y las decisiones de diseño, lo que compromete el rendimiento en cuanto a precisión y exactitud. Python como lenguaje dinámico tiene características únicas que subyacen a algunas de estas decisiones de diseño.



Espacio problema



Pysa está diseñado para detectar solo problemas de seguridad relacionados con los flujos de datos. No todas las preocupaciones de seguridad o privacidad se modelan como flujos de datos. Mira un ejemplo:



def admin_operation(request: HttpRequest):
  if not user_is_admin():
      return Http404
 
  delete_user(request.GET["user_to_delete"])


Pysa no es la herramienta adecuada para garantizar que se user_is_adminejecute una verificación de autorización antes de una operación privilegiada delete_user. El analizador puede detectar datos request.GETdirigidos a delete_user, pero esos datos nunca pasan por la validación user_is_admin. El código se puede reescribir para hacer que el problema sea modelado por Pysa, o puede incorporar la verificación de permisos en una operación administrativa delete_user. Pero este código en primer lugar muestra qué problemas no resuelve Pysa.



Límites de recursos



Tomamos una decisión de diseño sobre las restricciones para que Pysa pueda completar el análisis antes de que los cambios propuestos lleguen al código base. Cuando el analizador monitorea flujos de datos en demasiados atributos de un objeto, a veces tiene que simplificar y tratar el objeto completo como si contenga exactamente esos datos. Esto puede dar lugar a falsos positivos.



Otra limitación es el tiempo de desarrollo. Forzó una compensación sobre las características de Python que son compatibles. Pysa aún no incluye decoradores en el gráfico de llamadas al llamar a funciones y, por lo tanto, omite problemas dentro de los decoradores. 



Python como lenguaje dinámico



La flexibilidad de Python dificulta el análisis estático. Es difícil realizar un seguimiento de los flujos de datos a través de llamadas a métodos sin información de tipo. En el siguiente código, es imposible determinar cuál de las implementaciones flyse llama:



class Bird:
  def fly(self): ...
 
class Airplane:
  def fly(self): ...
 
def take_off(x):
  x.fly()  # Which function does this call?


El analizador funciona en proyectos completamente sin tipificar. Pero se necesita poco esfuerzo para cubrir los tipos importantes.



La naturaleza dinámica de Python impone otra limitación. Vea abajo:



def secret_eval(request: HttpRequest):
  os = importlib.import_module("os")
 
  # Pysa won't know what 'os' is, and thus won't
  # catch this remote code execution issue
  os.system(request.GET["command"])


La vulnerabilidad de ejecución es claramente visible aquí, pero el analizador la omitirá. El módulo se osimporta dinámicamente. Pysa no comprende que la variable local os representa exactamente el módulo os. Python le permite importar dinámicamente casi cualquier código en cualquier momento. Además, el lenguaje puede cambiar el comportamiento de una llamada a función para casi cualquier objeto. Pysa puede aprender a analizar el sistema operativo y detectar el problema. Pero el dinamismo de Python significa que hay un sinfín de ejemplos de flujos de datos patológicos que el analizador no verá.



resultados



En la primera mitad de 2020, Pysa representó el 44 por ciento de todos los problemas detectados en Instagram. Entre todos los tipos de vulnerabilidad, se encontraron 330 problemas únicos en los cambios de código propuestos. 49 (15%) problemas resultaron ser significativos, 131 de los problemas (40%) eran reales, pero tenían circunstancias atenuantes. Se registraron falsos negativos en 150 (45%) casos.



Revisamos periódicamente los problemas notificados de otras formas. Por ejemplo, a través del programa Bug Bounty. Así es como nos aseguramos de corregir todas las señales negativas falsas. La detección de cada tipo de vulnerabilidad es configurable. A través del perfeccionamiento constante, los ingenieros de seguridad se han movido a tipos más sofisticados para informar problemas reales el 100 por ciento del tiempo.



En general, estamos contentos con las compensaciones que hicimos para ayudar a los ingenieros de seguridad a escalar. Pero siempre hay espacio para el desarrollo. Creamos Pysa para mejorar continuamente la calidad del código mediante una estrecha colaboración entre ingenieros de seguridad y programadores. Esto nos permitió iterar y crear rápidamente una herramienta que satisface nuestras necesidades mejor que cualquier solución lista para usar. La colaboración de los ingenieros condujo a adiciones y mejoras a los movimientos de Pysa. Por ejemplo, la forma en que ve el seguimiento del problema ha cambiado. Ahora es más fácil ver falsos negativos.



Documentación y tutorial del analizador Pysa .

imagen


Descubra los detalles de cómo obtener una profesión de alto perfil desde cero o subir de nivel en habilidades y salario tomando cursos en línea de SkillFactory:





E







All Articles