Suas aplicações são seguras?

Conheça a Conviso!

Construindo analisadores de código

Com a preocupação em manter a qualidade do código, um bom desenvolvedor procura seguir padrões, mais conhecidos como boas práticas de programação, embora nem sempre essas boas práticas sejam aplicadas, elas ajudam a evitar problemas e de certa forma garantir um certo nível de segurança no produto final.

Conforme dito no parágrafo anterior, a ausência dessas boas práticas acabam trazendo problemas futuros que normalmente conhecemos como bugs. No caso específico desse post, vamos atentar aos bugs no contexto da segurança da aplicação, segue um programa em linguagem C com uma falha chamada Uncontrolled Format String:

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
 char *login=malloc(sizeof(char)*16);
 char *password=malloc(sizeof(char)*5);
 char *secret_pass="1337"; 

 puts("Por favor digite seu login:");
 scanf("%15s",login);
 getchar();

 puts("Por favor digite sua senha:"); 
 scanf("%4s",password);
 getchar();

 printf(login);
 puts("nTentativa de login...n");

 if(strcmp(secret_pass,password))
  puts("Erro senha errada");
 else
  puts("Logado !");

 free(password);
 password=NULL;
 free(login);
 login=NULL;


 return 0;
}

 Conforme visto no exemplo, a função printf() recebe uma variável de entrada chamada login sem nenhum tipo de tratamento e com o uso da função de forma incorreta, permitindo com isso que alguém possa inserir qualquer tipo de formato e até mesmo tentar identificar os últimos registros de dados na memória conforme podemos ver no exemplo abaixo, caso algum usuário tente inserir na entrada recebida pela variável login algo como:


Por favor digite eu login:
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
Por favor digite sua senha:
%s%s�"���%#��F#��|#���#���#���#���#��  �1�O1337%s%s%s%s%s%s%s%s%s%
Tentativa de login...
Erro senha errada

 Nesse caso, podemos ver através da sujeira que foi retornada “�1�O1337%” os dados armazenados em alguma outra variável que por coincidência nesse caso nos retorna uma senha, o que acaba sendo algo extremamente crítico. 

Atualmente um grande problema na análise de código fonte em um projeto é o volume de códigos que acaba dificultando o tempo de análise, sendo que um fator crucial é o tempo de entrega e em muitos casos, essa relação fator x entrega acaba afetando a análise do código, perdendo assim a qualidade da mesma. Normalmente, para resolver esse caso, são utilizadas soluções que automatizam essa tarefa de análise de código. 

Assim como corretores ortográficos em software de textos, bem como Open Office. Analistas fazem uso de recursos para tentar automatizar a identificação de problemas, um deles análise estática,  que faz uso de técnicas para avaliar o código-fonte sem executá-lo, apontando assim possíveis problemas, meio á tantos falsos/positivos, por fim cabe somente ao analista dizer o que realmente é um “problema“. 

 
 

Para se construir um analisador estático do zero, precisamos fazer um Parser para abstração de todas as palavras em um código fonte, tratando funções especificas da linguagem  int, if, while e for  e inserindo essas palavras em uma  AST (árvore de sintaxe abstrata)  que permite que sejam criadas buscas de acordo com casos que não obedeçam boas práticas e sejam geradores de problemas como por exemplo,  format string, memory leak, code injection, etc.

Vale a pena lembrar que os procedimentos utilizados para se construir um analisador de código, corretor ortográfico e tantos outros são semelhantes aos utilizados para construir um compilador, ou seja, a abstração do código fonte é feita utilizando um analisador léxico que separa o conteudo em cadeias que chamamos de tokens  para que seja feita logo após uma análise sintática que verifica se esses tokens realmente fazem parte da gramática da linguagem e alimentam uma árvore de derivação que servirá para  análise semântica que auxilia na validação da informação para por fim gerar o código final. 

