Antes de empezar...

Scroll view
Si estás en un dispositivo móvil.
Fragment view
Tal como se ve en la presentación.
Speaker mode
Para ver las notas del presentador.
Print mode
Exportar a PDF.
Search
Busca en la presentación (Ctrl + Shift + F).
Shortcuts

Navega usando

Seguridad en aplicaciones Python

De un developer a otro

Santos Gallegos - [email protected]
@stsewd

Nunca confíes en input de un usuario

- Fin

$ WHOAMI

Enfoque

  • Vulnerabilidades más comunes
  • Python standard library
  • Web (Django)

Los sospechosos de siempre

  • Autenticación / Autorización
  • Inyección SQL / XSS
  • Componentes desactualizados vulnerables

https://owasp.org/Top10/

Fallas al validar y sanitizar input

Fallas de autenticación y autorización

Autenticación y autorización

Autenticación: ¿Quién eres?

Autorización: ¿Qué puedes hacer?

Django tiene un sistema de autenticación y autorización muy completo. ÚSALO

https://docs.djangoproject.com/en/5.1/topics/auth/default/

Autenticación


          from django.contrib.auth.decorators import login_required
          from django.contrib.auth.mixins import LoginRequiredMixin

          @login_required
          def my_view(request):
              pass

          class MyView(LoginRequiredMixin, View):
              pass
        

Autorización


          from django.contrib.auth.decorators import login_required
          from django.contrib.auth.decorators import permission_required

          @login_required
          def list_books(request):
              books = Book.objects.filter(user=request.user)
              ...

          @login_required
          @permission_required("books.change_book")
          def update_book(request):
              book_id = request.POST["book_id"]
              book = Book.objects.get(user=request.user, pk=book_id)
              ...
        

Simple y efectivo

Para un developer y un atacante

Controles de acceso son muy fáciles de implementar y usar. Pero si son omitidos o implementados incorrectamente, pueden ser un gran problema.

¿Cómo evitar estos problemas?

TESTS

TESTS

  • Usuario no autenticado
  • Usuario autenticado, con permisos
  • Usuario autenticado, sin permisos

Escribe tests por cada operación

urllib.parse

https://docs.python.org/3/library/urllib.parse.html

urllib.parse


          from urllib.parse import urlparse

          urlparse("https://example.com/path/to/file/?query#fragment")
          ParseResult(
              scheme="https",
              netloc="example.com",
              path="/path/to/file/",
              params="",
              query="query",
              fragment="fragment",
          )
        

urllib.parse

The urlsplit() and urlparse() APIs do not perform validation of inputs. They may not raise errors on inputs that other applications consider invalid. They may also succeed on some inputs that might not be considered URLs elsewhere. Their purpose is for practical functionality rather than purity.

https://docs.python.org/3/library/urllib.parse.html#url-parsing-security

Casos de uso comunes

  • Validar que sea una URL válida
  • Validar que una URL pertenezca a un dominio específico
  • Validar que una URL sea relativa al dominio actual
  • Validar que el path de una URL esté dentro de un directorio específico

El caso curioso de las URLs

URL
https://example.com protocolo con dominio
https://docs.example.com protocolo con sub-dominio
https://docs.example.example.com protocolo con sub-dominio
= urllib.parse = browser

El caso curioso de las URLs

URL
https://example.com. protocolo con dominio
https://docs.example.com. protocolo con dominio
= urllib.parse = browser

El caso curioso de las URLs

URL
example.com path relativo
/example.com path absoluto
//example.com protocolo relativo
= urllib.parse = browser

El caso curioso de las URLs

URL
foo://example.com URL con protocolo foo
mailto:[email protected] Abrir cliente de email
= urllib.parse = browser

El caso curioso de las URLs

URL
javascript:alert(document.domain) Ejecutar JS
javascript://example.com/alert(document.domain) Ejecutar JS (comentario)
javascript://example.com/%0Aalert(document.domain) Ejecutar JS (comentario seguido de salto de línea)
= urllib.parse = browser

