segurança de código

30
SEGURANÇA DE CÓDIGO Código Seguro

Upload: wanderlei-silva-do-carmo

Post on 13-Apr-2017

264 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Segurança de código

SEGURANÇA DE CÓDIGO Código Seguro

Page 2: Segurança de código

Extraído do documento acessível em http://www.las.ic.unicamp.br/paulo/cursos/segc/mat.extra/isc-

codigo.seguro.pdf e reproduzido em apresentação Powerpoint por Wanderlei Silva do Carmo

[email protected].

Todos os direitos e créditos são do(s) autor(es) do artigo e conteúdos correlatos.

Page 3: Segurança de código

INTRODUÇÃO

“...É preciso entender desde o início que segurança de software é mais do quesimplesmente a escrita de um código seguro. No cenário atual de segurança, asconsiderações devem ir além da mera funcionalidade para levar em conta tambémrequisitos de segurança.”

“... O desenvolvedor de software deve primeiramente entender como o seu códigopode ser explorado (código inseguro) para depois usar esse conhecimento na escritade códigos que não fiquem sujeitos à exploração (código seguro). Assim como notratamento de doenças, o médico deve primeiramente diagnosticar o problemacentral antes de tratar os sintomas, ao desenvolver um software resistente a hackers épreciso entender, antes de qualquer coisa, o que constitui um código inseguro, paradepois abordar suas possíveis vulnerabilidades.”

Page 4: Segurança de código

FONTES DE LEITURA

A série Hacking Exposed (Hacking Revelado), 19 Deadly Sins of Software Security(19 Pecados Mortais em Segurança de Software), Exploiting Software (ExplorandoSoftware), Building Secure Software (Construindo um Software Seguro) e WritingSecure Code (Escrevendo Códigos Seguros). Embora essas fontes sejam consideradasde leitura obrigatória para todos os desenvolvedores de software, outras fontes comoa Chronology of Data Breaches (Cronologia das Quebras de Segurança de Dados) elistas de bugs de segurança e de divulgação completa mostram evidências de que osaplicativos de software produzidos nos dias de hoje ainda contêm um grande númerode vulnerabilidades.”

Page 5: Segurança de código

QUEM É RESPONSÁVEL PELA (IN)SEGURANÇA NO CÓDIGO

“A insegurança de software deve ser atribuída a todos os envolvidos no ciclo dedesenvolvimento do software, e os desenvolvedores – aqueles que escrevem o código– podem representar um papel vital no desenvolvimento de softwares seguros.”

Page 6: Segurança de código

O QUE É CÓDIGO INSEGURO?

Como diz o provérbio chinês, toda longa jornada começa com o primeiro passo. Ajornada para desenvolver softwares seguros começa com o primeiro passo aoidentificar o que torna um código inseguro. Portanto, o que é código inseguro? Códigoinseguro é aquele vulnerável a ataques de segurança. Para facilitar a vida do leitor,a palavra “insecure” (inseguro em inglês) pode ser usada como um acróstico paradescrever estruturas de programação e códigos que são vulneráveis a quebras desegurança. A lista a seguir não tem a intenção de ser exaustiva no que se refereàquilo que constitui um código inseguro, mas sim uma compilação das estruturas deprogramação mais comumente observadas que tornam o software inseguro

Page 7: Segurança de código

A injeção do código malicioso permite que os dados

fornecidos pelo usuário mal intencionado sejam

executados como código. Há diversos tipos de ataques

de injection: contra bancos de dados (por exemplo, SQL

Injection), contra estruturas de diretórios (por exemplo,

LDAP Injection) e até contra o próprio sistema

operacional (por exemplo, comandos no sistema

operacional injection). Validação ou filtragem

inadequada pode resultar em ataques de injection.

Injeção de Código Malicioso

Page 8: Segurança de código

A falta de validação de dados fornecidos pelo usuário é a

principal razão que possibilita os ataques de injection,

resultando em graves consequências. Brechas como a

