Práticas de Cibersegurança no Desenvolvimento de Software

Práticas de Cibersegurança no Desenvolvimento de Software

No mundo digital atual, a segurança é uma preocupação crescente. Com softwares presentes em quase tudo que fazemos, é fundamental garantir que ele seja criado com segurança em mente.

A cada dia, novas vulnerabilidades são descobertas e exploradas por atores mal-intencionados. Ignorar a Cibersegurança pode resultar em graves consequências financeiras, de reputação e, em alguns casos, até mesmo físicas.

Ao longo deste artigo, iremos ver algumas boas práticas de segurança que podem ser aplicadas no desenvolvimento de seus softwares e que darão a você e aos seus usuários uma maior confiança e proteção contra as ameaças digitais atuais. Vamos mergulhar em técnicas, ferramentas e estratégias que farão a diferença na hora de construir um software robusto e seguro.

Utilizaremos para construção dos exemplos a linguagem Javascript, o framework Node.js, banco de dados PostgreSQL, e bcrypt para segurança de senhas. Além disso, serão apresentadas ferramentas que ajudam a identificar vulnerabilidades potenciais.

Lembrando que todas as práticas mencionadas aqui podem e devem ser aplicadas em qualquer linguagem, onde cada linguagem tem suas próprias bibliotecas e frameworks similares.

O código pode ser obtido através do link no GitHub: https://github.com/luizfilipelgs/Revelo/tree/main/Praticas-de-Cibersegurança

Práticas e temas abordados:

  • Validação de Entrada;
  • Princípio do Mínimo Privilégio;
  • Criptografia de Senhas;
  • Testes de Segurança e Ferramentas Úteis.

Validação de entrada

É crucial para evitar que dados mal-intencionados ou formatados incorretamente causem problemas em sua aplicação. Sem uma validação adequada, várias ameaças podem se manifestar:

  • Injeção de Código: Esta é talvez a mais perigosa. Se os dados de entrada não forem corretamente validados e tratados, um invasor pode inserir código malicioso, que a aplicação executa inadvertidamente. O exemplo mais comum disso é a Injeção SQL, onde comandos SQL maliciosos são inseridos em campos de formulário para manipular ou danificar bancos de dados.
  • Cross-Site Scripting (XSS): Uma ameaça que ocorre quando um invasor consegue inserir scripts maliciosos em páginas web, que são então executados no navegador do usuário final. Isso pode levar ao roubo de informações, cookies e sessões.
  • Ataques de Overflows: Se um invasor souber que um campo de entrada não está sendo validado para tamanho, ele pode tentar sobrecarregar o sistema inserindo mais dados do que o sistema espera, o que pode causar falhas ou comportamento inesperado.
  • Ataques de Manipulação de Lógica: Sem a validação adequada, os atacantes podem fornecer entradas que façam a aplicação se comportar de maneira não intencional, como manipular preços em um carrinho de compras ou alterar IDs de usuário para acessar contas que não pertencem a eles.
  • Falsificação de Solicitação entre Sites (CSRF): Embora isso envolva mais do que apenas validação de entrada, garantir que as solicitações sejam legítimas e venham de fontes confiáveis é uma parte crucial da defesa contra CSRF.
  • Exposição de Informações Sensíveis: Sem a validação adequada, os invasores podem explorar entradas para obter respostas do sistema que revelem informações sobre sua configuração, versões de software, ou até mesmo dados do usuário.

Estes são apenas alguns dos muitos problemas que podem ocorrer quando a validação de entrada é negligenciada. Uma regra simples, mas fundamental é nunca confiar nos dados fornecidos pelos usuários sem antes validá-los e tratá-los adequadamente.

Neste artigo abordaremos apenas Injeção de Código e XSS.

Abaixo está um exemplo simples usando Node.js para demonstrar a validação de entrada onde bloqueamos os caracteres < e >, com isso estamos mitigando um vetor comum de ataques, particularmente relacionado ao Cross-Site Scripting (XSS).

