No cenário atual de segurança da informação, percebemos que devemos estar preparados contra os possíveis ataques. Nesse contexto, um grande aliado contra as ameaças na integração de segurança no ciclo de desenvolvimento é a metodologia OWASP Software Security Assurance Process, também descrita pela sigla OSSAP.
Neste artigo, iremos falar um pouco sobre desenvolvimento seguro e como podemos mitigar algumas ameaças já no processo de desenvolvimento.
Por onde começar?
Para saber do que precisamos nos proteger e como devemos nos proteger, precisamos entender quais são as vulnerabilidades mais encontradas em aplicações web. Para tanto, utilizamos como base o guia Open Web Application Security Project, mais conhecido como OWASP. Por ele temos a lista das vulnerabilidades mais comuns que são exploradas nas aplicações web.
Abaixo estão listadas as 10 principais vulnerabilidades atuais citadas pela OWASP:
- Injection
- Broken Authentication and Session Management
- Cross-Site Scripting (XSS)
- Broken Access Control
- Security Misconfiguration
- Sensitive Data Exposure
- Insufficient Attack Protection
- Cross-Site Request Forgery (CSRF)
- Using Components with Known Vulnerabilities
- Underprotected APIs
Em alguns dos nossos exemplos, vamos mostrar funções que utilizamos na construção do site https://desafiocyber.beyellow.com.br, desenvolvido em PHP. O objetivo dessa página era o cadastro dos candidatos para que participem do nosso processo seletivo através de um mini-CTF (Capture The Flag), com desafios relacionados à segurança da informação, reforçando a necessidade de um desenvolvimento seguro da nossa aplicação, pois prevíamos que sua proposta poderia torná-la alvo de usuários maliciosos.
Conexão com MySQL
No primeiro passo, tomamos algumas medidas para mitigar os riscos de ataques de SQL Injection. Utilizamos a biblioteca PDO para as interações com banco de dados MySQL.
A função prepare() do PDO já faz o escape dos caracteres da variável recebida e trata-os no momento da interação com o banco de dados. Partindo da premissa de que não confiamos nas requisições feitas pelo cliente, decidimos validar a variável antes de adicioná-la à função prepare.
Exemplo de código:
$email=filter_var($email, FILTER_VALIDATE_EMAIL); if ($stmt = $pdo->prepare("SELECT id,nick FROM users WHERE email = :email LIMIT 1")) { $stmt->bindValue(':email',$email); list($user_id, $nick) = $stmt->fetch( PDO::FETCH_NUM ); }
Validando as variáveis e utilizando o PDO da maneira correta, conseguimos mitigar problemas como o SQL injection.
Autenticação e validação
Para uma autenticação segura, é fundamental que toda comunicação entre cliente e servidor esteja criptografada. Para isto, além do certificado digital SSL/TLS, utilizamos HTTP Strict Transport Security (HSTS), que é um parâmetro de segurança que força a utilização do canal seguro, impedindo a comunicação sem criptografia.
Abaixo estão alguns parâmetros de segurança que definimos no cabeçalho HTTP em um arquivo de configuração na pasta conf-enabled do apache.
Header set X-Content-Type-Options: "nosniff" Header always set x-xss-protection "1; mode=block" Header always set x-frame-options "SAMEORIGIN" Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
A primeira função que definimos para a aplicação foi o cadastro de usuários. Isto ocorria da seguinte forma: solicitávamos algumas informações básicas dos usuários e uma senha, que seria utilizada para que fizessem o login posteriormente. Como uma forma de segurança adicional, utilizamos um script em JavaScript para enviar a hash da senha que o usuário preencheu no formulário, passando a hash pelo canal seguro HTTPS.
Desta forma, mesmo se um atacante comprometesse o canal, seria mais difícil comprometer a senha do usuário, já que a política de senhas também estava forte.
Após receber os dados via post, pegamos a hash da senha que o usuário enviou e utilizamos para criar outra hash. Com um salt randômico, utilizamos SHA-512. Para gerar o salt, também recorremos ao SHA-512 com 16 caracteres randômicos. Depois disso, pegamos a hash do salt e concatenamos com a hash da senha, formando outra hash que é enviada ao banco de dados. Quanto mais segurança, melhor – ainda mais quando seu público é constituído por pessoas que são da área de segurança.
$salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE)); $senha = hash('sha512', $_POST['rec-p'] . $salt);
Após cadastrar tudo no banco de dados, um link é enviado por e-mail para que o usuário ative o cadastro, diminuindo a quantidade de possíveis usuários falsos na aplicação.
$salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE)); $sha=hash('sha512', $email); $hash = hash('sha512', $sha . $salt ); $stmt = $pdo->prepare("INSERT INTO users_active (cpf,hash) VALUES (:cpf,:hash) "); $stmt->execute(array(':cpf' => $cpf,':hash' => $hash)); $link='https://'.$_SERVER['SERVER_NAME']."/process_active.php?KEY1=$sha&KEY2=$salt";
Por que não usar o $_SERVER[‘HTTP_HOST’]? Porque ele é vulnerável. Isto porque esse comando pode dar abertura para que um atacante manipule a requisição e altere o HOST no cabeçalho. Lembrando que muitas aplicações o utilizam para importar scripts, gerar links e redefinir senhas.
O próximo passo após a ativação do usuário é a autenticação na aplicação. Criamos um formulário simples pedindo e-mail e senha. Para evitar ataques de força bruta, utilizamos o reCaptcha.
Abaixo está um trecho de como recebemos o e-mail e a senha do usuário e como validamos o reCaptcha:
require_once "recaptchalib.php"; $secret = " YWNob3UgcXVlIGNvbG9jYXJpYSBtaW5oYSBjaGF2ZSBhcXVpPyBycw"; $response = null; $reCaptcha = new ReCaptcha($secret); if (isset($_POST['email'],$_POST[''rec-p''],$_POST["g-recaptcha-response"])){ $response = $reCaptcha->verifyResponse($_SERVER["REMOTE_ADDR"],$_POST["g-recaptcha-response"]); if ($response != null && $response->success){ $email=filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); $password = trim(preg_replace("/[^a-z0-9]/", "",$_POST['rec-p'])); } }
Como podemos ver, não há muito segredo. O maior diferencial foi na hora de tratar a hash que recebemos, que só aceita números e letras minúsculas, eliminando outra possibilidade de caractere.
Para criar uma sessão segura, utilizamos o seguinte código:
function sec_session_start() { $session_name = 'SEC_PHPSESSID'; $secure = true; $httpOnly = true; $domain=$_SERVER['SERVER_NAME']; ini_set('session.use_only_cookies', 1) session_set_cookie_params(time+1500,'/',$domain,$secure,$httpOnly); session_name($session_name); session_start(); session_regenerate_id(true); }
Configurando os parâmetros de segurança no cookie que será gerado, definimos o atributo secure como true, para que ele só seja transferido se estiver no canal seguro (HTTPS). Já o HttpOnly pode ser utilizado para que o cookie não se torne “acessível” por meio de scripts, tornando mais difícil o roubo de sessão através de Cross-site scripting (XSS). Definimos o domain recorrendo ao SERVER_NAME que definimos no apache, de modo que o cookie somente fique disponível para o domain.
Agora vamos à parte do XSS, sobre o qual falamos há pouco. Para mitigar o risco contra o roubo de sessão por meio de XSS, tratamos as variáveis que enviavam dados ao banco de dados para que nele não entre nenhuma tag HTML. Se por algum motivo ocorrer a entrada de tags no banco de dados, tratamos na hora de consultar os dados e mostrar a variável na aplicação, utilizando a função htmlspecialchars ($variavel, ENT_QUOTES).
if ($botao->rowCount() == 0){ echo '<button data-toggle="modal" data-target="#'.htmlentities(preg_replace("/[^0-9]+/", "", $id),ENT_QUOTES).'" class="btn btn-default">'.htmlentities($name,ENT_QUOTES).'</button>'; }else{ echo '<button data-toggle="modal" data-target="#" class="btn btn-success">' .htmlentities($name,ENT_QUOTES).'</button>'; } }
Controle de acesso
A autenticação é de suma importância para verificar se o usuário está logado na aplicação, antes que ele retorne dados de uma página autenticada. Por exemplo:
if (usuario_logado() == true) { echo “Usuário logado”; //continua código pela pagina autenticada. }else{ header('Location: index.php); }
Nunca, em hipótese alguma, faça como no exemplo abaixo:
if (usuario_logado() == false) { header('Location: index.php); } echo “Usuário logado”; //continua código pela pagina autenticada.
No exemplo acima, o código só é redirecionado depois que a página é carregada por completo, apresentando o conteúdo sem necessariamente ter uma sessão ativa e autenticada. Isto torna o controle de acesso falho.
Proteção adicional
Mesmo com algumas proteções contra as ameaças, no desenvolvimento é de suma importância ter outras formas de proteção – como, por exemplo, o WAF (Web Application Firewall). Em nossa aplicação, utilizamos o ModSecurity e o Fail2ban. O Modsecurity foi acionado para barrar as tentativas de exploração das vulnerabilidades mais conhecidas. Já o Fail2ban, para bloquear possíveis DoS (Denial Of Service) ou tentativas de conexão em nosso SSH, que obviamente não estava rodando na porta default, pois desabilitamos o usuário root e criamos contas de acesso imprevisíveis. Além disso, implementamos um segundo fator de autenticação com o Google Authenticator. Há ainda outras técnicas para esconder o SSH que poderiam ter sido implementadas, como o Port Knocking.
Outra função muito importante foi a configuração do arquivo htaccess do apache, para que retorne o status code 200 nas requisições, evitando riscos com a enumeração de páginas e arquivos da aplicação. Desabilitamos também o Directory browsing pelo htaccess e barramos eventuais tentativas de acessar o arquivo htacess pela aplicação.
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L] ErrorDocument 401 /index.php ErrorDocument 403 /index.php ErrorDocument 404 /index.php ErrorDocument 500 /index.php </IfModule> <Files .htaccess> Require all denied </Files> Options -Indexes –MultiViews
O motivo de redirecionar esses erros para a página index foi justamente evitar a enumeração por status code. Em nosso header, incluímos um http_response_code(200), que na maioria dos casos retornaria como 200.
Conclusão
A maioria das aplicações hoje está vulnerável por falta de um desenvolvimento seguro. Percebemos que sites bem conceituados no mercado ainda sofrem com falhas da lista das 10 principais vulnerabilidades citadas pela OWASP. Existem, hoje, no mercado, várias formas de realizar a revisão estática no código em busca de brechas que podem colocar a aplicação em risco.
Uma das formas de analisar estaticamente é por meio da ferramenta Checkmarx. Outra forma, sem custos, é recorrer ao guia de code review da OWASP.
Se alguma dúvida surgir, ou caso o leitor tenha sugestões de melhorias, sinta-se livre para comentar.
Referências
- https://www.w3schools.com/php/filter_validate_email.asp
- https://www.owasp.org/index.php/Main_Page
- http://php.net/manual/en/function.setcookie.php
- http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL
- https://developers.google.com/recaptcha/old/docs/php
- https://gist.github.com/mbijon/1098477
- http://php.net/manual/en/function.htmlentities.php
- https://www.owasp.org/index.php/Secure_SDLC_Cheat_Sheet
- https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-16-04
- https://www.digitalocean.com/community/tutorials/how-to-use-port-knocking-to-hide-your-ssh-daemon-from-attackers-on-ubuntu
***
Artigo escrito em colaboração com Luiz Milagres que é gerente da prática de Cybersecurity na EY no Brasil, professor universitário de Cybersecurity na FIAP. Com mais de 10 anos de experiência em Tecnologia e Cybersecurity sendo aproximadamente 8 anos dedicados a Segurança da Informação. Com grande prática e com mais de 60 projetos entregues nas maiores empresas do Brasil e do mundo. Especialista em projetos de testes de invasão, avaliação de segurança em tecnologia da informação, estratégias de implantação para gestão de vulnerabilidades, gestão e resposta a incidentes, inteligência cibernética e gerenciamento de ameaças.