Disponível para novos projetos Acra, Gana
Todos os textos

Cibersegurança · Autenticação · OAuth · Privilégio mínimo

Trancar a porta dos fundos: como eu protegi o login de admin do meu site

Quem controla o painel de admin de um site controla o que todos os visitantes veem e executam. Aqui está como o admin deste site funciona sem um servidor para invadir, por que sua chave só abre uma porta e a lacuna na cadeia de suprimentos que ainda preciso fechar.

Por Festus Boamah · · 4 min de leitura

A porta da frente de um site é a parte que os visitantes veem. O painel de admin é a porta dos fundos, e os atacantes sabem disso. Quem controla o admin controla o que todos os visitantes lêem e, no pior caso, que código é executado nos seus navegadores. Uma página inicial vandalizada é constrangedora. Uma página inicial que serve silenciosamente um script malicioso a todos que a visitam é muito pior.

Então, quando eu adicionei um editor a este site, para que eu possa publicar posts do meu telefone sem abrir um editor de código, eu tratei seu login como a parte mais sensível de todo o sistema.

Diagrama do fluxo de entrada do administrador: o CMS no navegador pede a um pequeno trabalhador de autenticação para entrar, o trabalhador envia o usuário para o GitHub com um valor de estado aleatório, o GitHub retorna um código e o trabalhador retorna um token apenas para festusboamah.com. Cada salvamento se torna um commit do Git para o main.

Decisão 1: nenhum servidor para invadir

A maioria dos sistemas de gerenciamento de conteúdo é executada em um servidor com um banco de dados de usuários e senhas. Esse servidor se torna um alvo: um plugin desatualizado, uma senha fraca ou um banco de dados exposto pode entregar o site inteiro a um atacante.

Este site não tem nada disso. É um site estático, reconstruído a partir de um repositório Git no GitHub sempre que algo muda. O painel de admin (Sveltia CMS) é apenas um aplicativo JavaScript que é executado no meu navegador e se comunica diretamente com a API do GitHub. Salvar um post significa fazer um commit.

Isso muda a pergunta de segurança. Não há servidor de admin para corrigir e nenhum banco de dados de senhas para vazar. A verdadeira pergunta se torna: quem pode escrever no repositório? Tudo o mais segue da resposta.

Decisão 2: uma chave que abre uma porta

Para escrever no repositório, o CMS precisa de um token do GitHub. A maneira fácil de obtê-lo é um aplicativo OAuth clássico com o repo escopo. Mas esse token seria capaz de ler e escrever todos os repositórios que a minha conta pode alcançar, públicos e privados. Se ele vazar, o dano irá muito além deste site.

Em vez disso, o login passa por um Aplicativo GitHub que é:

  • instalado apenas neste repositório, e
  • concedido uma permissão: Conteúdo (ler e escrever). Ele não pode tocar em problemas, configurações, fluxos de trabalho, outros repositórios ou minha conta.

Isso é o princípio do privilégio mínimo: dar a cada chave o menor poder que ainda faz o trabalho, para que quando (não se) algo dá errado, o raio de ação seja pequeno.

Decisão 3: guardar a transferência

O fluxo de login OAuth passa por um pequeno Cloudflare Worker (o sveltia-cms-auth de código aberto, vendido no meu repositório sem modificações em um commit fixo, para que eu saiba exatamente qual código é executado). Ele mantém o segredo do cliente do Aplicativo GitHub como um segredo de Trabalhador criptografado, nunca no código ou no repositório. Três de suas verificações valem a pena explicar, porque cada uma bloqueia um ataque real:

  • Lista de domínios permitidos. O Trabalhador só inicia um login para festusboamah.com. Alguém que copia minha página de admin para o seu próprio domínio não pode usar meu ajudante de login.
  • Proteção contra CSRF com state. Antes de me enviar para o GitHub, o Worker gera um valor aleatório, armazena-o em um HttpOnly, Secure cookie, e o passa para o GitHub como o parâmetro OAuth state. Quando o GitHub me devolve, os dois devem coincidir. Isso impede que um atacante engane meu navegador para concluir um login que eles iniciaram.
  • Entrega verificada de origem. O token é devolvido à janela do CMS com postMessage. O Worker só o libera quando a origem relatada pelo navegador da janela que faz a solicitação é meu domínio. Essa origem é definida pelo navegador, não pela página, então não pode ser falsificada.

Decisão 4: tornar todas as alterações responsáveis

Porque cada salvamento é um commit Git para o main, cada alteração no site tem um autor, um carimbo de data e hora e uma diferença exata. Em termos de STRIDE, isso aborda a negação de responsabilidade: nada muda no site sem um registro. Isso também me dá recuperação instantânea: se algo ruim fosse publicado, posso reverter o commit e o Cloudflare Pages reconstrói a versão limpa em cerca de um minuto.

As páginas de administração também recebem seus próprios cabeçalhos HTTP: uma Política de Segurança de Conteúdo separada, X-Robots-Tag: noindex para que os mecanismos de busca não as listem, e Cache-Control: no-store para que nada sensível seja armazenado em cache.

Onde ainda é fraco

  • O script do CMS não tem verificação de integridade. A página de administração carrega o Sveltia CMS do CDN unpkg. Eu fixo uma versão exata, o que protege contra atualizações surpresa, mas não há hash de Integridade de Sub-recurso. Se o CDN fosse comprometido e servisse um arquivo modificado, meu navegador o executaria com um token do GitHub válido ao alcance. Isso é um risco clássico de cadeia de suprimentos de software. A solução é um integrity hash na tag do script ou servir o arquivo a partir do meu próprio domínio.
  • A CSP de administração é mais frouxa do que a do site. Para carregar o CMS, a política de administração permite scripts do unpkg e scripts inline. Isso significa que se um bug de injeção existisse na administração, a política não impediria que um script de qualquer pacote no unpkg fosse executado. Hospedar o CMS em meu próprio domínio me permitiria apertar isso também.
  • Tudo depende de uma conta do GitHub. O Aplicativo limita o que um token pode fazer, mas quem controla minha conta do GitHub controla o site. Isso torna a autenticação de dois fatores forte nessa conta (idealmente um passkey ou chave de segurança em vez de SMS) o controle mais importante nesse design inteiro, mesmo que não esteja no código.

A lição

A decisão de segurança mais útil aqui não foi uma verificação inteligente. Foi remover o servidor inteiro, e então tornar a única chave restante o mais estreita possível. Antes de adicionar defesas, sempre vale a pena perguntar: o que posso remover, para que haja menos a defender?

Descobriu algo que eu perdi? Diga-me.

Festus Boamah

Vamos trabalhar juntos

Vamos construir algo com significado.

Tem um produto para construir, uma marca para moldar ou uma aula para dar? Adoraria saber.