divulgação de informações sensíveis (quebra de

confidencialidade), adulteração e manipulação de estruturas

e árvores de diretório (quebra de integridade), DoS (Denial-

of-Service – quebra de disponibilidade), burla da

verificação de autenticação e autorização (check bypass), e

mesmo a execução de código remoto tornam-se possíveis.

Outra possibilidade que não deve ser omitida é a de

estruturas de código que dependem de dados fornecidos

pelo usuário para montar dinamicamente as queries a serem

executadas dinamicamente no backend. Há diversas técnicas

de codificação defensiva contra ataques de injection. Uma

opção é o saneamento das entradas de dados, que pode ser

conseguido pela restrição a um determinado conjunto de

entradas válidas ou pela proibição e remoção de quaisquer

padrões e caracteres de entrada inválidos

Injeção de Código Malicioso

Page 9: Segurança de código

Uma segunda opção é o uso de queries parametrizadas,

quer dizer, que não sejam geradas dinamicamente. Elas usam

os dados fornecidos pelo usuário como parâmetros. Quando

aplicadas com a arquitetura correta, podem ajudar também

no desempenho. Os padrões de codificação não devem

admitir a construção dinâmica de queries no código com

vistas a reduzir a possibilidade de ataques de código

inoculável e isso deve ser definido como obrigatório.

O artigo “All Input Data is Evil – So Make Sure You Handle It

Correctly and with Due Care” (“Todos os dados de entrada

são maldosos – então, certifique-se de tratá-los corretamente

e com o devido cuidado”) escrito por Dino Esposito e

publicado na revista CoDe é uma boa referência e, conforme

sugerido apropriadamente pela segunda metade do título, é

extremamente importante ser capaz de identificar ataques

de injection e tomar as providências necessárias (devido

cuidado) para tratá-los.

Injeção de Código Malicioso

Page 10: Segurança de código

Em um mundo altamente interconectado e móvel, é

imperativo que a autenticidade da origem do código e de

transações comerciais e administrativas críticas seja

incontestável. O não repúdio é a garantia que a origem do

código é aquela que ele diz ter. É a capacidade de

verificação da autenticidade da origem do código. Isso é

especialmente importante, visto que os códigos podem ser

transferidos a partir de um local remoto e executados no

sistema local. O assim chamado código “móvel”, deve conter

prova de sua origem para verificação de autenticidade, o

que pode ser conseguido por meio de códigos com

assinatura digital. A assinatura digital é o processo pelo qual

os códigos (executáveis, scripts etc.) são digitalmente

marcados para assegurar que não tenham sido adulterados

e que são originados de um editor válido. A assinatura

digital é conhecida também como embalagem shrinkwrap

digital.

N – Ausência de mecanismos de Não Repúdio

Page 11: Segurança de código

O não repúdio garante também que as ações tomadas pelo

código não possam ser recusadas. A funcionalidade de

auditoria de código é um mecanismo para garantir que o

repúdio não seja possível. Como mínimo absoluto, para

transações comerciais e administrativas críticas, o código

deve ser escrito de forma a registrar as ações tomadas,

incluindo data/hora e outros detalhes pertinentes, como o

usuário ou o processo que está realizando a ação.

N – Ausência de mecanismos de Não Repúdio

Page 12: Segurança de código

O não repúdio garante também que as ações tomadas pelo

código não possam ser recusadas. A funcionalidade de

auditoria de cSpoofing é o ato de personificar outro usuário

ou processo. Código sujeito a spoofing é aquele que permite

ataques de spoofing. Ataques de spoofing permitem que o

imitador realize ações com o mesmo nível de confiança que o

usuário válido que está sendo imitado. Revelação acidental

de informações, adulteração de dados, esgotamento de

recursos, burla da autenticação, contorno de verificações de

autenticidade e exclusão ou manipulação de registros de

auditoria são todas possíveis consequências do spoofing. O

spoofing foi observado em casos onde a base do código não

era segmentada para ser executada sob diferentes

contextos de execução dependentes do nível de confiança

