tutoriales.com

Protección de Recursos Web con CORS: Una Guía Completa para Desarrolladores Seguros

Este tutorial ofrece una guía exhaustiva sobre la configuración y mitigación de riesgos asociados con el Intercambio de Recursos de Origen Cruzado (CORS). Aprenderás cómo implementar CORS de forma segura en tus aplicaciones web y cómo defenderte de posibles ataques.

Intermedio15 min de lectura13 views
Reportar error

La seguridad web es un pilar fundamental en el desarrollo de aplicaciones. Uno de los mecanismos esenciales para proteger tus recursos en el navegador es el Intercambio de Recursos de Origen Cruzado (CORS). Pero, ¿qué es exactamente CORS y por qué es tan importante para la seguridad de tus aplicaciones web? Vamos a desglosarlo.

📖 ¿Qué es CORS (Cross-Origin Resource Sharing)?

CORS es un mecanismo de seguridad implementado por los navegadores web que permite a un servidor indicar cualquier otro origen (dominio, esquema o puerto) aparte del suyo propio, desde el cual un navegador debe permitir la carga de recursos. Su objetivo principal es fortalecer la seguridad del navegador al prevenir que sitios web maliciosos realicen solicitudes no autorizadas a otros dominios en nombre del usuario.

Sin CORS, los navegadores imponen una política de mismo origen (Same-Origin Policy - SOP), que restringe las solicitudes HTTP iniciadas desde un script a interactuar únicamente con recursos del mismo origen del que se sirvió la página HTML. CORS relaja esta política de forma controlada.

🌐 La Política de Mismo Origen (SOP) vs. CORS

Para entender CORS, primero debemos comprender la SOP. La Política de Mismo Origen es una característica de seguridad crítica que evita que un documento o script cargado de un origen obtenga o interactúe con recursos de otro origen. Por ejemplo, un script en www.ejemplo.com no puede hacer una solicitud XMLHttpRequest a api.otrodominio.com sin el permiso explícito del servidor api.otrodominio.com.

La SOP protege a los usuarios de sitios maliciosos que intentan robar datos sensibles, como cookies de sesión, credenciales o información bancaria, de otros sitios web en los que el usuario está autenticado. CORS es la forma en que los servidores pueden optar por relajar esta restricción de seguridad para orígenes específicos.

