Codificação Segura

Teoria e Prática

Robson Cruz

SECOMPP 2017

@deapyxel

Cronograma

  1. Introdução e Motivação
  2. Conceitos básicos
  3. Identificando vulnerabilidades
  4. Padrões de Segurança
  5. Utilização de Ferramentas
  6. Segurança em IoT
  7. Privacidade vs Segurança vs Produto Personalizável
  8. 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!
  1. Deixe explícito quando houverem dados opcionais
  2. 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;

VCG

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

  • 115