Disponible para nuevos proyectos Acra, Ghana
Todo escrito

Ciberseguridad · Autenticación · OAuth · Privilegio mínimo

Bloquear la puerta trasera: cómo seguí el acceso de administrador de mi sitio web

Quienquiera que controle el panel de administrador de un sitio web controla lo que cada visitante ve y ejecuta. Aquí está cómo funciona el administrador de este sitio sin un servidor que pueda ser vulnerado, por qué su clave solo abre una puerta y la brecha en la cadena de suministro que todavía necesito cerrar.

Por Festus Boamah · · 4 min de lectura

La puerta principal de un sitio web es la parte que ven los visitantes. El panel de administrador es la puerta trasera, y los atacantes lo saben. Quienquiera que controle el administrador controla lo que cada visitante lee, y en el peor de los casos, qué código se ejecuta en sus navegadores. Una página de inicio desfigurada es vergonzosa. Una página de inicio que sirve silenciosamente un script malicioso a todos los que la visitan es mucho peor.

Así que cuando agregué un editor a este sitio web, para que pueda publicar posts desde mi teléfono sin abrir un editor de código, traté su inicio de sesión como la parte más sensible de todo el sistema.

Diagrama del flujo de inicio de sesión de administrador: el CMS en el navegador solicita a un pequeño trabajador de autenticación que inicie sesión, el trabajador envía al usuario a GitHub con un valor de estado aleatorio, GitHub devuelve un código y el trabajador devuelve un token solo a festusboamah.com. Cada guardado se convierte en un commit de Git en main.

Decisión 1: no hay servidor que pueda ser vulnerado

La mayoría de los sistemas de gestión de contenido se ejecutan en un servidor con una base de datos de usuarios y contraseñas. Ese servidor se convierte en un objetivo: un plugin desactualizado, una contraseña débil o una base de datos expuesta pueden entregarle el sitio completo a un atacante.

Este sitio no tiene nada de eso. Es un sitio estático, reconstruido desde un repositorio de Git en GitHub cada vez que algo cambia. El panel de administrador (Sveltia CMS) es solo una aplicación de JavaScript que se ejecuta en mi navegador y habla directamente con la API de GitHub. Guardar un post significa hacer un commit.

Eso cambia la pregunta de seguridad. No hay servidor de administrador que parchear ni base de datos de contraseñas que filtrar. La verdadera pregunta se convierte en: ¿quién puede escribir en el repositorio? Todo lo demás sigue de la respuesta.

Decisión 2: una clave que abre una puerta

Para escribir en el repositorio, el CMS necesita un token de GitHub. La forma fácil de obtener uno es una aplicación clásica de OAuth con el repo alcance. Pero ese token podría leer y escribir cada repositorio al que mi cuenta puede acceder, público y privado. Si se filtra, el daño iría mucho más allá de este sitio web.

En su lugar, el inicio de sesión se realiza a través de una aplicación de GitHub que es:

  • instalada en este repositorio solo, y
  • concedida una sola permiso: Contenido (leer y escribir). No puede tocar problemas, ajustes, flujos de trabajo, otros repositorios o mi cuenta.

Esto es el principio de privilegio mínimo: dar a cada clave el menor poder que aún hace el trabajo, para que cuando (no si) algo sale mal, el radio de acción es pequeño.

Decisión 3: proteger la transferencia

El flujo de inicio de sesión de OAuth pasa por un pequeño Cloudflare Worker (el de código abierto sveltia-cms-auth, incorporado a mi repositorio sin modificar en un commit anclado para que sepa exactamente qué código se ejecuta). Contiene el secreto del cliente de la aplicación de GitHub como un secreto de Worker cifrado, nunca en el código ni en el repositorio. Tres de sus comprobaciones valen la pena explicar, porque cada una bloquea un ataque real:

  • Lista de dominios permitidos. El Worker solo inicia un inicio de sesión para festusboamah.com. Alguien que copia mi página de administrador en su propio dominio no puede usar mi ayudante de inicio de sesión.
  • Protección contra CSRF con state. Antes de enviarme a GitHub, el Worker genera un valor aleatorio, lo almacena en una HttpOnly, Secure cookie, y lo pasa a GitHub como el parámetro OAuth state. Cuando GitHub me devuelve, los dos deben coincidir. Esto evita que un atacante engañe a mi navegador para que termine una sesión de inicio que ellos iniciaron.
  • Entrega con verificación de origen. El token se devuelve a la ventana del CMS con postMessage. El Worker solo lo libera cuando el origen informado por el navegador de la ventana que realiza la solicitud es mi dominio. Ese origen lo establece el navegador, no la página, por lo que no se puede falsificar.

Decisión 4: hacer que cada cambio sea responsable

Como cada guardado es un commit de Git en main, cada cambio en el sitio tiene un autor, una marca de tiempo y una diferencia exacta. En términos de STRIDE, esto aborda la negación: nada cambia en el sitio sin un registro. También me da una recuperación instantánea: si algo malo se publicara, puedo revertir el commit y Cloudflare Pages reconstruye la versión limpia en aproximadamente un minuto.

Las páginas de administración también obtienen sus propias cabeceras HTTP: una Política de Seguridad de Contenido separada, X-Robots-Tag: noindex para que los motores de búsqueda no las listen, y Cache-Control: no-store para que nada sensible se almacene en caché.

Donde todavía es débil

  • El script del CMS no tiene comprobación de integridad. La página de administración carga Sveltia CMS desde el CDN unpkg. Establezco una versión exacta, lo que me protege contra actualizaciones sorpresa, pero no hay un hash de Integridad de Subrecursos. Si el CDN se viera comprometido y sirviera un archivo modificado, mi navegador lo ejecutaría con un token de GitHub válido al alcance. Esto es un riesgo clásico de cadena de suministro de software. La solución es un integrity hash en la etiqueta del script o servir el archivo desde mi propio dominio.
  • La CSP de administración es más permisiva que la del sitio. Para cargar el CMS, la política de administración permite scripts desde unpkg y scripts en línea. Eso significa que si alguna vez existiera un bug de inyección en la administración, la política no detendría un script de cualquier paquete en unpkg. Alojar el CMS en mi propio dominio me permitiría ajustar esto también.
  • Todo depende de una sola cuenta de GitHub. La aplicación limita lo que puede hacer un token, pero quien controle mi cuenta de GitHub controla el sitio. Eso hace que la autenticación de dos factores fuerte en esa cuenta (idealmente un pase o una clave de seguridad en lugar de SMS) sea el control más importante en todo este diseño, aunque no esté en el código en absoluto.

La lección

La decisión de seguridad más útil aquí no fue una comprobación astuta. Fue eliminar el servidor por completo, y luego hacer que la única clave restante sea lo más estrecha posible. Antes de agregar defensas, siempre vale la pena preguntar: ¿qué puedo eliminar, para que haya menos que defender?

¿Has detectado algo que me he perdido? Dímelo.

Festus Boamah

Trabajemos juntos

Construyamos algo que importe.

¿Tienes un producto que construir, una marca que dar forma o una clase que impartir? Me encantaría saberlo.