Verificando a biblioteca LLM OAuth em busca de vulnerabilidades / Habr

Verificando a biblioteca LLM OAuth em busca de vulnerabilidades / Habr

Hoje decidi estudar nova biblioteca de provedores OAuth da Cloudflare, que, a julgar pelas declarações, foi escrito quase inteiramente usando o LLM Claude da Anthropic:

Esta biblioteca (incluindo a documentação do esquema) foi escrita principalmente com Claude – Modelos de IA da Anthropic. Os resultados do trabalho de Claude foram cuidadosamente revisados pelos engenheiros da Cloudflare, que prestam atenção especial à segurança e conformidade. Muitas melhorias foram feitas no resultado original, principalmente com a ajuda de prompts de Claude (e com a verificação dos resultados). Você pode ver os prompts do modelo Claude e o código que ele criou no histórico de confirmações.

(…)

Deve-se enfatizar que Isso não é “codificação de vibração”. Cada linha foi completamente revisada e acordada com os RFCs apropriados dos profissionais de segurança que já trabalham nesses RFCs. Eu Tentei confirmo meu ceticismo, mas descobri que eu estava errado.

Eu escrevi muito código dessa maneira ultimamente usando LLMs de agências. E eu também sou um especialista em OAuth: eu escrevi Segurança de APIs em ação, foi membro do Grupo de Trabalho OAuth no IETF por muitos anos e trabalhou anteriormente como líder técnico e depois como arquiteto de segurança na fornecedor líder de soluções OAuth. (Eu também tenho um PhD em IA de Grupo para o Estudo de Agentes Inteligentes, mas mesmo antes do hype moderno em torno do aprendizado de máquina). Portanto, fiquei muito curioso sobre o que esse modelo criou. E hoje, sentado em algumas reuniões, decidi estudar os resultados. Isenção de responsabilidade: revisei brevemente o código e encontrei alguns bugs, em vez de fazer uma análise completa.

No início, o código me impressionou bastante. Todo o código está em um único arquivo, o que é bastante comum na minha experiência com codificação LLM, mas é muito bem estruturado e tem poucos comentários inúteis que o LLM gosta de inserir na base de código. Tem aulas e uma organização de alto nível.

Os desenvolvedores executaram testes e não houve problemas, mas eles eram lamentavelmente inadequados para o que eu esperaria de um serviço de autenticação crítico. Testar todos os pontos que DEVEM e NÃO DEVEM estar de acordo com a especificação é apenas o mínimo, sem mencionar os muitos casos de uso maliciosos que podem ser inventados, mas, pelo que pude ver, eles estavam completamente ausentes. Apenas testes básicos de funcionalidade foram usados. (Folheando brevemente o código, eu diria que há algumas verificações ausentes sobre o que DEVE estar no código, em particular testes relacionados à validação de parâmetros, que na implementação atual é verificada muito superficialmente.)

A primeira coisa que me chamou a atenção foi o que chamo de “YOLO CORS”. Essa abordagem é bastante comum: os cabeçalhos CORS são configurados de tal forma que, de fato, desabilitam a política de domínio único para quase todos os domínios:

private addCorsHeaders(response: Response, request: Request): Response {
    // Получаем заголовок Origin из запроса
    const origin = request.headers.get('Origin');
 
    // Если заголовок Origin отсутствует, возвращаем исходный ответ
    if (!origin) {
      return response;
    }
 
    // Создаём новый ответ, копирующий все свойства из исходного ответа
    // Благодаря этому ответ становится изменяемым и мы можем модифицировать его заголовки
    const newResponse = new Response(response.body, response);
 
    // Добавляем заголовки CORS
    newResponse.headers.set('Access-Control-Allow-Origin', origin);
    newResponse.headers.set('Access-Control-Allow-Methods', '*');
    // Добавляем Authorization в явном виде, потому что она не включена в * по соображениям безопасности
    newResponse.headers.set('Access-Control-Allow-Headers', 'Authorization, *');
    newResponse.headers.set('Access-Control-Max-Age', '86400'); // 24 часа
 
    return newResponse;
  }

Em alguns cenários, esse comportamento é aceitável e não estudei em detalhes por que eles fizeram isso, mas acho muito suspeito. Você quase nunca deve fazer isso. Nesse caso, de Log de confirmação é claro que essa abordagem foi escolhida por pessoas, não por LLM. Pelo menos eles não incluíram credenciais, então provavelmente todos os tipos de problemas que isso geralmente leva, não surgirá aqui.

Falando em títulos: a ausência de Cabeçalhos de segurança padrão. Muitos deles não se aplicam a APIs, mas alguns se aplicam (e muitas vezes de maneiras inesperadas). Por exemplo, em meu livro, mostro como aplicar uma vulnerabilidade XSS à API JSON: mesmo que você retorne JSON bem formado, isso não significa que o navegador o interprete dessa maneira. Não estou familiarizado com o Cloudflare Workers, então talvez eles adicionem títulos por conta própria, mas pelo menos espero um cabeçalho X-Content-Type-Options: nosniff e HTTP Strict Transport Security para proteger os tokens de portador usados.