(privilégio) do chamador do código. Isso viola o princípio do

“mínimo mecanismo comum” que afirma que os mecanismos

comuns a diversos usuários/processos não devem ser

compartilhados.

S – Código sujeito a Spoofing

Page 13: Segurança de código

Com somente um contexto de execução para todo o código, um agressor

poderia imitar uma identidade e executar o código como se fosse um

usuário válido com permissão. Identificadores de sessão previsíveis,

senhas gravadas, credenciais em cache e permissão de imitação de

identidades são vulnerabilidades de codificação comuns que podem

resultar em ataques de spoofing. A figura 1 mostra um exemplo de

configuração que possibilita a personificação de um usuário específico.

Além do fato dessa personificação ser permitida, o nome de usuário e a

senha estão gravados na linha de código em texto puro, o que também

não é recomendável. A figura 2 é um exemplo que ilustra como a

identidade de um usuário autenticado é imitada em código. Códigos

sujeitos a spoofing podem resultar em diversos comprometimentos da

segurança, sendo mais comum o sequestro e reprodução de sessões. Um

agressor pode imitar a identidade de um usuário válido e assumir o

controle de uma sessão já estabelecida entre o cliente e o servidor e

depois reproduzir a ação. Caso haja alguma razão comercial válida

para permitir a personificação, tais ações deverão ser detalhadamente

monitoradas e auditadas. Devem ser tomadas precauções para garantir

que o código não permita ataques de personificação e spoofing.

S – Código sujeito a Spoofing

Page 14: Segurança de código
Page 15: Segurança de código

Qualquer desenvolvedor de software entende que é muito difícil fazer

um código livre de erros. Exceções e erros são inevitáveis. Entretanto, não

tratar exceções e erros ou tratá-los inadequadamente são opções

inaceitáveis quando se trata de segurança de software. Códigos que

revelam detalhes explícitos são um exemplo de tratamento inadequado

de exceções e erros. Um exemplo simples de erro explícito é a mensagem

“Nome de usuário inválido” durante uma tentativa de autenticação.

Mesmo uma mensagem simples como essa contém mais informações do

que é necessário. Uma mensagem como “Login inválido” seria suficiente.

Essa mensagem de erro não explícita deixa o agressor

E – Tratamento inadequado de Exceções e Erros

Page 16: Segurança de código

Qualquer desenvolvedor de software entende que é muito difícil fazer

um código livre de erros. Exceções e erros são inevitáveis. Entretanto, não

tratar exceções e erros ou tratá-los inadequadamente são opções

inaceitáveis quando se trata de segurança de software. Códigos que

revelam detalhes explícitos são um exemplo de tratamento inadequado

de exceções e erros. Um exemplo simples de erro explícito é a mensagem

“Nome de usuário inválido” durante uma tentativa de autenticação.

Mesmo uma mensagem simples como essa contém mais informações do

que é necessário. Uma mensagem como “Login inválido” seria suficiente.

Essa mensagem de erro não explícita deixa o agressor em dúvida se

inválido é o nome de usuário ou a senha, diferente do caso anterior onde

o agressor sabe que é o nome de usuário. Mensagens de erro não

explícitas que não mostram detalhes abertos da exceção aumentam

consideravelmente o trabalho do agressor que quiser tentar entrar no

sistema. No entanto, isso pode ter um efeito negativo na solução de

problemas e suporte ao usuário.

E – Tratamento inadequado de Exceções e Erros

Page 17: Segurança de código

Desta forma, as considerações de projeto devem ser ponderadas de

maneira que o software seja adequadamente explicito nas mensagens,

mas sem revelar detalhes. O tratamento inadequado de exceções e erros

pode resultar na revelação da arquitetura interna, do fluxo, tipo e

valores de dados e dos caminhos do código. Durante um exercício de

reconhecimento, um agressor pode usar as informações coletadas de uma

mensagem de erro explícita ou dos detalhes de uma exceção para

