Ferramentas de Inteligência Artificial como Bard, ChatGPT e Bing Chat são os grandes nomes atuais na categoria , que está em ascensão. Large Language Model (LLM) Os LLMs são treinados em vastos conjuntos de dados para serem capazes de se comunicar usando a linguagem humana cotidiana como um prompt de bate-papo. Dada a flexibilidade e o potencial dos LLMs, as empresas estão a integrá-los em muitos fluxos de trabalho dentro da indústria tecnológica para tornar as nossas vidas melhores e mais fáceis. Uma das principais integrações de fluxo de trabalho de IA é um plug-in de código de preenchimento automático, que fornece a capacidade de prever e gerar padrões de código correspondentes para codificar de forma mais rápida e eficiente. No mercado atual de ferramentas geradoras de código de IA, existem muitos participantes, incluindo , , , e muito mais. Esta postagem do blog descreve exemplos de armadilhas de segurança comuns que os desenvolvedores enfrentam ao usar essas ferramentas. GitHub Copilot Amazon CodeWhisperer Google Cloud Code (Duet AI) Blackbox AI Code Generation O objetivo deste artigo é conscientizar e enfatizar que o código gerado automaticamente não pode ser cegamente confiável e ainda requer uma revisão de segurança para evitar a introdução de vulnerabilidades de software. Observação: alguns fornecedores sugerem que suas ferramentas de preenchimento automático podem conter trechos de código inseguros. Muitos artigos já enfatizaram que as ferramentas de preenchimento automático geram vulnerabilidades conhecidas, como , injeção de SQL e XSS. Esta postagem destacará outros tipos de vulnerabilidade que dependem mais do contexto do código, onde há uma grande probabilidade de o preenchimento automático da IA inserir bugs em seu código. IDOR Os modelos de IA treinados para código geralmente são treinados em grandes bases de código com a missão principal de produzir código funcional. Ferramentas de segurança e geração de código de IA Muitos problemas de segurança têm uma forma conhecida e definida de mitigação sem comprometer o lado do desempenho (por exemplo, injeções de SQL poderiam ser evitadas não concatenando entradas/parâmetros do usuário diretamente na consulta) - e, portanto, podem ser erradicadas, pois não há razão para escrever o código de maneira insegura. No entanto, muitos problemas de segurança e podem ser perfeitamente seguros quando implementados como funcionalidade interna, ao mesmo tempo que, quando confrontados com informações externas de um cliente, tornam-se uma vulnerabilidade e são, portanto, difíceis de distinguir através do conjunto de dados de aprendizagem da IA. dependem do contexto Nessas vulnerabilidades, o uso específico do código geralmente depende de outras partes do código e da finalidade geral do aplicativo. Aqui estão alguns casos de uso comuns e exemplos que testamos, mostrando alguns desses tipos de vulnerabilidades: Caso de uso: Buscando arquivos da entrada do usuário - leitura arbitrária de arquivos Caso de uso: comparação de tokens secretos - malabarismo/coerção de tipo Caso de uso: Mecanismo de senha esquecida - Colisões de mapeamento de casos Unicode Caso de uso: Gerando arquivo de configuração - práticas de segurança inadequadas Caso de uso: Objetos de configuração – Inconsistências que levam à desserialização insegura Depois de ver como as ferramentas de geração de código de IA lidam com os casos acima, tentamos testar sua capacidade de criar filtros de segurança adequados e outros mecanismos de mitigação. Aqui estão alguns casos de uso comuns e exemplos que testamos solicitando deliberadamente código seguro: Caso de uso de solicitação de código seguro: leitura de arquivos - evitando passagem de diretório Solicitação de código seguro - Upload de arquivo - Lista de extensões restritas Caso de uso: Buscando arquivos da entrada do usuário - leitura arbitrária de arquivos Muitos aplicativos incluem código que retorna um arquivo ao usuário com base em um nome de arquivo. Usar a passagem de diretório para ler arquivos arbitrários no servidor é um método comum usado por malfeitores para obter informações não autorizadas. Pode parecer óbvio limpar a entrada do nome/caminho do arquivo proveniente do usuário antes de recuperar o arquivo, mas assumimos que é uma tarefa difícil para um modelo de IA treinado em bases de código genéricas - isso ocorre porque alguns dos conjuntos de dados como parte de sua funcionalidade (pode até diminuir o desempenho ou prejudicar a funcionalidade) ou não são prejudicados por caminhos não higienizados, pois destinam-se apenas para uso interno. não precisam usam caminhos higienizados Vamos tentar gerar uma função básica de busca de arquivos a partir da entrada do usuário, usando o preenchimento automático do Google Cloud Code - Duet AI. Permitiremos que o Duet AI complete automaticamente nosso código, com base em nossos nomes de funções e rotas - Sugestão de ferramenta AI (Google Cloud Code - Duet AI): Como podemos ver em cinza, a sugestão de preenchimento automático é usar a função “ ” do Flask, que é a função básica de manipulação de arquivos do Flask. send_file Mas mesmo a documentação do Flask afirma “ ”, o que significa que ao inserir a entrada do usuário diretamente na função “send_file”, o usuário terá a capacidade de ler qualquer arquivo no sistema de arquivos, por exemplo, usando caracteres de passagem de diretório como “../” no nome do arquivo. Nunca passe caminhos de arquivo fornecidos por um usuário Implementação adequada: Substituir a função “send_file” pela função segura “send_from_directory” do Flask mitigará o risco de passagem de diretório. Ele só permitirá a busca de arquivos de um diretório específico codificado (“exportações” neste caso). Caso de uso: comparação de tokens secretos - malabarismo/coerção de tipos Em algumas linguagens de programação, como PHP e NodeJS, é possível fazer comparações entre dois tipos diferentes de variáveis e o programa fará uma nos bastidores para completar a ação. conversão de tipo Comparações vagas não verificam o tipo da variável nos bastidores e, portanto, são mais propensas a ataques de malabarismo de tipo. No entanto, o uso de comparações vagas não é considerado uma prática de codificação insegura por si só - depende do contexto do código. E, novamente, é difícil para os modelos de IA distinguir os casos. O exemplo mais comum de malabarismo de tipo PHP são comparações soltas de tokens/hashes secretos onde a entrada do usuário que é lida por meio de uma solicitação JSON (que permite o controle do tipo de entrada) atinge uma comparação vaga (“ =”). Sugestão de ferramenta de IA (Amazon CodeWhisperer): ” ) em vez de estrito (“ Ao que parece, o CodeWhisperer gera condições de comparação vagas em sugestões de preenchimento automático: A comparação frouxa do secret_token permite que um adversário ignore a comparação $data[“secret_token”] == “secret_token”. Ao enviar um objeto JSON com o valor “secret_token” sendo True (booleano), o (mesmo em versões mais recentes do PHP). resultado da comparação flexível também é True Mesmo ao “sugerir” a ferramenta (por meio de um comentário) para escrever o cheque “com segurança”, ela não gerou um trecho de código contendo uma comparação estrita - Implementação adequada: Somente ao especificar uma comparação estrita (“===”) estamos protegidos contra ataques de malabarismo de tipo. Caso de uso: Mecanismo de senha esquecida - Colisões de mapeamento de casos Unicode Vulnerabilidades no mecanismo “Esqueci minha senha” são muito comuns em aplicativos SaaS e foram a causa raiz de CVEs como (WordPress), (Django) e (GitLab). CVE-2017-8295 CVE-2019-19844 CVE-2023-7028 Especificamente, uma vulnerabilidade de colisão de mapeamento de caso Unicode ocorre quando a entrada do usuário está em letras maiúsculas ou minúsculas em uma expressão de comparação, enquanto seu valor original também é usado no código. Alguns caracteres diferentes resultarão no mesmo código de caracteres quando transformados. Por exemplo, “ß” e “SS” serão transformados em “0x00DF” quando maiúsculos. Esses casos são geralmente usados para contornar/enganar algumas verificações de segurança, enganando a verificação de comparação e posteriormente usando o valor original que é diferente do esperado. Esses casos são bastante difíceis de compreender, especialmente quando muitas implementações são possíveis. um mecanismo particularmente suscetível a esse tipo de vulnerabilidade é o . É comum normalizar a entrada do usuário em letras maiúsculas ou minúsculas (em endereços de e-mail ou nomes de domínio) ao realizar a verificação para evitar incompatibilidades indesejadas. Sugestão de ferramenta de IA (copiloto do GitHub): mecanismo de esquecimento de senha Por exemplo, dar uma olhada no código gerado pelo Copilot para a função de esquecimento de senha (abaixo) mostra que ele é vulnerável a esse problema. O código do Copilot primeiro procura o endereço de e-mail em letras minúsculas: Então, ao prosseguir com o resto do processo, sugere o seguinte: Como podemos ver, a sugestão de preenchimento automático gera uma senha aleatória e a envia para o e-mail do usuário. No entanto, inicialmente ele usa uma versão em letras minúsculas do e-mail do usuário para buscar a conta do usuário e, em seguida, continua a usar o e-mail do usuário original "no estado em que se encontra" (sem uma conversão em letras minúsculas) como destino do e-mail de recuperação. Por exemplo, digamos que o invasor seja dono da caixa de entrada de “miKe@example.com” (o “K” é um caractere unicode ) e use este e-mail para o mecanismo “Esqueci a senha”. Os e-mails “miKe@example.com” e “mike@example.com” são equivalentes 'no estado em que se encontram', mas após a conversão para minúsculas serão - do sinal Kelvin não Usar o endereço de e-mail “miKe@example.com” irá buscar as informações da conta de “mike@example.com”, mas a senha redefinida será enviada para a caixa de entrada do invasor (“miKe@example.com”), permitindo o controle do “ conta mike@example.com”. O endereço de e-mail usado para buscar a conta do usuário deve ser exatamente igual ao endereço de e-mail de recuperação (ou seja, o parâmetro send_email também usará email.lower()). Implementação adequada: Além disso, por precaução, recomenda-se utilizar o valor que já estava armazenado no servidor ao invés daquele fornecido pelo usuário. Caso de uso: Gerando arquivo de configuração - práticas de segurança inadequadas Outro tópico que pode confundir as ferramentas de preenchimento automático é a gravação de arquivos de configuração, especialmente para infraestruturas complicadas em nuvem. A principal razão é que de facto existem directrizes de melhores práticas de segurança, mas nem todos as seguem e, em alguns casos, é necessário ir contra elas. Esses exemplos de código podem chegar ao conjunto de dados de treinamento de IA. Como resultado, algumas saídas de preenchimento automático violarão as diretrizes de segurança recomendadas. No exemplo a seguir, criaremos um arquivo de configuração para um , a menor unidade de execução do Kubernetes. Pod do Kubernetes Neste exemplo, começamos a criar um arquivo de configuração do Pod. Sugestão de ferramenta AI (geração de código AI Blackbox): A sugestão cria a seção de recursos e adiciona um recurso de kernel Linux (SYS_ADMIN) ao contêiner que é essencialmente equivalente a executar o pod como usuário root. então é melhor omitir o securityContext? Não - já que muitos outros recursos serão adicionados por padrão, violando o . Implementação adequada: princípio do menor privilégio De acordo com , a configuração mais segura é primeiro descartar todos os recursos do kernel Linux e só depois adicionar aqueles necessários para o seu contêiner. as melhores práticas de segurança do Docker Para corrigir os problemas mencionados acima, a seção de recursos começa eliminando todos os recursos e, em seguida, adiciona gradualmente os necessários ao nosso contêiner. Caso de uso: Objetos de configuração – Inconsistências que levam à desserialização insegura Muitas vulnerabilidades exigem a definição de um objeto de configuração, que decide como o aplicativo tratará o componente necessário. O exemplo que escolhemos é a biblioteca de desserialização em C#, que pode ser usada de forma segura ou insegura dependendo da configuração do objeto JsonSerializerSettings e, especificamente, do . JSON da Newtonsoft TypeNameHandling Enquanto testamos o código de desserialização, percebemos que a definição do objeto de configuração é bastante aleatória e mudanças sutis podem levar à geração de um conjunto de configurações diferente. os exemplos a seguir exibem um comportamento inconsistente baseado apenas nos nomes dos métodos usados pelo desenvolvedor: Sugestões de ferramentas de IA (Amazon CodeWhisperer): Podemos ver duas configurações diferentes que resultam em desserialização JSON insegura devido à propriedade TypeNameHandling ser “Auto” e “ALL”. Essas propriedades permitem que o documento JSON defina a classe desserializada – permitindo que invasores desserializem classes arbitrárias. Isso pode ser facilmente aproveitado para execução remota de código, por exemplo, desserializando a classe “System.Diagnostics.Process”. A única diferença no código original do desenvolvedor são os nomes dos métodos. Implementação adequada: Por padrão, um objeto JsonSerializerSettings é instanciado com “TypeNameHandling = TypeNameHandling.None”, que é considerado seguro. Assim, usamos o construtor padrão de JsonSerializerSettings que resultará em um valor TypeNameHandling seguro. Semelhante ao primeiro caso de uso, onde examinamos o código vulnerável produzido pelo Copilot para busca de arquivos, aqui tentamos solicitar uma versão segura de um mecanismo de leitura de arquivos. Caso de uso de solicitação de código seguro: leitura de arquivos - evitando passagem de diretório Sugestão de ferramenta de IA (copiloto do GitHub): Podemos ver que a verificação de segurança é realmente ingênua, evitando apenas sequências ponto-ponto-barra que são necessárias para conduzir tentativas de passagem de diretório. O parâmetro path pode ser um caminho absoluto - apontando para qualquer caminho desejado no sistema (por exemplo, /etc/passwd), o que anula o propósito da verificação de segurança. Implementação adequada: Aqui, a função secure_file_read obtém um parâmetro de nome de arquivo (relativo) junto com um diretório (safe_dir) onde o nome do arquivo deve residir (e do qual não deve ser escapado). Ele cria um caminho absoluto juntando safe_dir e filename e prossegue para obter sua forma canônica chamando realpath. Em seguida, obtém a forma canônica da pasta safe_dir. Então, o conteúdo do arquivo será retornado somente se o caminho canônico começar com o safe_dir canônico. É um caso de uso comum aceitar apenas tipos de arquivo específicos para upload com base em sua extensão. Solicitação de código seguro - Upload de arquivo - Lista de extensões restritas No exemplo a seguir, pedimos ao autocompletador de IA para gerar uma função encarregada de filtrar extensões de arquivo perigosas. Sugestão de ferramenta de IA (geração de código de IA da Blackbox): O código Python sugerido pega o nome do arquivo, separa a extensão e compara-o com uma lista de extensões perigosas. À primeira vista, este trecho de código parece seguro. No entanto, proíbem nomes de arquivos que terminem com um ponto e, ao fornecer um nome de arquivo que termine com um ponto (“.”) durante a criação do arquivo, o ponto será descartado. Ou seja, o filtro gerado pode ser ignorado no Windows ao enviar um arquivo com uma extensão maliciosa seguida de um ponto (ex. “example.exe.”). as convenções de nomes de arquivos do Windows Normalmente, a melhor abordagem é usar uma , que verifica a extensão do arquivo apenas em relação às extensões permitidas. No entanto, como as ferramentas adotaram uma abordagem de lista negra (que é necessária em alguns casos), iremos delinear uma implementação de lista negra mais segura: Implementação adequada: lista de permissões No trecho a seguir, eliminamos todo o controle que o usuário tinha sobre a entrada, removendo a extensão de todos os caracteres não alfanuméricos e concatenando-a com um nome de arquivo recém-gerado. Co-programadores automáticos são ótimos para sugerir ideias, completar código automaticamente e acelerar o processo de desenvolvimento de software. Esperamos que essas ferramentas continuem evoluindo com o passar do tempo, mas por enquanto, conforme demonstrado com os casos de uso nesta postagem, as soluções de IA de preenchimento automático têm muitos desafios a serem superados até que possamos relaxar e confiar em seus resultados sem verificar novamente. insetos. Resumindo - use com cuidado Uma forma possível de reduzir a probabilidade de introdução de vulnerabilidades é solicitar deliberadamente à ferramenta de geração de código de IA para gerar código seguro. Embora isso possa ser útil em alguns casos de uso, não é infalível – a saída “aparentemente segura” ainda deve ser revisada manualmente por alguém proficiente em segurança de software. Para ajudar a proteger seu software, recomendamos revisar manualmente o código produzido por ferramentas geradoras de IA, bem como incorporar soluções de segurança, como Static Application Security Testing (SAST), que pode descobrir vulnerabilidades com segurança à medida que você avança. Conforme visto no exemplo acima, as ferramentas SAST podem alertar e capturar a colisão de mapeamento de caso Unicode descrita no caso de uso nº 3. Essa abordagem proativa é essencial para garantir a segurança do seu software. Recomendações da equipe de pesquisa de segurança da JFrog As descobertas e pesquisas da equipe de pesquisa de segurança desempenham um papel importante na melhoria dos recursos de segurança de software de aplicação da plataforma JFrog. Mantenha-se atualizado com a pesquisa de segurança JFrog Acompanhe as últimas descobertas e atualizações técnicas da equipe de pesquisa da JFrog Security em nosso e no X . site de pesquisa @JFrogSecurity