Reino: Input Validation and Representation

Problemas de validação e representação da entrada são causados por metacaracteres, codificações alternativas e representações numéricas. Confiar na entrada resulta em problemas de segurança. Os problemas incluem: “Buffer Overflows”, ataques de “Cross-Site Scripting”, “SQL Injection”, entre outros.

Buffer Overflow

Abstract
A gravação fora dos limites da memória alocada pode corromper dados, travar o programa ou provocar a execução de código mal-intencionado.
Explanation
O buffer overflow é provavelmente a forma mais conhecida de vulnerabilidade de segurança de software. A maioria dos desenvolvedores de software sabe o que é uma vulnerabilidade de buffer overflow, mas ataques de buffer overflow contra aplicativos legados e recém-desenvolvidos ainda são bastante comuns. Uma parte do problema deve-se à grande variedade de maneiras de como estouros de buffer podem ocorrer, enquanto outra parte deve-se às técnicas propensas a erros frequentemente utilizadas para impedir esses estouros.

Em uma exploração de buffer overflow clássica, o invasor envia dados a um programa, que ele armazena em um buffer de pilha de tamanho menor do que o normal. O resultado é que as informações na pilha de chamadas são substituídas, incluindo o apontador de retorno da função. Os dados definem o valor do apontador de retorno de forma que, quando a função é retornada, ela transfere o controle para o código mal-intencionado contido nos dados do invasor.

Embora esse tipo de buffer overflow de pilha ainda seja comum em algumas plataformas e comunidades de desenvolvimento, há vários outros tipos de buffer overflow, incluindo estouros de buffer de heap e erros "off-by-one", entre outros. Existem diversos livros excelentes que fornecem informações detalhadas sobre como ataques de buffer overflow funcionam, entre eles Building Secure Software [1], Writing Secure Code [2] e The Shellcoder's Handbook [3].

Em nível de código, vulnerabilidades de buffer overflow geralmente envolvem a violação das premissas do programador. Muitas funções de manipulação de memória em C e C++ não realizam verificações de limites e podem facilmente substituir os limites alocados dos buffers sob os quais elas operam. Até mesmo funções limitadas, como strncpy(), podem causar vulnerabilidades quando usadas incorretamente. A combinação entre manipulação de memória e suposições equivocadas sobre o tamanho ou a composição de um determinado dado é a causa raiz da maioria dos estouros de buffer.

Em geral, vulnerabilidades de buffer overflow ocorrem em um código que:

- Baseia-se em dados externos para controlar seu comportamento.

- Depende de propriedades dos dados que são aplicados fora do escopo imediato do código.

- É tão complexo que um programador não consegue prever seu comportamento com precisão.



Os exemplos a seguir demonstram todos esses três cenários.

Exemplo 1.a: O exemplo de código a seguir demonstra um buffer overflow simples que muitas vezes é causado pelo primeiro cenário, em que o código se baseia em dados externos para controlar seu comportamento. O código usa a função gets() para ler uma quantidade arbitrária de dados em um buffer de pilha. Como não há nenhuma maneira de limitar a quantidade de dados lida por essa função, a segurança do código depende de o usuário sempre inserir menos de BUFSIZE caracteres.


...
char buf[BUFSIZE];
gets(buf);
...
Exemplo 1.b: Este exemplo mostra como é fácil imitar o comportamento não seguro da função gets() em C++ usando o operador >> para ler a entrada em uma string char[].


...
char buf[BUFSIZE];
cin >> (buf);
...
Exemplo 2: O código neste exemplo também se baseia na entrada do usuário para controlar seu comportamento, mas adiciona um certo nível de desvio com o uso da função de cópia de memória limitada memcpy(). Essa função aceita um buffer de destino, um buffer de origem e o número de bytes a serem copiados. O buffer de entrada é preenchido por uma chamada limitada para read(), mas o usuário especifica o número de bytes que são copiados por memcpy().


...
char buf[64], in[MAX_SIZE];
printf("Enter buffer contents:\n");
read(0, in, MAX_SIZE-1);
printf("Bytes to copy:\n");
scanf("%d", &bytes);
memcpy(buf, in, bytes);
...


Observação: Esse tipo de vulnerabilidade de buffer overflow (em que um programa lê os dados e depois confia em um valor desses dados em operações de memória subsequentes nos dados restantes) apareceu com uma determinada frequência em bibliotecas de imagem e áudio e também em outras bibliotecas de processamento de arquivos.