// Módulo readline do Node.js para interagir com o console.

const readline = require('readline');


const rl = readline.createInterface({

    input: process.stdin,

    output: process.stdout

});


// Solicite ao usuário inserir algum texto.

rl.question('Digite algo (tente usar caracteres especiais como < ou >): ', (input) => {

   

    // Função para validar a entrada do usuário.

    function validateInput(data) {

        // Substitua quaisquer instâncias de < ou > para evitar injeção maliciosa, por exemplo.

        const sanitized = data.replace(/<|>/g, '');

        return sanitized;

    }


    const sanitizedInput = validateInput(input);


    console.log(`Entrada original: ${input}`);

    console.log(`Entrada após validação: ${sanitizedInput}`);

   

    rl.close();

});



Ao executarmos este código e entrarmos com o dado solicitado veremos que os caracteres foram removidos para que a partir daí possam ser utilizados nas lógicas da sua aplicação:

Digite algo (tente usar caracteres especiais como < ou >): <<<!!!!aaa

Entrada original: <<<!!!!aaa

Entrada após validação: !!!!aaa


A seguir, veremos um exemplo para  prevenção de injeção SQL realizando uma inserção de dados em uma tabela no PostgreSQL.

const { Pool } = require('pg');


const pool = new Pool({

    user: 'your_db_user',

    host: 'localhost',

    database: 'your_database',

    password: 'your_db_password',

    port: 5432,

});


const registreSales = async (idSale, productId, quantity) => {

    try {

        await pool.query(

            'INSERT INTO StoreManager.sales_products (sale_id, product_id, quantity) VALUES ($1, $2, $3)',

            [idSale, productId, quantity]

        );

    } catch (error) {

        console.error("Erro ao registrar venda:", error.message);

    }

};

Neste trecho, a função registreSales utiliza consultas parametrizadas para inserir dados na tabela StoreManager.sales_products. Os placeholders $1, $2 e S3 na consulta SQL são substituídos pelos valores fornecidos no array [idSale, productId, quantity]. O uso de placeholders e o fornecimento de valores como um array separado, ao invés de concatenar ou interpolá-los diretamente na string de consulta, garantem que os valores sejam sempre tratados como dados e nunca como parte do comando SQL. Isso efetivamente impede tentativas de injeção SQL, pois um atacante não pode inserir comandos SQL maliciosos que o sistema irá executar inadvertidamente.

Em resumo, essa abordagem, usando consultas parametrizadas, é uma das práticas recomendadas para prevenir a injeção SQL.

Princípio do Mínimo Privilégio

Também conhecido como Princípio da Menor Autoridade ou POLP (Principle of Least Privilege), é um conceito de segurança da informação que recomenda que qualquer usuário, programa ou sistema só deve ter acesso aos recursos e informações estritamente necessários para realizar sua função ou tarefa designada, e nada mais. O objetivo deste princípio é limitar o potencial dano que pode ocorrer se uma conta ou programa for comprometido. Ao restringir os direitos e permissões, a superfície de ataque é reduzida e a propagação de atividades maliciosas pode ser contida.

Vamos considerar o cenário de um banco de dados. Muitas vezes, aplicações têm acesso total a um banco de dados, o que pode ser um grande risco de segurança. Em vez disso, aplicando o Princípio do Mínimo Privilégio, a aplicação deveria ter apenas as permissões estritamente necessárias.

Vamos ver um exemplo conceitual usando Node.js e PostgreSQL. Primeiro, configure o PostgreSQL e crie um usuário com privilégios limitados:

// Criar usuário somente-leitura

CREATE USER readonly_user WITH PASSWORD 'your_password';

// Dar acesso somente-leitura à tabela sales_products

GRANT USAGE ON SCHEMA public TO readonly_user;

GRANT SELECT ON TABLE public.sales_products TO readonly_user;


