TL;DR
Modelos de raciocínio não são necessários para a maioria das regras SAST, mas para casos extremos como path traversal em JavaScript, eles detectam o dobro de falsos positivos.
Modelos de Raciocínio São Apenas Marketing?
“Modelos de Raciocínio” estão em alta. Os grandes laboratórios de IA estão em uma batalha pela liderança; impulsionando os limites de tamanho e desempenho de modelos através de leis de escala, pré-treinamento mais inteligente e ajuste fino com RLHF (Aprendizagem por Reforço a partir de Feedback Humano). Eles também estão adicionando o prompting de cadeia de pensamento para fazer os modelos “pensarem em voz alta” durante a inferência. Isso os permite dominar sistemas não-IA em tarefas de lógica - e liderar os rankings enquanto o fazem.
Isso é impressionante. Mas eles são realmente úteis na prática? Depende.
No caso do AutoTriage*, muito depende da complexidade da regra SAST**.
*Breve resumo - AutoTriage é um recurso que o Aikido usa para filtrar falsos positivos de SAST.
**Um achado SAST é uma vulnerabilidade potencial descoberta no código-fonte, conforme relatado por um detector de padrões hardcoded.
Excessivamente Caro para a Maioria das Regras SAST
O AutoTriage funciona em duas etapas: primeiro, tentamos descartar a possibilidade de explorabilidade. Se isso for possível, podemos filtrar um falso positivo. Isso exige um pensamento binário sobre a alcançabilidade de variáveis controladas pelo usuário em vulnerabilidades. O modelo basicamente verifica se a variável é realmente controlada pelo usuário e se há sanitização/validação/casting em vigor. E se houver alguma forma de mitigação, ele verifica se ela é realmente eficaz.
A segunda etapa só acontece quando não conseguimos descartar a possibilidade de explorabilidade na primeira etapa. Então, focaremos na priorização. A prioridade é definida pela probabilidade de algo dar errado e pela severidade caso isso ocorra. Esta segunda etapa é menos binária, mas também depende de estimativas subjetivas – por exemplo, uma variável ser controlada pelo usuário quando nos falta contexto completo.
Para a maioria das regras, podemos resumir como fazer isso em um número razoável de 'regras de ouro', tornando os modelos de raciocínio um exagero: eles tendem a ter uma precisão semelhante, mas vêm com um custo significativamente mais alto.
Por Que Pequenos Modelos de Raciocínio Funcionam
Algumas regras são surpreendentemente complexas e modelos não-racionais têm dificuldade em compreendê-las. Imagine usar o mesmo espaço mental para cada palavra que você diz: inicialmente alguém pergunta: “quanto é 1+1?” Então, essa mesma pessoa pergunta “quanto é 26248 + 346237?” Enquanto modelos normais lutam com níveis variados de complexidade, modelos de raciocínio podem lidar com eles simplesmente usando mais palavras para entradas complexas e dividindo problemas maiores em subproblemas menores e mais gerenciáveis.
Infelizmente, por consumirem mais tokens, eles geralmente também são mais caros. No entanto, modelos estruturados como modelos de raciocínio sofrem menos com a redução de tamanho do modelo do que modelos não-racionais. Existem duas razões para optar por modelos maiores: (1) eles têm mais capacidade para armazenar mais conhecimento (mas ter muito conhecimento não é realmente necessário no caso de triagem de vulnerabilidades). (2) Modelos maiores tendem a ser um pouco mais precisos por palavra. No entanto, modelos de raciocínio podem se recuperar de erros, graças à sua estrutura de raciocínio. Assim, apesar de consumir mais tokens, é viável na prática trabalhar com modelos menores com um custo por token mais baixo para compensar o maior uso de tokens.
Path Traversal em Javascript
Path traversal é uma regra onde os modelos de raciocínio podem realmente brilhar, porque são surpreendentemente complexos de triar. Path traversal é uma vulnerabilidade onde usuários finais podem ler ou escrever arquivos fora de um diretório pretendido. Por exemplo, imagine que o Google Drive teria uma pasta dedicada a cada usuário separadamente em um sistema de arquivos como este:
Google Drive/userId1/
Google Drive/userId2/…Da próxima vez que você quiser baixar um de seus arquivos, você envia uma requisição GET do seu cliente de navegador para o Google Drive, por exemplo, com o nome de arquivo meuCachorroComendoSapatos.jpg. Mas e se você tentasse o seguinte nome de arquivo: ../userId2/minhasSenhas.txt. Se o Google Drive não tivesse protegido seu back-end contra path traversal, então você poderia baixar um arquivo ‘minhasSenhas.txt’ de outro usuário, se esse arquivo existisse.
Diferentes Ataques de Path Traversal
Para triar achados SAST de path traversal, precisamos entender diferentes casos em que algo é vulnerável ou não. Vamos começar com os casos mais simples e gradualmente aumentar a complexidade.
Padrão 1: ‘../’
O problema principal aqui é o padrão ‘../’. Se você lê ou escreve em um caminho de arquivo com ‘../’ nele, ele pode escapar do diretório pretendido e ler/escrever em um local que você não pretendia. Então, se não houver verificação para ‘../’ no caminho do arquivo e o arquivo for especificado no lado do cliente, você tem uma vulnerabilidade real. Nos casos realmente ruins, hackers poderiam ler arquivos contendo credenciais em seu sistema.
Padrão 2: ‘..\\’
Imagine que você verificou por ‘../’, mas o código está rodando em um sistema Windows. Você teria um problema novamente, já que o path traversal ainda é possível com padrões ‘..\\’. Até agora, tudo bem, duas regras de ouro para verificar ainda são gerenciáveis, certo?
Padrão 3: ‘..’
Para obter caminhos limpos e organizados sem barras ausentes, muitas pessoas usam funções como path.resolve() ou path.join(). É aqui que a diversão começa. Imagine algo assim:
if (userControlledSubPath.includes(‘../’) || userControlledSubPath.includes(‘..\\’)|| filename.includes(‘../’) || filename.includes(‘..\\’))
{
throw new Error(‘Path traversal attempt detected);
}
const filepath = path.join(HARDCODED_BASE_PATH, userControlledSubPath, filename);
return fs.readFileSync(filepath);Acontece que isso ainda é vulnerável: se um atacante usar userControlledSubPath === '..', o path.join ainda o interpretará como subindo um diretório.
No entanto, o último argumento em path.join() é imune a esse ataque. Se um atacante especificasse o ‘..’ no último argumento, a path.join() função retornaria um diretório em vez de um caminho de arquivo (filepath), o que resultaria em uma operação de leitura/escrita inválida.
Padrão 4: ‘/*’
Em um novo exemplo, tivemos novamente um teste como este:
if (filename.includes(‘..’))
{
throw new Error(‘Path traversal attempt detected);
}
const filepath = path.resolve(HARDCODED_BASE_PATH, filename);
return fs.readFileSync(filepath);Isso parece seguro, certo? A verificação cobre os casos ‘..’, ‘../’ e ‘..\\’ - é elegante! Agora vem a maneira surpreendente de como isso ainda é vulnerável. Rufar de tambores… trrrrrrrrr… Quando um argumento em path.resolve() começa com uma barra, ele ignora todos os argumentos anteriores. Então, quando um atacante faz algo como filename = /etc/passwd, então path.resolve() ignorar o caminho base (base path) codificado e resolver para /etc/passwd. Assustador, certo? Deveríamos ter verificado também a barra final. Note que usar path.join() o teria tornado seguro.
Apreciando a Complexidade
Charlie Chaplin disse uma vez ‘A simplicidade não é uma coisa simples’. Isso se aplica aqui também: existe uma remediação simples e eficaz, mas primeiro você precisa entender a gama de possíveis vetores de ataque. A remediação mais simples e robusta contra path traversal é primeiro resolver o caminho e verificar se ele ainda começa com o caminho base (base path) pretendido. Não há como escapar dessa verificação.
No entanto, a equipe AutoTriage não tem o luxo de poder escolher a remediação. Precisamos ser capazes de marcar soluções alternativas como seguras para não sobrecarregar desnecessariamente os clientes com falsos positivos. Vimos agora 4 vetores de ataque diferentes de path traversal e todos eles vêm com verificações específicas. Para cada um desses vetores de ataque, o LLM precisa verificar se ele pode ocorrer com todos os requisitos para realizar um ataque bem-sucedido ou para descartar qualquer possibilidade de ataque.
Apesar de os modelos de raciocínio não serem o padrão para a maioria das regras, eles são capazes de filtrar com segurança o dobro de falsos positivos em comparação com modelos não-racionais para path traversal em JavaScript. Isso é um divisor de águas para a redução de ruído.