Exemplo 3: Este é um exemplo do segundo cenário, no qual o código depende de propriedades dos dados que não são verificadas localmente. Neste exemplo, uma função denominada lccopy() usa uma string como seu argumento e retorna uma cópia alocada por heap dessa string com letras maiúsculas convertidas em minúsculas. A função não realiza verificações de limites em sua entrada, pois espera que str sempre seja menor que BUFSIZE. Se um invasor ignorar as verificações no código que chama lccopy() ou se uma mudança nesse código tornar inválida a suposição sobre o tamanho de str, lccopy() fará o estouro de buf com a chamada ilimitada para strcpy().


char *lccopy(const char *str) {
char buf[BUFSIZE];
char *p;

strcpy(buf, str);
for (p = buf; *p; p++) {
if (isupper(*p)) {
*p = tolower(*p);
}
}
return strdup(buf);
}
Exemplo 4: O código a seguir demonstra o terceiro cenário em que o código é tão complexo que seu comportamento não pode ser facilmente previsto. Esse código vem do popular decodificador de imagens libPNG, que é usado por uma ampla variedade de aplicativos.

O código parece realizar a verificação de limites com segurança, pois verifica o tamanho do comprimento de variáveis, que ele utiliza mais tarde para controlar a quantidade de dados copiada por png_crc_read(). No entanto, logo antes de testar o comprimento, o código realiza uma verificação em png_ptr->mode e, se essa verificação falhar, um aviso será emitido e o processamento continuará. Como length é testado em um bloco else if, length não poderá ser testado se a primeira verificação falhar e será usado às cegas na chamada para png_crc_read(), possivelmente permitindo um buffer overflow de pilha.

Embora o código nesse exemplo não seja o mais complexo que vimos até agora, ele demonstra por que a complexidade deve ser minimizada no código que realiza operações de memória.


if (!(png_ptr->mode & PNG_HAVE_PLTE)) {
/* Should be an error, but we can cope with it */
png_warning(png_ptr, "Missing PLTE before tRNS");
}
else if (length > (png_uint_32)png_ptr->num_palette) {
png_warning(png_ptr, "Incorrect tRNS chunk length");
png_crc_finish(png_ptr, length);
return;
}
...
png_crc_read(png_ptr, readbuf, (png_size_t)length);
Exemplo 5: Este exemplo também demonstra o terceiro cenário, no qual a complexidade do programa o expõe a estouros de buffer. Nesse caso, a exposição é decorrente da interface ambígua de uma das funções, e não da estrutura do código (como foi o caso no exemplo anterior).

A função getUserInfo() usa um nome de usuário especificado como uma string de vários bytes e um apontador para uma estrutura de informações do usuário e preenche essa estrutura com informações sobre o usuário. Como a autenticação do Windows usa Unicode para nomes de usuário, o argumento username é primeiro convertido de uma string de vários bytes em uma string Unicode. Em seguida, essa função transmite incorretamente o tamanho de unicodeUser em bytes em vez de em caracteres. Portanto, a chamada para MultiByteToWideChar() pode gravar caracteres com um comprimento de até (UNLEN+1)*sizeof(WCHAR), ou
(UNLEN+1)*sizeof(WCHAR)*sizeof(WCHAR) bytes, na matriz unicodeUser, que tem apenas (UNLEN+1)*sizeof(WCHAR) bytes alocados. Se a string username contiver mais de UNLEN caracteres, a chamada para MultiByteToWideChar() causará um estouro no buffer unicodeUser.


void getUserInfo(char *username, struct _USER_INFO_2 info){
WCHAR unicodeUser[UNLEN+1];
MultiByteToWideChar(CP_ACP, 0, username, -1,
unicodeUser, sizeof(unicodeUser));
NetUserGetInfo(NULL, unicodeUser, 2, (LPBYTE *)&info);
}
References
[1] J. Viega, G. McGraw Building Secure Software Addison-Wesley
[2] M. Howard, D. LeBlanc Writing Secure Code, Second Edition Microsoft Press
[3] J. Koziol et al. The Shellcoder's Handbook: Discovering and Exploiting Security Holes John Wiley & Sons
[4] About Strsafe.h Microsoft
desc.dataflow.cpp.buffer_overflow