Em seu aplicativo Node.js, conecte-se usando este usuário com privilégios limitados:

const { Pool } = require('pg');


const pool = new Pool({

    user: 'readonly_user',

    host: 'localhost',

    database: 'your_database',

    password: 'your_password',

    port: 5432,

});


pool.query('SELECT * FROM sales_products', (error, results) => {

    if (error) {

        throw error;

    }

    console.log(results.rows);

    pool.end();

});


No exemplo acima, a aplicação só pode executar consultas SELECT na tabela sales_products, e não pode modificar dados ou acessar outras tabelas ou informações sensíveis. Se um invasor conseguisse explorar algum tipo de vulnerabilidade no aplicativo, o dano seria limitado, pois ele não teria a capacidade de, por exemplo, excluir ou alterar registros na tabela.

Criptografia de senhas

A criptografia de senhas é uma prática essencial de segurança na qual uma senha é transformada em uma representação cifrada, de modo que sua forma original seja escondida. Ao armazenar senhas, nunca é uma boa prática salvar a versão em texto simples. Em vez disso, sempre criptografe a senha e armazene essa versão criptografada.

A técnica amplamente adotada para a "criptografia" de senhas é o "hashing". É um processo unidirecional: uma vez que você tem um hash, não pode "descriptografar" de volta à senha original. Quando um usuário tenta fazer login, você pega a senha fornecida, aplica a mesma função de hash e compara com o hash armazenado. Se eles corresponderem, a senha está correta.

Utilizaremos uma biblioteca chamada bcrypt muito popular para hashing de senhas. Ele não apenas fornece uma função de hash forte, mas também inclui um "sal", que é uma série de caracteres aleatórios adicionados à senha para garantir que cada hash seja único, mesmo para senhas idênticas.

const bcrypt = require('bcrypt');


const saltRounds = 10;

const myPasswordTrue = 'password123';

const myPasswordFalse = '123456'


// Hashing da senha

bcrypt.hash(myPasswordTrue, saltRounds, (err, hash) => {

    if (err) {

        console.error('Erro ao criar hash:', err);

        return;

    }


    console.log(`Senha em texto simples: ${myPasswordTrue}`);

    console.log(`Hash da senha: ${hash}`);


    // Verificando a senha contra o hash

    bcrypt.compare(myPasswordTrue, hash, (err, result) => {

        if (err) {

            console.error('Erro ao comparar senha e hash:', err);

            return;

        }


        if (result) {

            console.log('A senha fornecida corresponde ao hash!');

        } else {

            console.log('A senha fornecida NÃO corresponde ao hash.');

        }

    });

});

Saída com a senha correta:

[Running] node "c:\...\Praticas-de-Cibersegurança\Criptografia-de-Senhas.js"

Senha em texto simples: password123

Hash da senha: $2b$10$p1/.m0CtEIocgwJ5YvaUy.RqA5lZ.KjPYd5fVKf4Jblb/th.Ihs8C

A senha fornecida corresponde ao hash!


Saída com a senha Incorreta:

[Running] node "c:\...\Praticas-de-Cibersegurança\Criptografia-de-Senhas.js"

Senha em texto simples: 123456

Hash da senha: $2b$10$Jkks3F1no9qwQk6it.2C6OpH2Tim2h.HTYUy3LSZ1LO7nq/lfyuMK

A senha fornecida NÃO corresponde ao hash.


Ao executar o código você verá a senha em texto simples, seu hash correspondente e uma confirmação de que a senha em texto simples corresponde ao hash gerado. Este exemplo demonstra como criar um hash para uma senha e, em seguida, como verificar uma senha contra um hash existente usando bcrypt. Em aplicações reais, o hash gerado é o que você armazenaria em um banco de dados, em vez da senha em texto simples.

Testes de Segurança e Ferramentas Úteis