El caso curioso de las URLs

URL
https:/{NEWLINE}/example.com protocolo con dominio
https://example.{NEWLINE}com protocolo con dominio
https://example.com/path{NEWLINE}to/file protocolo con dominio
= urllib.parse = browser

El caso curioso de las URLs

URL
https:/example.com protocolo sin dominio protocolo con dominio
https://user:[email protected] URL con basic auth URL con basic auth
= urllib.parse = browser

El caso curioso de las URLs

URL
\example.com path relativo path absoluto
\\example.com path relativo dominio relativo
/\example.com path absoluto dominio relativo
https:\\example.com\path\to\file Protocolo y path Full URL
= urllib.parse = browser

Ejemplo: validar si una URL pertece a un dominio

Open redirect

https://example.com/login/?next=https://evil.example.com

Ejemplo: validar si la URL es relativa al dominio actual

Ejemplo: validar si el path de una URL está dentro de un directorio

XSS

Cross-site scripting

XSS

Es una vulnerabilidad que permite a un atacante inyectar código HTML en una página web. Usualmente esto permite al atacante ejecutar JS en el contexto de la página.

XSS: Ejemplo


          def index(request):
              name = request.GET.get("name")
              return HttpResponse(f"<h1>Hello, {name}!</h1>")
        

XSS: cómo evitarlo

  • Escapar todo input
  • Usar templates

XSS: errores comunes

Uso incorrecto de las funciones de escape

https://docs.djangoproject.com/en/5.0/ref/utils/#django.utils.html.format_html

XSS: errores comunes

Confiar que ciertos tipos de datos no pueden contener código malicioso.

XSS: errores comunes

No poner entre comillas valores de atributos de los tags HTML


          <p title={{ name }}>Hello, {{ name }}!</p>
        

XSS: errores comunes

Retornar una objecto HttpResponse sin especificar el tipo de contenido.

SQL injection

Es una vulnerabilidad que permite a un atacante ejecutar comandos SQL en una base de datos.

SQLI: Ejemplo


          cursor.execute(f"SELECT * FROM users WHERE username='{username}'")
        

SQL injection

  • Usar queries parametrizadas
  • Usar un ORM

SQLi: errores comunes

Mal uso de queries parametrizadas

https://docs.djangoproject.com/en/5.1/topics/db/sql/#passing-parameters-into-raw

SQLi: errores comunes

Confiar en que ciertos tipos de datos no pueden contener comandos SQL

SSRF

Server side request forgery

SSRF

Es una vulnerabilidad que permite a un atacante realizar peticiones a la red interna de la aplicación.

SSRF: Ejemplo


          url = request.GET.get("url")
          response = requests.get(url)
        

Webhooks

  • Falta de autenticación / autorización
  • SSRF

SSRF: cómo evitarlo

  • Limitar los hosts a los que se puede hacer peticiones
  • Aislar requests que no son de confianza

https://github.com/stripe/smokescreen

Command injection

Es una vulnerabilidad que permite a un atacante ejecutar comandos arbitrarios en el sistema operativo.

Command injection: Ejemplo


          domain = request.GET.get("domain")
          os.system(f"ping -c 1 {domain}")
        

Command injection: errores comunes

  • Usar funciones que permiten ejecutar comandos directamente en el sistema operativo
  • No validar input de usuarios
  • No separar opciones de argumentos posicionales

ReDoS

Regular expression denial of service

ReDoS

Es una vulnerabilidad que permite a un atacante que la evaluación de una expresión regular sea muy costosa.

ReDOS: errores comunes

  • Usar regex
  • No escapar input de usuarios
  • Escribir expresiones regulares ineficientes

https://redosdetector.com/
https://makenowjust-labs.github.io/recheck/playground/

Nunca confíes en input de un usuario

Ni de las LLMs