levantar o perfil do software. A figura 3 ilustra como uma exceção não

tratada pode revelar a existência de uma conta de login chamada

SecuRiskLabUser, além de revelar detalhes do stack da exceção,

fornecendo informações ao agressor que podem ser usadas para

levantar o perfil do software. A ausência de uma rotina abrangente de

tratamento de exceções e o mero repasse das informações de exceção

brutas para o front-end ou cliente é outro exemplo de tratamento

inadequado de exceções ou erros. Quando uma exceção ocorre, o

código deve tratá-la explicitamente. Além do mais, os ativos não devem

ser postos em risco em caso de falha, o que é um princípio de proteção

contra falhas (fail-safe ou fail-secure em inglês). As decisões devem ser

baseadas em permissões explícitas e não em exclusões.

E – Tratamento inadequado de Exceções e Erros

Page 18: Segurança de código

É importante garantir que erros e exceções sejam tratados de forma não explícita, sem revelar

mais informações do que o necessário e sem violar os princípios de proteção contra falhas.

Page 19: Segurança de código

Desenvolvedores são, essencialmente, os solucionadores de problemas

criativos, são aqueles que utilizam suas habilidades e conhecimento

tecnológico na criação de soluções para problemas e necessidades de

negócios. Os desenvolvedores buscam o aperfeiçoamento das

funcionalidades existentes. Infelizmente, sabese que isso também pode

ser um tiro pela culatra, especialmente no contexto da criptografia,

conforme evidenciado por diversas implementações medíocres de

criptografia personalizada observadas em revisões de código. Tais

revisões revelam que as funcionalidades de criptografia no código são,

na maioria das vezes, desenvolvidas internamente em vez de serem

aperfeiçoadas a partir de algoritmos existentes de criptografia

comprovados e validados. Isso contradiz o princípio de projeto seguro ao

“promover os componentes existentes” para minimizar a exposição a

ataques.

C – Código com Criptografia vulnerável

Page 20: Segurança de código

Além disso, mesmo quando algoritmos de criptografia comprovados são

utilizados, os detalhes da implementação permanecem inseguros. Os

algoritmos de criptografia utilizam um valor secreto, conhecido como

chave, para criptografar (converter texto puro em texto cifrado) e

descriptografar (converter texto cifrado em texto puro). A essência da

força de uma implementação de criptografia não está necessariamente

na robustez do algoritmo em si, mas na forma como a chave é derivada

e tratada. O uso de números não aleatórios para derivar a chave de

criptografia torna a proteção criptográfica fraca e ineficaz. Algumas

vezes, senhas em código ASCII não aleatórias e fáceis de adivinhar são

empregadas na derivação da chave de criptografia, o que deve ser

evitado. Outro problema comum é que as chaves não são armazenadas

de forma segura. Foram observadas chaves codificadas diretamente nas

linhas de código. Isso é a mesma coisa que trancar a porta e deixar a

chave na fechadura, proporcionando uma proteção mínima, se é que

proporciona alguma proteção. Deve-se dar atenção especial ao escolher

o algoritmo, verificando o histórico de robustez. Uma vez escolhido, deve-

se usar Geradores de Números Aleatórios (GNA) e Geradores de

Números Pseudo-Aleatórios (GNPA) para derivar a chave de criptografia

para codificação e decodificação. As chaves derivadas devem ser

armazenadas de forma segura.

C – Código com Criptografia vulnerável

Page 21: Segurança de código

As funções inseguras são consideradas inerentemente perigosas. Essas

funções são aquelas desenvolvidas sem necessariamente levar em

consideração as implicações de segurança. O uso de tais funções não

garante ao programador nenhuma proteção de segurança. Pode resultar

em vulnerabilidades que permitiriam que um potencial agressor

corrompesse a memória do sistema e/ ou conseguisse controle total sobre

o sistema. Uma das razões atribuídas aos notórios ataques de buffer

overrun é o uso de funções inseguras no código. Diversos boletins e

atualizações de segurança que abordam essas funções já foram