Assegurar a segurança de um software vai além das boas práticas de codificação. É essencial ser proativo na identificação e correção de vulnerabilidades. Existem várias ferramentas no mercado que podem ajudar desenvolvedores e especialistas em segurança a descobrir e remediar esses problemas:

  • OWASP ZAP (Zed Attack Proxy):
  • O que é? Uma ferramenta de teste de penetração gratuita e open-source focada em encontrar vulnerabilidades em aplicações web.
  • Características: Funciona como um proxy interceptador, o que significa que ele fica entre o navegador do usuário e a aplicação web. Isso permite ao ZAP inspecionar, modificar e redirecionar o tráfego que entra e sai do navegador. Ele tem capacidades de scanning automático, detecção passiva de problemas, e também permite ataques ativos.
  • Burp Suite:
  • O que é? Uma aplicação gráfica para teste de segurança de aplicações web. Características: Atua como um proxy entre navegadores e servidores, permitindo interceptar, visualizar e modificar tráfego HTTP e HTTPS. Também oferece funcionalidades de scanning automático e intrusão. Nikto: O que é? Um scanner de servidor web open-source.
  • Características: Testa servidores web para identificar softwares perigosos, configurações defeituosas, e várias vulnerabilidades.
  • SQLmap:
  • O que é? Uma ferramenta open-source para detecção e exploração de vulnerabilidades de injeção SQL.
  • Características: Detecta e explora uma ampla variedade de vulnerabilidades de injeção SQL.
  • W3af:
  • O que é? Um framework open-source para testes de segurança de aplicações web.
  • Características: Possui diversos plugins que ajudam a identificar vulnerabilidades, desde a detecção de XSS e injeção SQL até problemas de CORS e falhas em APIs.
  • Metasploit:
  • O que é? Uma plataforma para desenvolvimento, teste e execução de exploits contra máquinas-alvo.
  • Características: Amplamente utilizado para testes de penetração e avaliações de IDS/IPS.
  • Arachni:
  • O que é? Um scanner de segurança para aplicações web.
  • Características: Identifica uma variedade de vulnerabilidades, incluindo erros de XSS, injeção SQL, e falhas de execução de código remoto.

Ao escolher ferramentas de segurança, é essencial considerar o escopo e as especificidades do seu projeto, bem como a curva de aprendizado de cada ferramenta. Uma combinação de várias ferramentas geralmente proporciona uma visão mais holística da segurança de uma aplicação.

Conclusão

Integrar práticas de Cibersegurança desde o início do desenvolvimento de software não é apenas uma boa prática, mas uma necessidade nos tempos atuais. As ameaças estão sempre mudando, com novos riscos surgindo frequentemente. Como quase tudo gira em torno da tecnologia, a segurança não pode ser deixada de lado.

É essencial se atualizar e se adaptar continuamente às novas práticas e tendências em segurança. O aprendizado contínuo é nossa melhor defesa para proteger nossos valiosos recursos digitais.

Quero salientar que os exemplos fornecidos são simplificados para fins ilustrativos. Em cenários reais, a segurança demanda uma abordagem mais detalhada. Não basta simplesmente aplicar soluções padrão. É necessário personalizá-las para o contexto da aplicação, realizar testes aprofundados e, sempre que possível, buscar a opinião de especialistas para garantir proteção robusta.

Referências

  1. Node.js: https://nodejs.org/
  2. Express.js: https://expressjs.com/
  3. bcrypt: https://github.com/kelektiv/node.bcrypt.js
  4. PostgreSQL (pg): https://www.postgresql.org/
  5. OWASP ZAP (Zed Attack Proxy): https://www.zaproxy.org/
  6. Burp Suite: https://portswigger.net/burp
  7. Nikto: https://github.com/sullo/nikto
  8. SQLmap: http://sqlmap.org/
  9. W3af: http://w3af.org/
  10. etasploit: https://www.metasploit.com/
  11. Arachni: http://www.arachni-scanner.com/

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.