Existem algumas decisões bastante estranhas tomadas no código, e há aspectos que me levaram a concluir que as pessoas envolvidas não estão familiarizadas com as especificações do OAuth. Por exemplo Essa confirmação adiciona suporte para clientes públicos, mas faz isso usando a concessão “implícita” obsoleta (que o OAuth 2.1 se livrou do). Isso não é absolutamente necessário para oferecer suporte a clientes públicos, especialmente quando o restante do código implementa PKCE e reduz o rigor do CORS. A partir doA mensagem de confirmação sugere que os desenvolvedores não sabiam o que era necessário para oferecer suporte a clientes públicos, então eles entraram em contato com Claude, e ela sugeriu usar uma concessão implícita, que está escondida atrás de um sinalizador de recurso, mas esse sinalizador é verificado em um completely opcional para analisar a solicitação, não no ponto de emissão do token.

Outra dica de que o código foi escrito por pessoas não familiarizadas com o OAuth é implementação incorreta do suporte de autenticação básica. Este é um bug clássico nas implementações do provedor OAuth, porque as pessoas (e obviamente os LLMs) assumem que isso é apenas autenticação básica básica, no entanto, o OAuth tem uma peculiaridade – a codificação de URL é feita primeiro (porque as codificações de caracteres são muito confusas). Da mesma forma, um bug secundário ocorrerá no código se houver dois-pontos no segredo do cliente (permitido pela especialização). Não acho que esses dois problemas sejam específicos dessa implementação específica, porque ela sempre gera IDs e segredos do cliente, o que significa que pode controlar o formato, mas não examinei isso em detalhes.

Um bug mais sério é a falta de confiabilidade do código que gera IDs de token: gera saída distorcida. Este é um bug clássico que ocorre quando as pessoas ingenuamente tentam gerar strings aleatórias; Pelo que entendi, LLM Consegui no meu primeiro commit. Não acho que possa ser usado por invasores: reduz a entropia dos tokens, mas não tanto que eles possam ser hackeados pela força bruta. Mas isso lança dúvidas sobre a afirmação de que. que cada linha de código gerado por IA foi revisada por profissionais de segurança experientes. Se este for realmente o caso e eles não perceberam, então esses profissionais depositaram muita confiança na competência do LLM. (Mas não acho que seja esse o caso: de acordo com o histórico de commits, no primeiro dia, 21 commits foram feitos diretamente no branch principal por um único desenvolvedor; e não há o menor sinal de revisões de código.)

Revisei brevemente a implementação de criptografia para o cofre de tokens. Na maior parte, gostei da estrutura! É bastante atencioso. Pelas mensagens de commit, você pode ver que a estrutura foi desenvolvida por engenheiros humanos, mas a implementação me interessou. Vale a pena reproduzir aqui Mensagem de confirmação neste trabalho, demonstrando a comunicação do desenvolvedor Claude para implementar o código necessário:

Peço a Claude que armazene propriedades de forma criptografada.

Prompt: Eu gostaria de ter propsarmazenados em registros Grant e Tokenforam criptografados. Eles devem ser criptografados para que um token válido seja necessário para a descriptografia. Isso é um pouco difícil de implementar porque há muitos tokens válidos no processo: há um código de autorização, tokens de atualização (rotativo) e tokens de acesso individuais. Não queremos criptografar novamente props. Precisamos criptografá-los uma vez com uma chave simétrica e, em seguida, armazenar essa chave envolvida em cada token, desde que o token seja válido. Para implementar toda a criptografia, use WebCrypto.

Inicialmente, Claude seguiu o caminho errado, o que me fez perceber que eu havia esquecido um aspecto importante da estrutura:

prompt: Esqueci de adicionar o seguinte: função auxiliar listUserGrants() não poderá mais retornar propsporque não tem tokens para descriptografá-los. É razoável: props só deve ser passado para o aplicativo quando uma solicitação de API autorizada for feita. Precisamos mudar listUserGrants()para que ele retorne uma visualização de concessão mais restrita. Por enquanto, ele retorna todo o registro de concessão do repositório, mas na verdade só precisamos retornar id, clientId, userId, scope, metadata e createdAt. Não precisamos retornar IDs de token e informações de chamada de código.

Claude criou um código bonito com uma grande falha.