publicados. Funções inseguras são encontradas predominantemente em

sistemas-legado e linguagens de programação de gerações anteriores.

Dois exemplos comuns em linguagem C são as funções strcpy e strcat.

Como essas funções não realizam verificações de comprimento/tamanho

(também conhecidas como verificações de limites), um agressor poderia

fornecer dados de entrada de tamanho arbitrário, excedendo a

capacidade dos buffers de memória.

U – Funções e rotinas inseguras/não Utilizadas no código

Page 22: Segurança de código

A figura 4 mostra um exemplo da função insegura strcpy em linguagem

C, sendo utilizada para copiar os dados de entrada para um buffer de

memória local. Se não houver uma verificação de limites e os dados de

entrada fornecidos pelo usuário contiverem mais caracteres do que a

capacidade do buffer de memória local, o resultado será um buffer

overflow na memória local.

U – Funções e rotinas inseguras/não Utilizadas no código

Page 23: Segurança de código

Atualmente, os editores de linguagens de programação estão evitando

funções inseguras em favor de alternativas mais seguras, como as funções

strncpy e strncat que permitem a verificação de comprimento/tamanho

pelo desenvolvedor. Outra estrutura insegura de codificação, observada

em revisões de código, é a presença de funções não utilizadas,

representadas por trechos de código redundantes que não são mais

utilizados para tratar nenhuma funcionalidade. Mudanças nos negócios,

avanços tecnológicos e o receio de causar incompatibilidade com versões

anteriores são algumas das razões para que funções não utilizadas

sejam deixadas no código. Isso aumenta a exposição relativa do código

a ataques.

U – Funções e rotinas inseguras/não Utilizadas no código

Page 24: Segurança de código

“Easter eggs” é outro exemplo clássico de funções não utilizadas. Em

software, um “Easter eggs” é um código oculto que pode causar uma

alteração de comportamento do programa (por exemplo, a exibição de

uma mensagem, a reprodução de um som etc.) quando determinadas

condições são satisfeitas (por exemplo, uma sequência de cliques do

mouse ou de teclas etc.). Normalmente, o “Easter eggs” é inócuo, mas

também aumentam a exposição do código a ataques e, portanto, podem

sofrer consequências de segurança potencialmente graves. O risco de

perder clientes, de introduzir novos bugs e de impactos no desempenho

que podem resultar em esgotamento de recursos, ou seja, impacto na

disponibilidade (juntamente com o risco de parecer um eggomaníaco!)

são alguns dos efeitos negativos do “Easter eggs”. O interessante artigo

“Favorite software Easter Eggs” (Ovos de Páscoa de Software Favoritos)

publicado na IT World mostra o lado sinistro do “Easter eggs” de

software. O melhor a fazer é minimizar a exposição relativa do código a

ataques e isso significa evitar o uso de funções inseguras, removendo

funções não utilizadas que não possam ser rastreadas por uma matriz de

acompanhamento de requisitos, evitando, assim, a codificação de “Easter

eggs”.

U – Funções e rotinas inseguras/não Utilizadas no código

Page 25: Segurança de código

O aclamado trabalho do IEEE intitulado “Reverse Engineering and Design

Recovery: A Taxonomy” (Engenharia Reversa e Recuperação de Projetos:

Uma Taxonomia) define engenharia reversa como “o processo de análise

de um sistema para identificar seus componentes e o respectivo

interrelacionamento para criar uma representação do sistema em outra

forma ou em um nível mais alto de abstração”. Em resumo, a engenharia

reversa de software ou reversão é o processo de ir de trás para diante a

partir do sistema ou do código para determinar o projeto interno ou

detalhes de implementação. A reversão do software pode ser realizada no

nível de sistema ou de código. Códigos não obscurecidos ou sem assinatura

digital são facilmente revertidos. O obscurecimento de código é o processo

de tornar seu funcionamento difícil de entender e o código resultante é

algumas vezes chamado de “encoberto”. O obscurecimento é obtido pela

convolução do código para que, mesmo de posse do código fonte, este não

possa ser entendido. Os programas de obscurecimento podem operar no

código fonte, no código objeto ou em ambos, com o propósito principal de

impedir a engenharia reversa. A figura 5 mostra as versões não

obscurecida e obscurecida de um simples programa de impressão

HelloWorld. A assinatura digital do código (discutida anteriormente) é

outra técnica contra a engenharia reversa que oferece mais proteção do

que o obscurecimento.

R – Código sujeito a engenharia Reversa

Page 26: Segurança de código
Page 27: Segurança de código

Verificar o código quanto à existência de debuggers e enganar os

decompiladores utilizando dados não referentes a instruções ou bytes

inúteis são outras técnicas contra a engenharia reversa. Deve-se reconhecer

que tais técnicas contra a reversão só podem dificultar a reversão, mas não

necessariamente impedi-la. Não obstante, é imperativo que o código seja

protegido o máximo possível contra os riscos da engenharia reversa. O

livro Reversing - Secrets of Reverse Engineering (Reversão – Segredos da

Engenharia Reversa) de Eldad Eilam é uma fonte de informação excelente

para os interessados em escrever códigos irreversíveis.

R – Código sujeito a engenharia Reversa

Page 28: Segurança de código

O princípio do menor privilégio afirma que um usuário ou processo deve

receber somente o nível mínimo necessário de direitos de acesso

(privilégios) pelo menor tempo necessário para que o usuário ou processo

conclua a operação. Embora, algumas vezes um código que é executado

normalmente em um ambiente de desenvolvimento menos seguro apresente

soluços ou deixe de funcionar em um ambiente de produção mais restrito. A

solução usual para tais casos é aumentar o nível de privilégio sob o qual o

código possa ser executado ou remover a verificação de nível de privilégio.

Nenhuma dessas “soluções” é recomendável do ponto de vista de

segurança. Elas poderiam conduzir a uma evasão das permissões,

permitindo que usuários e processos com privilégios menores executem

códigos para os quais não estão autorizados. Adicionalmente, um outro

exemplo clássico de código inseguro é o código que pode ser

explicitamente configurado para ser executado com privilégios elevados

(administrativos) pelo programa.

As diferenças de configuração entre os ambientes de desenvolvimento e de

produção devem ser garantidas para que o código possa ser executado

com o menor nível de privilégios, independentemente do ambiente. Também

é imperativo garantir que os códigos configurados explicitamente para

serem executados com privilégios elevados sejam cuidadosamente

monitorados (auditados) e administrados.

E – Necessidade de privilégios Elevados para execução

Page 29: Segurança de código

‘Não escreva códigos inseguros’ é um dos oito hábitos seguros

discutidos em “8 Simple Rules for Developing More Secure Code”

(8 Regras Simples para Desenvolver Códigos Mais Seguros). Sem

um entendimento completo do que constitui um código inseguro,

seria injusto esperar que os desenvolvedores escrevessem códigos

mais seguros.

Conclusão

Caso o código seja vítima de uma quebra de segurança, o código em si pode ser considerado inocente, mas esse

tipo de indulgência não será estendido ao indivíduo, à equipe ou à organização que escreveu o código. Dessa

forma, é essencial que todos os que escrevem códigos tornem um hábito incorporar segurança ao código que

escrevem. Enumerar todas as formas de evitar a escrita de códigos inseguros e/ou como escrever códigos seguros

iria muito além do escopo deste trabalho. Em vez disso, ele deve ser visto meramente como um alerta para a escrita

de códigos seguros e para os tremendos riscos de não fazê-lo. Assim como um jogo de xadrez produz um número

infinito de movimentos uma vez que o jogo seja iniciado, com um número limitado de gambitos de abertura, o

entendimento das características dos códigos inseguros é um dos primeiros movimentos para assegurar que o código

seja escrito de forma a ser resistente a hackers. Código inseguro significa xeque-mate.

Page 30: Segurança de código

Conclusão