📌 Nota: La SOP se aplica a solicitudes HTTP realizadas con JavaScript (como `XMLHttpRequest` o `Fetch API`), no a la inclusión de recursos como imágenes (``), hojas de estilo (``) o scripts (`
Política de Mismo Origen Origen A Acceso Denegado Origen B Seguridad por defecto: el navegador bloquea peticiones entre dominios. CORS Origen A Acceso Permitido (si el servidor B lo autoriza) Origen B Intercambio de recursos controlado mediante cabeceras HTTP.

🚀 ¿Cómo Funciona CORS? Tipos de Solicitudes

CORS introduce un conjunto de encabezados HTTP estándar que permiten a los servidores y navegadores negociar si se permite o no el acceso a un recurso desde un origen diferente. Hay dos tipos principales de solicitudes CORS:

1. Solicitudes Simples (Simple Requests)

Una solicitud se considera "simple" si cumple todas las siguientes condiciones:

  • Método: GET, HEAD o POST.
  • Encabezados: Solo ciertos encabezados "seguros para CORS2" se establecen automáticamente por el agente de usuario (como Accept, Accept-Language, Content-Language, Content-Type).
    • Si se usa Content-Type, su valor debe ser application/x-www-form-urlencoded, multipart/form-data o text/plain.

Cuando un navegador detecta una solicitud simple a un origen cruzado, simplemente envía la solicitud con un encabezado Origin adicional que indica de dónde proviene la solicitud. El servidor decide si permite la solicitud basándose en la configuración de CORS y responde con el encabezado Access-Control-Allow-Origin si la solicitud es permitida.

Flujo de Solicitud Simple:

  1. Navegador del cliente hace una solicitud GET a api.ejemplo.com/data desde www.mi-app.com.
GET /data HTTP/1.1
Host: api.ejemplo.com
Origin: http://www.mi-app.com
  1. Servidor api.ejemplo.com recibe la solicitud. Si está configurado para permitir http://www.mi-app.com, responde:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.mi-app.com
Content-Type: application/json

{"mensaje": "Datos permitidos"}
  1. Navegador verifica el encabezado Access-Control-Allow-Origin. Si coincide con el origen de la solicitud o es *, permite que la aplicación acceda a la respuesta.

2. Solicitudes Preflight (Preflight Requests)

Las solicitudes "no simples" o "preflighted" son aquellas que no cumplen las condiciones de una solicitud simple. Esto incluye:

  • Usar métodos HTTP como PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH.
  • Usar encabezados personalizados o encabezados de tipo Content-Type como application/json.

Antes de enviar la solicitud real, el navegador envía automáticamente una solicitud OPTIONS (conocida como "preflight") al servidor para preguntar qué métodos y encabezados son seguros para usar con ese recurso. El servidor debe responder a esta solicitud preflight con información sobre sus capacidades CORS.

Flujo de Solicitud Preflight:

  1. Navegador del cliente quiere hacer una solicitud POST con Content-Type: application/json a api.ejemplo.com/users desde www.mi-app.com.
  2. Navegador envía una solicitud OPTIONS (preflight) al servidor:
OPTIONS /users HTTP/1.1
Host: api.ejemplo.com
Origin: http://www.mi-app.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
  1. Servidor api.ejemplo.com recibe la solicitud OPTIONS. Si está configurado para permitir http://www.mi-app.com, POST y Content-Type, responde:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://www.mi-app.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
*   `Access-Control-Allow-Origin`: Origen(es) permitidos.
*   `Access-Control-Allow-Methods`: Métodos HTTP permitidos.
*   `Access-Control-Allow-Headers`: Encabezados personalizados permitidos.
*   `Access-Control-Max-Age`: Tiempo en segundos que la respuesta preflight puede ser cacheada.

4. Navegador recibe la respuesta preflight. Si es satisfactoria, procede a enviar la solicitud POST real:

POST /users HTTP/1.1
Host: api.ejemplo.com
Origin: http://www.mi-app.com
Content-Type: application/json

{"nombre": "Juan"}
  1. Servidor api.ejemplo.com procesa la solicitud POST real y responde con los datos.
🔥 Importante: Si la respuesta preflight no es satisfactoria (por ejemplo, el origen no está permitido), el navegador *no* enviará la solicitud real y reportará un error CORS.

🛠️ Configuración Segura de CORS

Una configuración incorrecta de CORS puede abrir tu aplicación a vulnerabilidades significativas. La clave es ser lo más restrictivo posible.

1. Access-Control-Allow-Origin (ACAO)

Este es el encabezado más crítico. Indica qué orígenes tienen permiso para acceder a los recursos.

  • ❌ Evita Access-Control-Allow-Origin: * (Wildcard) con credenciales: Si permites el wildcard * y también Access-Control-Allow-Credentials: true, estás creando una vulnerabilidad seria. El navegador bloqueará esta combinación, pero si se desactiva Access-Control-Allow-Credentials, el wildcard permite que cualquier sitio web realice solicitudes, lo que puede ser explotado para fugas de información o ataques CSRF si no hay otras protecciones.

  • ✅ Lista Blanca de Orígenes: La mejor práctica es especificar explícitamente los orígenes permitidos. Esto significa que solo tus dominios de frontend (o de otras aplicaciones que necesitan acceder a tu API) pueden interactuar con tus recursos.

# Ejemplo en Nginx
add_header 'Access-Control-Allow-Origin' 'https://mi-aplicacion-frontend.com';
add_header 'Access-Control-Allow-Origin' 'https://staging.mi-aplicacion-frontend.com';
En muchos frameworks de desarrollo, puedes configurar esto dinámicamente, verificando el encabezado `Origin` de la solicitud y respondiendo con él si está en tu lista de permitidos.
# Ejemplo en Flask (Python)
from flask import Flask, request, jsonify
from flask_cors import CORS # Necesitas pip install Flask-CORS

app = Flask(__name__)

# Lista de orígenes permitidos
allowed_origins = [
"http://localhost:3000",
"https://mi-aplicacion-frontend.com",
"https://staging.mi-aplicacion-frontend.com"
]

@app.before_request
def handle_preflight():
if request.method == 'OPTIONS':
response = jsonify({'message': 'Preflight Ok'})
origin = request.headers.get('Origin')
if origin in allowed_origins:
response.headers.add('Access-Control-Allow-Origin', origin)
response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
response.headers.add('Access-Control-Max-Age', '86400')
return response

@app.after_request
def add_cors_headers(response):
origin = request.headers.get('Origin')
if origin in allowed_origins:
response.headers.add('Access-Control-Allow-Origin', origin)
response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
return response

@app.route('/api/data')
def get_data():
return jsonify({"message": "Datos seguros!"})

if __name__ == '__main__':
app.run(debug=True)
<div class="callout tip">💡 <strong>Consejo:</strong> Para entornos de desarrollo local, `http://localhost:XXXX` suele ser un origen necesario. Recuerda eliminarlo o usar variables de entorno para controlar su presencia en producción.</div>

2. Access-Control-Allow-Methods

Especifica los métodos HTTP permitidos (GET, POST, PUT, DELETE, etc.). Sé explícito y permite solo los métodos necesarios para cada endpoint.

add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';

3. Access-Control-Allow-Headers

Indica qué encabezados personalizados pueden ser usados en las solicitudes reales. Si tu frontend envía un encabezado Authorization, debes permitirlo aquí.

add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';

4. Access-Control-Allow-Credentials

Si tu aplicación necesita enviar cookies, encabezados de autorización HTTP o certificados TLS con las solicitudes de origen cruzado, debes establecer este encabezado a true y el cliente debe usar credentials: 'include' en su Fetch API o withCredentials = true en XMLHttpRequest.

⚠️ Advertencia: Usar `Access-Control-Allow-Origin: *` con `Access-Control-Allow-Credentials: true` está prohibido por el estándar y los navegadores lo ignorarán (mostrarán un error). Si necesitas credenciales, DEBES especificar un origen concreto.

5. Access-Control-Max-Age

Define por cuánto tiempo (en segundos) el navegador puede cachear la respuesta preflight. Esto reduce el número de solicitudes OPTIONS y mejora el rendimiento. Un valor común es 86400 segundos (24 horas).

add_header 'Access-Control-Max-Age' '86400';

🛡️ Vulnerabilidades y Mala Configuración de CORS

Una implementación incorrecta de CORS puede llevar a graves vulnerabilidades de seguridad. Aquí algunas de las más comunes:

1. Wildcard * con Dominios Confiables Falsos

Algunas implementaciones intentan ser inteligentes y devuelven el valor del encabezado Origin en Access-Control-Allow-Origin si Origin termina en un dominio "confiable".

Por ejemplo, si un servidor permite *.ejemplo.com:

# Solicitud de un atacante
GET /data HTTP/1.1
Host: api.servidor.com
Origin: http://malicioso.ejemplo.com.evil.com

# Respuesta del servidor vulnerable
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://malicioso.ejemplo.com.evil.com # ¡Vulnerable!

Un atacante podría registrar un dominio como ejemplo.com.evil.com y engañar a la lógica de validación, obteniendo acceso a recursos sensibles.

💡 Consejo: Siempre valida que el `Origin` esté en una **lista estricta de dominios permitidos** o use una expresión regular que coincida exactamente con los dominios. Evita verificaciones de sufijo ingenuas.

2. Orígenes Nulos (null Origin)

El encabezado Origin: null puede ser enviado por el navegador en ciertas situaciones, como redirecciones (data: o file: URLs) o el uso de iframes sandbox. Si tu configuración CORS permite el origen null, un atacante podría crear un archivo HTML local o un iframe sandbox que interactúe con tu API.

# Evita esto si no es estrictamente necesario
add_header 'Access-Control-Allow-Origin' 'null';

3. Reflexión del Origin sin Validación

Simplemente reflejar el encabezado Origin de la solicitud en la respuesta Access-Control-Allow-Origin sin ninguna validación es una de las vulnerabilidades más críticas.

// Ejemplo PHP (Vulnerable)
if (isset($_SERVER['HTTP_ORIGIN'])) {
    header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
}

Esto permite a cualquier dominio realizar solicitudes de origen cruzado, eludiendo completamente la SOP y permitiendo ataques como el robo de tokens CSRF, información sensible o ejecución de acciones maliciosas.

4. Mala Configuración del Access-Control-Allow-Headers

Permitir encabezados personalizados innecesarios podría, en casos raros y combinados con otras vulnerabilidades, abrir una puerta a ataques de inyección. Sé restrictivo también con los encabezados permitidos.

5. Servidores Frontend y Backend en el Mismo Origen

Si tu frontend y backend están en el mismo dominio (ej. mi-app.com/frontend y mi-app.com/api), CORS no es necesario porque están bajo la misma Política de Mismo Origen. La configuración de CORS en este escenario podría ser superflua, pero si se planea desacoplar los servicios en el futuro, es bueno considerarlo.

🕵️ Cómo Auditar y Probar la Configuración CORS

Es crucial auditar tu configuración CORS para asegurar que no introduces vulnerabilidades.

1. Herramientas del Navegador (DevTools)

La forma más sencilla es usar las herramientas de desarrollo de tu navegador (Chrome DevTools, Firefox Developer Tools). Abre la pestaña "Network" y observa las solicitudes HTTP. Los errores CORS se mostrarán en la consola y las respuestas preflight serán visibles.

F12 (o Ctrl + Shift + I en Windows/Linux, Cmd + Option + I en Mac) para abrir las DevTools.

2. curl o Postman/Insomnia

Puedes simular solicitudes de origen cruzado usando curl o herramientas como Postman. Incluye el encabezado Origin para ver cómo responde el servidor.

# Simular una solicitud simple
curl -v -H "Origin: https://malicious-site.com" https://your-api.com/data

# Simular una solicitud preflight
curl -v -X OPTIONS -H "Origin: https://malicious-site.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization" \
  https://your-api.com/users

Busca los encabezados Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc., en la respuesta.

3. Escáneres de Vulnerabilidades

Herramientas como Burp Suite o ZAP (OWASP Zed Attack Proxy) tienen funcionalidades para detectar configuraciones CORS inseguras.

✅ Mejores Prácticas para una Implementación Segura de CORS

Sigue estas directrices para implementar CORS de manera robusta y segura:

  • Principio de Mínimo Privilegio: Permite solo los orígenes, métodos y encabezados HTTP que sean absolutamente necesarios.
  • Lista Blanca Estricta: Mantén una lista estricta y explícita de dominios permitidos para Access-Control-Allow-Origin. Evita el wildcard * en producción si Access-Control-Allow-Credentials: true es requerido.
  • Validación del Origin: Si necesitas un Access-Control-Allow-Origin dinámico, valida rigurosamente el encabezado Origin de la solicitud contra una lista blanca predefinida o expresiones regulares robustas. NO lo reflejes directamente.
  • Access-Control-Max-Age: Úsalo para mejorar el rendimiento, pero ten en cuenta que un valor muy alto podría retrasar la aplicación de cambios en la política CORS.
  • Manejo de Credenciales: Si necesitas enviar credenciales (cookies, encabezados de autorización), asegúrate de establecer Access-Control-Allow-Credentials: true y que Access-Control-Allow-Origin no sea *.
  • Pruebas Exhaustivas: Realiza pruebas de seguridad regulares (incluyendo pen testing) para identificar cualquier mala configuración de CORS.
  • Documentación: Documenta claramente tu política CORS y por qué se han tomado ciertas decisiones.
⚠️ Advertencia: Una configuración de CORS incorrecta puede ser tan peligrosa como no tener ninguna seguridad, ya que relaja la SOP de manera incontrolada.

📊 Resumen de Encabezados CORS Clave

Encabezado HTTPPropósitoRecomendación de Seguridad
---------
Origin (Request)Indica el origen de la solicitud de origen cruzado.N/A (automático por el navegador).
Access-Control-Allow-OriginEspecifica qué orígenes pueden acceder al recurso.Lista blanca estricta. Evitar * con credenciales. No reflejar sin validación.
---------
Access-Control-Allow-MethodsEnumera los métodos HTTP permitidos (GET, POST, etc.).Mínimo privilegio. Permitir solo los métodos necesarios.
Access-Control-Allow-HeadersEspecifica qué encabezados personalizados están permitidos.Mínimo privilegio. Permitir solo los encabezados requeridos (ej. Authorization).
---------
Access-Control-Allow-CredentialsIndica si el navegador debe enviar cookies o encabezados de autorización.Usar true solo si es necesario y con ACAO explícito (no *).
Access-Control-Max-AgeDuración en segundos para cachear la respuesta preflight.Usar un valor razonable (ej. 86400s).
95% Conocimiento de CORS Alcanzado

Conclusión

CORS es una herramienta indispensable en el arsenal de seguridad web, diseñada para proteger a los usuarios de ataques de origen cruzado al imponer y relajar de forma controlada la Política de Mismo Origen. Comprender cómo funciona y, más importante aún, cómo configurarlo de forma segura es vital para cualquier desarrollador que construya aplicaciones web modernas.

Al seguir las mejores prácticas de una lista blanca estricta de orígenes, métodos y encabezados, y al evitar configuraciones permisivas o ingenuas, puedes asegurarte de que tus aplicaciones están bien protegidas contra vulnerabilidades de origen cruzado.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!