O analisador estático normalmente é específico para uma determinada linguagem de programação, o que já é o caso de muitos já existentes como Clang, splint, flawfinder, cppcheck. Devido a essa dependência, uma solução genérica para muitas linguagens é algo que parece impossível, porém, uma solução muito comum é a de modularização, que permite escrever regras de análise para uma linguagem especifica. 

Existem outras formas de se fazer um analisador de códigos, logo abaixo temos um exemplo de um caso específico. 

Em um código fonte de uma linguagem qualquer, encontramos uma função open(), no contexto comum abre um arquivo para leitura e escríta, entretanto a função close() não foi usadaindícios de estudos mostram que isso não é boa práticasendo evidente que podem ocorrer alguns problemas como por exemplo uma possível condição de corrida ( race conditions )…    

Para a construção do analisador, será utilizada para identificar o padrão de código um  Autômato Finito Determinístico que será responsável pela correlação do código. O código pode ser encontrado em:

https://github.com/convisoappsec/Mosca/blob/master/test_analysis/novotest.c


Como exemplo, utilizaremos um código PHP qualquer no seguinte caminho  /home/user/area_test/test.php, obviamente ao compilar nosso programa teremos como saída:


<?php

 $one = array("10", "20", "30", "40");
 $two = array("a", "b", "c", "d");

 $i=0;

 while($i < count($one)) {
    reset($two);
    while($a = each($two)) {
        echo $a[1]." - ".$one[$i].", n"; 
    }
    $i++;
 
 }
 $file = fopen('config.php', 'w');

 $output="bingo"; 

 fwrite($file, $output);
...
===================
OPEN function at line 16
Have BAD practice here
 

Perfeito atingimos nosso objetivo de automação para análise em um caso específico. Lembrando que nosso analisador também funciona em linguagens como C, Perl, Python.  Esse assunto não acaba por aqui.  

Caso você leitor queira saber ou pesquisar mais sobre o assunto, estou desenvolvendo um software pessoal para auxiliar na análise de códigos. É um projeto Open Source que tem a intenção de ser um compêndio de analisadores.

Projeto se chama Mosca e pode ser encontrado aqui, ele usa módulos chamados eggs, cada egg seria um arquivo com extensão “.egg” com regras, cada regra usa seguinte estrutura: 
 

 


 ::Title::( Possible Uncontrolled Format string with printf() ):: 
 ::Description::( when you use printf() )::
 ::Relevance::( High ):: 
 ::Reference::( URL_reference )::
 ::Match::( printf([a-zA-Z].* ):: 
 

Essa regra, detecta aquele Bug, bem no começo do nosso artigo Uncontrolled Format string, cada regra é agrupada com algumas informações “Título, Descrição, Referência…“. Entretanto o campo mais importante seria Match do qual seria a expressão regular para identificar certo padrão. Com cada egg, usuários poderiam criar regras para PHP, ASP, JAVA, e também qualquer outra linguagem, entretanto o projeto caminha para algo mais complexo… 

Fica uma frase do grande mestre Confúcio, para muitos frase de biscoito da sorte mas faz todo sentido pois tem uma grande relação com o artigo  :

“Transportai um punhado de terra todos os dias e fareis uma montanha.”

Algumas idéias que devem ser implementadas no futuro sâo:

  • Melhoria de código;
  • Enumerar mais casos e implementar com o uso de autômatos;
  • Carregar os analisadores usando a função dlopen, facilitando a utilização de plugins;
  • Ter um HTTPd próprio com a utilização da libmongoose;
  • Documentação de código utilizando Doxygen;
  • Usar Datatables e view com syntax highlight semelhante ao RIPS.

Para concluir, existe um grande campo de pesquisa na área de analisadores, pois é uma área que ainda possuí muitos problemas e que através de pesquisa e desenvolvimento pode ser muito melhorada e trazer bons resultados. 

Fica algumas referências:

http://dragonbook.stanford.edu/

https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard

Originalmente postado no Blog da Conviso Application Security – Siga-nos no Twitter @conviso Google+

Tags

Deixe um comentário

topo
%d blogueiros gostam disto: