Boa tarde, queridos leitores. Meu nome é Victor Burov, sou desenvolvedor do ISPsystem. No último post, falei sobre a
ferramenta para criação de autotestes , hoje vou compartilhar minha experiência na automação de testes de segurança.

Inicialmente, as vulnerabilidades nos produtos foram pesquisadas por um funcionário separado. O teste manual levou muito tempo e não garantiu que todas as vulnerabilidades fossem encontradas. Tendo verificado as leis básicas dos testes, chegamos à conclusão de que ele pode ser automatizado. Decidimos escrever um utilitário que facilitará a vida do testador, economize seu tempo e permita que você verifique os produtos após cada alteração. Como o testador foi chamado Lida, nomeamos o novo aplicativo em sua homenagem. Em geral, em nossa empresa, tornou-se uma tradição chamar as ferramentas de teste pelos nomes dos testadores.
Após analisar os utilitários de pesquisa de vulnerabilidades, cheguei à conclusão de que todos precisam especificar as funções a serem chamadas e os parâmetros usados. Decidimos novamente aproveitar a interface unificada e formulamos os requisitos para o Lida.
Requisitos de inicialização:
- Crie automaticamente listas de funções.
- Opções de preenchimento automático.
- Fazendo solicitações de API.
- Análise da saída de dados após a execução de funções.
- Pesquise vulnerabilidades nos dados.
- Formação de relatórios.
- Configurações flexíveis.
Perceber tudo isso não foi fácil.
Implementação
Ignorar formulários e listas
Para encontrar vulnerabilidades em uma função, ela deve ser executada passando os parâmetros necessários. Nossa interface é construída com base em listas e formulários, para que você possa automatizar a coleta de dados processando documentos xml que descrevem a estrutura dos elementos da interface.
Decidi iniciar o rastreamento no menu principal, entrando recursivamente em todas as listas aninhadas. "Lida" abre a lista do primeiro nível. Como regra, possui vários botões que invocam algumas funções.
Se o botão abrir o formulário, o resultado da chamada será um documento xml com nós contendo informações sobre os campos: nome, validador, intervalo de valores válidos. Com base nessas informações, os valores do campo são gerados. Por exemplo, um número será gerado para int. Se não houver validador, uma sequência aleatória será gerada. Após preencher todos os campos do formulário, uma solicitação é enviada.
Se a função for uma lista, ela será aberta e as funções associadas aos botões serão chamadas para seus elementos.
Ao verificar listas, surge um problema - todas as listas devem ter um conjunto de registros que garantam que todos os botões da lista sejam clicáveis.
Pesquisa por injeção SQL
A injeção de SQL é provavelmente um dos problemas mais comuns para aplicativos, incluindo o nosso. Muitas chamadas de função geram várias consultas ao DBMS. Ocorre um erro quando parâmetros que vêm de fora são substituídos no corpo da solicitação "como estão". As conseqüências de tais erros podem ser tristes: do recebimento não autorizado de dados à exclusão de tabelas.
Para iniciar a busca por injeções de SQL, organizei a saída de todas as consultas SQL para um arquivo. Depois que a função é executada, o aplicativo procura os valores dos parâmetros passados nas consultas SQL resultantes.
Você pode usar o log do próprio servidor SQL. Mas, no nosso caso, só existe um método para executar consultas e adicionar log a ele não foi difícil. Graças a isso, sabemos exatamente qual desafio gerou essa ou aquela solicitação.Quando o valor do parâmetro passado é encontrado, o utilitário passa um valor que contém uma aspas simples para esse parâmetro. Se a cotação for encontrada na mesma sequência, o parâmetro não será escapado - encontramos um local para injeção de SQL.
Análise de chamadas do sistema
Um problema semelhante ocorre quando fazemos chamadas do sistema. Decidi procurá-los com strace e selecionei os parâmetros de inicialização ideais para ele. Exemplo de inicialização do ISPmanager:
strace -f -s 1024 -e trace=file,process bin/core ispmgr
Como nas injeções de SQL, o Lida executa uma função e analisa a saída strace para
encontrar os valores dos parâmetros passados nas funções
open, unlink, rmdir, chmod chown, chflags, mknod, mkfifo, fcntl, symlink, link, execve, mkdir .
Se o parâmetro for encontrado, o utilitário passa um valor para ele, contendo, por exemplo, um caminho com uma transição para o diretório acima. Se ficar como está, será encontrada uma vulnerabilidade em potencial.
A análise da função execve acabou sendo muito útil. Ele permite determinar em quais argumentos da função para executar arquivos executáveis não são escapados. Este erro é muito caro, porque através dele você pode obter acesso root ao servidor simplesmente alterando a senha.
Quando os usuários encontram uma vulnerabilidade em nossos produtos, a empresa paga uma recompensa em dinheiro, cuja quantia depende da categoria de vulnerabilidade. Essa abordagem pode ser mais barata do que procurar erros por conta própria (o testador pode não encontrar o erro e receber um salário).
Outro teste interessante: verificar a ordem de chamar as funções stat e outras. Freqüentemente, o acesso é verificado primeiro por meio do stat e depois de algumas ações inseguras, o que deixa a oportunidade de substituição. Mas não automatizamos isso.Verificando o acesso a objetos estranhos
Verificamos o acesso a objetos estranhos sob o usuário para as entidades de outro usuário e administrador. Nesse modo, verificamos a capacidade de ler, modificar e visualizar as listas de elementos de um usuário estrangeiro.
O Lida ignora todas as funções disponíveis em nome do proprietário ou administrador e lembra seus elementos. Em seguida, chama as mesmas funções com elementos de outro usuário. Em uma situação ideal, a resposta deve ser um erro como Acesso ou Perdido. Se esse erro não for recebido, é muito provável que você possa ler os dados do usuário de outra pessoa.
Em alguns casos, a ausência de um erro não significa que você pode acessar diretamente os objetos de outros usuários. No início de tais funções, adicionamos uma verificação de permissões de elemento. Isso verifica não apenas a segurança, mas também a correção das respostas do servidor.
Validação de API
A validação da nossa API é um bônus adicional de verificação de vulnerabilidades nas funções. Analisando relatórios sobre o trabalho de Lida, aprendemos a retornar os tipos corretos de erros, tornamos nossa API mais conveniente e lógica. Como resultado, como no gravador, recebemos não apenas uma verificação de segurança, mas também verificamos novamente nossa API quanto à "consistência".
Dificuldades
Falsos positivos
O utilitário pode trabalhar com todos os nossos produtos, portanto, verifica muitas funções diferentes. Basicamente, os falsos positivos apareceram no modo de verificar o acesso a objetos estranhos. Isso ocorre devido aos recursos dos botões e funções.
Os falsos positivos foram o maior problema de Lida. Parecia que estava completamente depurado, mas ao verificar em painéis diferentes, surgiram novos falsos positivos. Como resultado, houve várias etapas de sua correção.
Criação de Entidades
A maioria das ações no painel é realizada em qualquer entidade (nome de domínio, banco de dados, etc.). Para alcançar o máximo de automação, a Lida precisou criar essas entidades automaticamente. Mas, na prática, isso foi difícil de implementar. Às vezes, a validação é realizada no código, portanto nem sempre é possível substituir um valor de parâmetro automaticamente. O segundo motivo são entidades dependentes. Por exemplo, para criar uma caixa de correio, você precisa criar um domínio de email.
Portanto, decidimos não lutar pela automação completa. Antes de iniciar, o testador cria entidades manualmente e tira uma captura instantânea da máquina, pois as entidades serão alteradas após a verificação. Isso permite que você não pule a verificação de um grupo de funções em caso de criação malsucedida de uma entidade.
Chamando funções destrutivas
Quase toda lista possui funções para excluir ou desativar uma entidade. Se você executá-los em sequência, a entidade será excluída antes de executar outras funções. Eu defino essas funções e realizo outras. Além disso, adicionou uma chave que proíbe a execução de tais funções.
Algumas funções reiniciam o servidor. Eles precisam ser rastreados e adicionados à lista de ignorados.
Devido à natureza da lógica operacional, algumas funções reiniciam o painel. Durante o teste de segurança, isso faz com que o painel inicie sem rastrear consultas SQL ou rastrear - a verificação adicional se torna sem sentido. Você precisa acompanhar isso e reiniciar o painel no modo de rastreamento.
Verificando parâmetros dependentes
Nos formulários, existem campos para inserir texto, caixas de seleção e listas suspensas, cujos valores dependem da disponibilidade de outros campos. Cada valor do campo dependente pode ter uma seção separada de código; portanto, as partes em que podem permanecer não verificadas.
Para resolver esse problema, adicionei algoritmos para analisar campos dependentes e verificar todas as combinações de controles dependentes.
Verificando os recursos não disponíveis na interface
As funções de serviço não estão disponíveis para transição, mas podem conter vulnerabilidades. Para identificá-los e verificá-los, temos uma função especial que retorna uma lista de todas as funções registradas. Comparamos esta lista com a lista de funções testadas. Não existem metadados para funções de serviço, portanto, é impossível verificar os parâmetros processados dentro
deles.Para de alguma forma verificar tais funções, passo neles nossos parâmetros padrão
elid, plid e outros.
Conclusão
Incluímos Lida em todas as noites na Jenkins. Com base nos resultados de seu trabalho, um relatório de verificação é gerado, informações sobre a função suspeita nele são exibidas com todos os parâmetros.
A tarefa encerrada pelo desenvolvedor agora é verificada não apenas pelo testador, mas também pela Lida. O testador processa o relatório recebido: copia os parâmetros da função suspeita no navegador e analisa o comportamento e o painel de log. Se a vulnerabilidade for confirmada, o erro será registrado no desenvolvedor.