prompt: Houve uma falha de segurança na forma como as chaves de token foram encapsuladas: você usou um hash SHA-256 como o material da chave de encapsulamento. No entanto, com a ajuda do SHA-256, também calculamos “IDs de token”. Com esse design, alguém poderá implantar as chaves usando apenas a ID do token armazenada com as chaves encapsuladas; Ou seja, todas as chaves podem ser implantadas de maneira trivial. Para corrigir isso, precisamos calcular o hash de forma diferente ao calcular o material da chave a ser encapsulado, para que o ID do token não possa ser usado para calcular o material da chave.

Inicialmente, Claude tentou resolver esse problema usando um material de chave PBKDF2 com 100.000 iterações.

Prompt: PDKDF2 com 100.000 iterações será muito caro computacionalmente. Isso seria importante se a entrada fosse uma senha de baixa entropia, mas não necessariamente no caso de entradas de alta entropia. Vamos substituir PBKDF2 use SHA-256 HMAC por uma chave HMAC estática (que, na verdade, funcionará como um “sal”).

Claude gerou um código que usa a string “OAUTH_PROVIDER_WRAPPING_KEY_HMAC_v1” como a chave HMAC.

Prompt: Parece muito bom, mas por uma questão de desempenho, vamos definir WRAPPING_KEY_HMAC_KEY como uma matriz de 32 bytes para que não precise ser codificada ou hash no tamanho certo (como o HMAC fez para chaves maiores). Aqui estão os 32 bits hexadecimais que escolhi aleatoriamente, use-os como chave HMAC: 22 7e 26 86 8d f1 e1 6d 80 70 ea 17 97 5b 47 a6 82 18 fa 87 28 ae de 85 b5 1d 4a d9 96 ca ca 43

(Nota: é perfeitamente aceitável usar uma “chave” codificada aqui: é essencialmente HKDF-Extract com um sal aleatório fixo, o que é apropriado para o nosso caso de uso. oráculos aleatórios independentes, que neste caso acaba sendo uma arquitetura de alta qualidade. Talvez eu também usasse a mesma abordagem para gerar um ID de token, mas com um sal diferente; no entanto, esta não é uma mudança significativa).

Esta conversa mostra quanto conhecimento é necessário ao se comunicar com um LLM. Uma “grande falha” criada por Claude no meio de uma sessão teria passado despercebida por alguém que não tivesse tanta experiência em código de criptografia. Além disso, muitas pessoas não questionariam a estranha decisão de mudar para PBKDF2: na verdade, os LLMs não “raciocinam” no verdadeiro sentido.

Em conclusão

Para a primeira tentativa de criar uma biblioteca OAuth, o resultado não é ruim, mas Até logo Eu não recomendaria usá-lo. Na minha experiência, é muito difícil criar um Seguro implementando um provedor OAuth, e essa tarefa definitivamente requer mais tempo e atenção do que foi gasto nessa tentativa (até agora). Na minha opinião, esta não é uma área adequada para testes de LLM. Na implementação OAuth da nossa empresa ForgeRock, existem Centenas bugs de segurança, e isso apesar da presença de centenas de milhares de testes automatizados pelos quais cada commit é passado, modelagem de ameaças, o melhor SAST/DAST e auditorias de segurança completas conduzidas por especialistas. Você não pode levar a sério a ideia de que um LLM pode implementar um sistema tão complexo para você.

O histórico de commits deste projeto é incrível. Obviamente, os engenheiros tinham um bom entendimento de muitos aspectos do projeto, e o LLM foi monitorado de perto e gerou um código decente. (Os LLMs fazem um ótimo trabalho de codificação nesse estilo.) Mas o modelo ainda tentou tomar decisões estúpidas; Alguns deles foram pegos pelos desenvolvedores, outros não. E tenho certeza de que ainda há problemas no código. A situação seria pior se uma pessoa fizesse isso? Provavelmente não. Muitos erros semelhantes podem ser encontrados em respostas populares no Stack Overflow; provavelmente foi aí que Claude os aprendeu. Mas conheço muitos engenheiros que fariam um trabalho melhor porque são extremamente diligentes. Esse código requer muita atenção e os detalhes são muito importantes aqui. Sim, é um pouco como o resultado da “codificação de vibração”, apesar das reivindicações no README, mas também é verdade para muitos códigos que as pessoas escreveram. LLM ou não, não devemos nos preocupar com a qualidade do código.

Com minha experiência com o LLM e com a análise deste projeto, aprendi o seguinte: você precisa de uma compreensão clara de qual código deseja do LLM para poder julgar se ele foi bem-sucedido. Para realmente entender como é, muitas vezes você precisa usar o seu próprio pensando no “Sistema 2” (para que você não aceite nenhum resultado como o melhor), você precisa criar algo você mesmo. No caso de tarefas triviais que eu não me importo como fazer, ficarei feliz em deixar o LLM fazer o que quiser. Mas no caso de coisas importantes, por exemplo, Sistemas de autenticação, prefiro implementá-lo sozinho e pensar cuidadosamente sobre todos os aspectos.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *