Codificação Segura
Teoria e Prática
Robson Cruz
SECOMPP 2017
@deapyxel
Cronograma
- Introdução e Motivação
- Conceitos básicos
- Identificando vulnerabilidades
- Padrões de Segurança
- Utilização de Ferramentas
- Segurança em IoT
- Privacidade vs Segurança vs Produto Personalizável
- Considerações Finais
Introdução
Todo programa, em toda e qualquer plataforma é um alvo em potencial
Pessoas mal intencionadas irão tentar encontrar falhas no sua aplicação, servidor, script, etc.
Pessoas mal intencionadas irão utilizar toda e qualquer vulnerabilidade para obter informações, corromper dados, ganhar o controle de sistemas...
A propriedade e reputação sua e do seu cliente está sempre em jogo.
1
2
3
Segurança não é algo que pode simplesmente ser adicionado ao software como um plugin ou de maneira trivial.
Uma casa feita de papelão não se tornou mais segura por colocarmos um cadeado na porta.
Apple Developers, 2016
É impossível se prevenir contra todas as vulnerabilidades existentes, e muitas vezes, nem todas são pertinentes ao seu software.
Nosso trabalho é identificar as falhas que oferecem risco ao nosso software e incorporar práticas de codificação segura durante o desenvolvimento do projeto.
Conceitos
O que vem a ser então essa "Codificação Segura"?
A prática de construir software levando em conta as vulnerabilidades, riscos e tecnologias disponíveis, de forma a mitigar e impedir a exploração e uso não intencionado do mesmo .
Qual o objetivo da segurança quando tratamos de software?
Manter a confiabilidade, integridade e disponibilidade das informações, garantindo uma operação de negócios de sucesso.
OWASP, 2010
Garantir que a informação seja revelada somente à partes autorizadas.
Garantir que a informação é precisa e válida, e que não houveram alteração por ações não autorizadas.
Garantir que a informação esteja disponível sempre que necessário.
C
I
D
Riscos
Riscos são a combinação de fatores que ameaçam o sucesso do negócio
Um agente malicioso interage com o sistema, que pode possuir uma vulnerabilidade a ser explorada com o objetivo de causar impacto.
Hacker
Cracker
Attacker
Vulnerabilidades
- Buffer Overflow.
- Entrada de dados não validada.
- Execução de eventos em cadeia.
- Comunicação inter-processo.
- Problemas de controle de acesso.
- Falhas nos processos de autenticação, autorização ou encriptação.
Fraquezas que tornam um sistema suscetível a ataques ou danos.
Todos esses conceitos são muito úteis mas...
Identificando vulnerabilidades
Buffer Overflow
- Ocorre quando há escrita além dos limites do final (ou começo) de um buffer.
- Existe principalmente pela filosofia de "excesso de testes deixa o software mais lento e ineficiente".
- Manipulação de ponteiros e índices incorreta é uma constante em vulnerabilidades de buffer overflow.
- Pode ser dividido de acordo com com a área onde ocorre:
- Stack Overflow;
- Heap Overflow;
Stack Overflow
Utiliza do overflow para desviar o fluxo de execução do programa.
int somar (int a, int b) {
int res = a + b;
return res;
}
int main (int argc, char ** argv) {
int resultado = somar (1,2);
printf ("Resultado = %d\n", resultado);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char ** argv) {
char buf1 [16];
char buf2 [32];
printf("stack\n");
memset(buf1, '1', 16);
memset(buf2, '2', 32);
printf("buf1 = %s\n", buf1);
printf("buf2 = %s\n", buf2);
getch();
return 0;
}
$ ./stack2 10
stack
buf1 = 1111111111111111,
buf2 = 222222222222222222222222222222221111111111111111,
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char ** argv) {
char buf1 [16];
char buf2 [32];
printf ("stack\n");
memset (buf1, '1', 16);
memset (buf2, '2', 32 + atoi(argv[1]));
printf ("buf1 = %s\n", buf1);
printf ("buf2 = %s\n", buf2);
getch();
return 0;
}
$ ./stack2 10
stack
buf1 = 2222222222111111,
buf2 = 222222222222222222222222222222222222222222111111,
Heap Overflow
Utiliza do overflow para manipular o conteúdo de outros buffers.
A exploração depende muito do ambiente.
É comum ser precedido de um integer overflow.
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
int main (int argc, char ** argv) {
FILE *fd;
char *buf = (char *) malloc(8);
char *arquivo = (char *) malloc(16);
strcpy(arquivo, "/tmp/arquivo");
gets(buf);
printf("Abrindo o arquivo: %s\n", arquivo);
fd = fopen (arquivo, "w");
fputs(buf, fd);
fclose(fd);
return 0;
}
Qual o problema com esse código?
$ echo "teste heap" | ./heap
Abrindo o arquivo: /tmp/arquivo
$ cat /tmp/arquivo
teste heap
$ perl -e ’print "A"x15’ | ./heap
Abrindo o arquivo: /tmp/arquivo
// mesmo com buf tendo 8 bytes, o programa não apresenta problemas
$ perl -e ’print "A"x16’ | ./heap
Abrindo o arquivo: /tmp/arquivo
Segmentation Fault
// com 16 bytes, o nome do arquivo some por uma falha de segmentação
$ perl -e ’print "A"x20’ | ./heap
Abrindo o arquivo: AAAA
// com 17+ bytes, o nome do arquivo é sobrescrito
$ perl -e ’print "A"x16+"/etc/passwd"’ | ./heap
Abrindo o arquivo: /etc/passwd
Dicas:
- Faça checagem do acesso aos buffers
- Tanto de tamanho quanto conteúdo
- Evite a utilização de funções inseguras
- Sempre utilize o terminador de string:
- Alocar uma posição a mais e não utilizar;
- Zerar os buffers antes de qualquer entrada;
- Forçar a reentrada de dados se o buffer for alterado de forma inesperada
- Cuidado com expressões regulares
Funções inseguras
Funções são consideradas inseguras por não permitir certas verificações, ou não possuir certos comportamentos restritivos.
Função | Risco | Sugestão |
---|---|---|
gets() | Altíssimo | fgets(buf, size, stdin) |
strcpy() | Alto | strncpy() |
strcat() | Alto | strncat() |
sprintf() | Alto | snprintf() e precisão do formato |
scanf() | Alto | Use precisão do formato |
Implementação da LibC, OpenBSD, 2005
Exemplo: strlen()
Função utilizada para calcular o tamanho de uma string.
int main (int argc, char ** argv) {
char *exemplo = "quantidade de bytes";
printf("%d\n", strlen(exemplo));
return 0;
}
./strlen_test
19
O problema é a condição de parada: o terminador de string \0
Exemplo: strlen()
int main (int argc, char ** argv) {
char a[32];
memset(a, 'a', 32);
printf("Tamanho de a = %d\n", strlen(a));
return 0;
}
$ ./problema_strlen
Tamanho de a = 49
O valor do tamanho de a pode variar dependendo do compilador, estado atual da máquina, etc.
Solução: caracter terminador inserido manualmente
int main (int argc, char ** argv) {
char a[33];
memset(a, 'a', 32);
a[32] = '\0';
printf("Tamanho de a = %d\n", strlen(a));
return 0;
}
$ ./solucao_strlen
Tamanho de a = 32
De forma geral, strlen() não é considerada uma função segura, e sua utilização deve ser evitada
Entrada de dados não validada
- Sem dúvida uma das maiores causas de invasão.
- Desenvolvedores tendem supor que o ambiente do cliente é seguro e controlado.
- Qualquer entrada de dados deve ser rigorosamente validada.
- Esse tipo de vulnerabilidade está intimamente ligado com o buffer overflow.
- Técnicas de Code Injection se utilizam dessa vulnerabilidade.
$myvar = "varname";
$x = $_GET['arg'];
eval("\$myvar = \$x;");
Qual o problema com esse código?
Quando a função eval() recebe dados não confiáveis e os interpreta uma injeção de código seria possível.
/index.php?arg=1; phpinfo()
/index.php?arg=1; system('id')
<?php
$name = $_REQUEST['name'];
?>
<html>
<body>
Hello,
<?php
echo $name;
?>!
</body>
</html>
XSS
localhost/welcome.php?name=%3Cscript%3Ealert%2842%29;%3C/script%3E
O parâmetro name é ecoado de volta para o navegador, que irá executar o JS. O navegador confia que o JS foi enviado pelo servidor, mas esse não é o caso.
<?
$a = htmlentities($_GET['a']);
$b = $_GET['b'];
$c = $_GET['c'];
$d = htmlentities($b);
echo($a); //"seguro"
echo(htmlentities($b)); //"seguro"
echo($c); //XSS
echo($d); //"seguro"
echo(htmlentities($_GET['id']); //"seguro"
?>
XSS
Falhas desse tipo são comuns a todo código que trabalham do lado do servidor
<?php
$var = $_POST['var'];
mysql_query("SELECT * FROM sometable WHERE id = $var");
?>
SQL Injection
Sem dúvida uma das vulnerabilidades mais simples de corrigir e menos corrigida.
<?php
$var = mysql_real_escape_string($_POST['var']);
?>
Dicas:
- Fontes não confiáveis:
- campos de entrada textual;
- linha de comando (terminal, prompt);
- Arquivos providos pelo usuário;
- Comandos passados através da URL;
- Qualquer dado lido pela rede;
- Invasores irão verificar e testar todas as fontes de entrada de dado possíveis para o seu programa, então realize a validação corretamente em todos os vetores.
- Deve sempre haver uma rotina central de validação.
- Sempre especifique charsets, como UTF-8.
Dicas:
- Realize o encode de todos os dados antes da validação.
- Valide também tarefas automatizadas de JS, Flash e outro código embedded.
- Valide:
- Em relação ao tamanho,
- ao tipo,
- ao "range",
- e ao que mais for possível.
- Quando for retornar dados ao usuário, realize o encode em um sistema confiável.
- Sanitarize toda e qualquer saída de Queries SQL, XML, etc.
Autenticação, Autorização e Criptografia
- Ao contrário da anteriores, é relativamente comum desenvolvedores tentarem implantar essas políticas.
- A especificação dessas políticas é feita de forma errada.
- A utilização de certificados digitais é difundida, mas nem sempre executada corretamente [CVE-2004-0927].
- Nem todos os métodos de autenticação são seguros.
- Todo e qualquer sistema de segurança e encriptação pode ser superado por alguém com acesso físico e tempo disponível.
Criptografia e a utilização do salt
import hashlib
import time
import threading
def ipv4_md5_search(hash, range_start=0, range_end=256):
for a in range(range_start, range_end):
# show progress every time new value of a is done.
print("{} : {}".format(a, time.ctime()))
for b in range(256):
for c in range(256):
for d in range(256):
h = hashlib.md5(str('%s.%s.%s.%s' %
(a, b, c, d)).encode('utf-8')).hexdigest()
if h == hash:
print("{}.{}.{}.{}".format(a, b, c, d))
return True
print("No match found")
threads = []
for i in range(0, 256, 1):
t = threading.Thread(target=ipv4_md5_search,
args=('f39d1e9bce27c0f31f536a272e544a16', i, i + 1))
threads.append(t)
t.start()
- Realizar operações que requerem privilégios de acesso elevado ao sistema quase sempre resultam em problemas.
- O princípio de "Least privilegies" é pouco seguido.
Every program and every user of the system should operate using the least set of privileges necessary to complete the job.
Saltzer AND Schroeder
Um usuário comum deveria ter o privilégio para deletar arquivos temporário de sistema?
Padrões de Segurança
Security Patterns
Confiança
- É a base da segurança de sistemas
- É extremamente importante estabelecer quais são as fontes confiáveis e inconfiáveis
Para pensar
Imagine que João, um empresário chega ao banco e solicita a caixa que ela transfira X dólares da conta A para a conta B.
O caixa então simplesmente realiza a transação.
O que pode dar errado?
Falhas
- Como o banco sabe que o João é o João?
- Como João sabe que o banco é o banco?
- Como o banco sabe se João realmente possui acesso a essa conta?
- Como o banco sabe que a conta possui o valor X solicitado?
Confiança
Sua aplicação
DB
Usuário
Bibliotecas
Requests
etc
Fronteira da Confiança
Como fazer algo fora da fronteira se tornar confiável?
Não confiável
Rejeitado
Aceito
VALIDAÇÃO
- Ter certeza que os dados são válidos
- o valor pra transferência é um número > -1?
- Canonização e/ou normalização
- XSS e Path Traversal.
- Sanitarização
- Logs.
Validação também é útil na identificação de bugs
Domain-Driven Security
- É utilizado em desenvolvimento que adota a prática DDD (Domain-Driven Design).
- DDD + convenções de validação.
Domain-Driven Security
- A mesma validação deve ser realizada várias vezes
- É fácil esquecer dela em algum lugar,
- Está em diferentes lugares,
- Deve validar até mesmo fontes "internas" como o DB
Sua aplicação
DB
Usuário
Bibliotecas
Requests
etc
Integer
Integer
Validação
Domain-Driven Security
- Valores e tipos primitivos são considerados não confiáveis por padrão.
- Objetos de domínio:
- (Built-in validation) Não podem ser criados de forma inválida.
- Imutabilidade.
Sua aplicação
DB
Usuário
Bibliotecas
Requests
etc
Integer
Conta
public final class NumeroConta{
private final Integer valor;
public NumeroConta(Integer valor){
if(!isValid(valor)){
throw new IllegalOperationException("NumeroConta inválido!!");
}
this.valor = valor;
}
public static boolean isValid(Integer numeroConta){
return numeroConta != null &&
hasLength(numeroConta, 10, 12) &&
isNumeric(numeroConta);
}
}
public void Reticulate(Spline spline, int angle);
public void Reticulate(Spline spline, Angle angle);
public Temperature calcTemperature(int heat, int time);
public Temperature calcTemperature(Temperature heat, Time time);
Domain-Driven Security
- Todos objetos do domínio são válidos
- Você sabe que tem algo errado quando encontra tipos primitivos sendo passados como parâmetro
- O sistema de tipos acrescenta e garante um novo nível de validação
- Você ainda precisa validar as regras de negócio
Domain-Driven Security:
O caso do Null
- Regra geral: Nunca use Null!
- Deixe explícito quando houverem dados opcionais
- Se algo não deveria acontecer utilize as exceptions
Untrusted Pattern
- A ideia é utilizar a própria "fronteira de confiança" como um perímetro, do qual dados não confiáveis não conseguem passar.
public void Foo(string untrusted_bar){
if(!IsValid(untrusted_bar)){
throw new ValidationException();
}
var bar = untrusted_bar;
doSomethingWith(bar);
}
public void Foo(string bar){
if(!IsValid(bar)){
throw new ValidationException();
}
doSomethingWith(bar);
}
Untrusted Pattern
public void Foo(Unstrusted<string> bar);
public class Untrusted<T>{
readonly T _value;
public Untrusted(T value){
_value = value;
}
private T Value {
get{
return _value
};
}
}
[assembly: InternalsVisibleTo("Validation")]
Não "existe" um Getter!
Untrusted Pattern
public abstract class Validator<T>{
public T Validate(Untrusted<T> untrusted){
if(!InnerValidate(untrusted.Value)){
throw new ValidationException();
}
return untrusted.Value;
}
protected abstract bool InnerValidate(T value);
}
Enquanto isso, na unidade de Validação...
Agora podemos construir nossas próprias validações simplesmente por herdar essa classe
Untrusted Pattern
public void HandleAcctNbr(Untrusted<string> accountNbr){
var trusted = new AccountNumberValidator().Validate(accountNbr);
DoSomethingWith(trusted);
}
public void CreateAccount(string nbr){
var untrusted Nbr = new Untrusted<string>(nbr);
HandleAccountNbr(untrustedNbr);
...
}
Imutabilidade
- O que passar pela fronteira de confiança, independente da direção, não deve ser capaz de mudar
- Como a sua aplicação se comporta com concorrência?
- Várias threads;
- Isso afeta validação?
- O que você validou continua válido?
- TOCTTOU (Time of Check to Time of Use)
TOCTTOU
//
//
// After the access check
symlink("/etc/passwd", "file");
// Before the open, "file" points to the password database
//
//
if (access("file", W_OK) != 0) {
exit(1);
}
fd = open("file", O_WRONLY);
write(fd, buffer, sizeof(buffer));
ToC
ToU
Imutabilidade
- Como garantir a imutabilidade então?
- Utilizar o final.
- A imutabilidade trabalha muito bem com DDSec
- E com concorrência...
- E com eventos em cadeia...
- E com integração...
- ...existe algum motivo para não utilizar?...
- É uma camada a mais de segurança no seu código
Inverse Life Coach Pattern
Anything that can possibly go wrong, does
Lei de Murphy
Seja pessimista em relação as possibilidades
boolean success = true;
...
return success;
boolean success = false;
...
return success;
Inverse Life Coach Pattern
Falhe rápido e garanta um caminho preciso para o sucesso.
public Resultado calculaResultado(Conta conta){
if(!possuiAcesso(conta))
throw new Exception();
...
return new Resultado(resultadoCalculadoNoCodigo);
}
Impossível sair sem objeto válido
Falhe rápido
Inverse Life Coach Pattern
A utilização de Ferramentas
Não existe ferramenta mágica.
Static Analysis
Ocorre fora do tempo de execução, e é muito útil para detecção de bugs, já que analisa o código diretamente.
Dinamic Analysis
Ocorre em tempo de execução, e analisa o fluxo, memória e dados da aplicação, se mostrando útil para detectar vulnerabilidades que passam despercebidas
Imagine um projeto do tamanho de um sistema operacional
Consome mais tempo e é mais "cara"
Técnicas
- DFA (Data Flow Analysis);
- CFG (Control Flow Graph);
- Taint Analysis;
- Lexical Analysis;
Qual a vantagem de se utilizar ferramentas?
- Altamente escalável;
- Pode ser executada repetidamente;
- Ferramentas poder auxiliar na detecção de falhas como Buffer Overflow, Injection e Address Switching;
Qual a desvantagem de se utilizar ferramentas?
- Muitas vulnerabilidades não são fáceis de encontrar, como problemas de autenticação;
- Falsos positivos;
- Difícil comprovar que o problema encontrado é realmente uma vulnerabilidade;
Hoje iremos utilizar 2 ferramentas: VisualCodeGrepper e Acunetix WVS
VisualCodeGrepper
- Análise Estática;
- Sistema de Rankeamento;
- Open-source;
- Costumizável;
- C/C++, C#, VB, PHP, Java;
Acunetix WVS
- Análise Dinâmica;
- Sistema de Relatórios;
- Comercial;
- Pode ser integraado no ciclo de desenvolvimento facilmente;
- Realiza as requisições localmente;
Segurança em IoT
Potenciais Ameaças
- Interface Web vulnerável
- Autenticação insuficiente
- Serviços de Rede inseguros
- Falta de encriptação no transporte
- Problemas relacionados a privacidade
- Falta de configurações disponíveis relacionadas à segurança
- Software ou Firmware inseguro
- Pouca Segurança física
Regras Gerais
- Evite a possibilidade de Account Harvest utilizando várias camadas de autenticação
- Dispositivos novos devem ter sido propriamente configurados e testados antes de ser integrados
- Utilize serviços testados, conhecidos e ativos
- Protocolos de encriptação sempre que possível, mesmo em ambientes "controlados"
- Evite armazenar e trabalhar com mais dados do que o necessário, e limite o acesso que o seu software possui
- Transferência de propriedade de dispositivos deve ser rigorosa
Privacidade vs Segurança vs Produto Personalizável
Segurança
Privacidade
Personalização
Considerações Finais
Valide,
Valide,
Valide!
Utilize os Padrões ao seu favor
Se acostume a criar um código seguro desde o começo
Avalie extensivamente quais são as sua fontes confiáveis
Ferramentas não são mágicas, mas ajudam imensamente
Fique por dentro das vulnerabilidades descobertas diariamente
Aprenda com os erros
Referências e Links
OWASP
Projeto mundial sem fins lucrativos que busca melhorar a segurança do software
https://www.owasp.org/index.php/Main_Page
GOTO Con
Conferência que acontece no mundo todo, abordo diversos temas, incluindo segurança
https://blog.gotocon.com/
SEI
O Software Engineering Instutute(SEI) da Carnegie Mellon University possui muitos materiais publicados sobre a importância da codificação segura.
CERT
A divisão CERT do SEI possui várias noticias e publicações relativas a segurança da informação, assim como programas de treinamento.
https://wiki.sei.cmu.edu/confluence/display/seccode/Top+10+Secure+Coding+Practices
Vários artigos publicados na área
Relatórios Kaspersky, Offensive Security
Vários fórums
Muito obrigado!
Codificação Segura: Teoria e Prática
By Robson Cruz
Codificação Segura: Teoria e Prática
- 113