Prácticas de Ciberseguridad en el Desarrollo de Software

Prácticas de Ciberseguridad en el Desarrollo de Software

En el mundo digital actual, la seguridad es una preocupación creciente. Con software presente en casi todo lo que hacemos, es fundamental garantizar que sea creado teniendo en mente la seguridad.

Cada día, se descubren y explotan nuevas vulnerabilidades por actores malintencionados. Ignorar la ciberseguridad puede traer graves consecuencias financieras, de reputación e incluso, en algunos casos, físicas.

A lo largo de este artículo, veremos algunas buenas prácticas de seguridad que se pueden aplicar en el desarrollo de tu software y que te darán a ti y a tus usuarios una mayor confianza y protección contra las amenazas digitales actuales. Vamos a sumergirnos en técnicas, herramientas y estrategias que marcarán la diferencia al construir un software robusto y seguro.

Utilizaremos para la construcción de los ejemplos el lenguaje JavaScript, el framework Node.js, la base de datos PostgreSQL, y bcrypt para la seguridad de contraseñas. Además, se presentarán herramientas que ayudan a identificar vulnerabilidades potenciales.

Cabe recordar que todas las prácticas mencionadas aquí se pueden y deben aplicar en cualquier lenguaje, ya que cada uno cuenta con sus propias bibliotecas y frameworks similares.

El código puede obtenerse en el siguiente enlace de GitHub: https://github.com/luizfilipelgs/Revelo/tree/main/Praticas-de-Cibersegurança

Prácticas y temas abordados:

  • Validación de entrada;
  • Principio del Mínimo Privilegio;
  • Cifrado de contraseñas;
  • Pruebas de seguridad y herramientas útiles.

Validación de entrada

Es crucial para evitar que datos malintencionados o mal formateados causen problemas en tu aplicación. Sin una validación adecuada, pueden manifestarse varias amenazas:

  • Inyección de Código: Esta es quizá la más peligrosa. Si los datos de entrada no se validan y tratan correctamente, un atacante puede insertar código malicioso que la aplicación ejecute inadvertidamente. El ejemplo más común es la inyección SQL, donde comandos SQL maliciosos se insertan en campos de formulario para manipular o dañar las bases de datos.
  • Cross-Site Scripting (XSS): Una amenaza que ocurre cuando un atacante consigue insertar scripts maliciosos en páginas web, que luego se ejecutan en el navegador del usuario final. Esto puede llevar al robo de información, cookies y sesiones.
  • Ataques de desbordamiento (Overflows): Si un atacante sabe que un campo de entrada no se valida por tamaño, puede intentar sobrecargar el sistema insertando más datos de los esperados, lo que podría causar fallos o comportamientos inesperados.
  • Ataques de Manipulación de Lógica: Sin una validación adecuada, los atacantes pueden proporcionar entradas que hagan que la aplicación se comporte de forma no intencionada, como manipular precios en un carrito de compras o cambiar IDs de usuario para acceder a cuentas ajenas.
  • Falsificación de Solicitud Entre Sitios (CSRF): Aunque involucra más que solo la validación de entrada, garantizar que las solicitudes sean legítimas y provengan de fuentes confiables es crucial para defenderse contra CSRF.
  • Exposición de Información Sensible: Sin la validación adecuada, los atacantes pueden explotar entradas para obtener respuestas del sistema que revelen información sobre su configuración, versiones de software, o incluso datos de los usuarios.

Estos son solo algunos de los muchos problemas que pueden ocurrir cuando se descuida la validación de entrada. Una regla simple, pero fundamental, es nunca confiar en los datos proporcionados por los usuarios sin antes validarlos y tratarlos adecuadamente.

En este artículo abordaremos solo la Inyección de Código y XSS.

A continuación, se muestra un ejemplo simple utilizando Node.js para demostrar la validación de entrada donde bloqueamos los caracteres "<" y ">", mitigando un vector común de ataques, particularmente relacionado con el 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();

});



Al ejecutar este código e ingresar los datos solicitados, veremos que los caracteres fueron eliminados para que puedan ser utilizados correctamente en las lógicas de tu aplicación:

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

Entrada original: <<<!!!!aaa

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


A continuación, veremos un ejemplo para la prevención de inyección SQL al realizar una inserción de datos en una tabla en 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);

    }

};

En este fragmento, la función registreSales utiliza consultas parametrizadas para insertar datos en la tabla StoreManager.sales_products. Los marcadores de posición $1, $2 e S3 en la consulta SQL se sustituyen por los valores proporcionados en el arreglo [idSale, productId, quantity]. El uso de marcadores de posición y el suministro de valores como un arreglo separado, en lugar de concatenarlos o interpolarlos directamente en la cadena de consulta, garantiza que los valores siempre se traten como datos y nunca como parte del comando SQL. Esto impide efectivamente los intentos de inyección SQL, ya que un atacante no puede insertar comandos SQL maliciosos que el sistema ejecute inadvertidamente.

En resumen, este enfoque, utilizando consultas parametrizadas, es una de las mejores prácticas recomendadas para prevenir la inyección SQL.

Principio del Mínimo Privilegio

También conocido como Principio de la Menor Autoridad o POLP (Principle of Least Privilege), es un concepto de seguridad de la información que recomienda que cualquier usuario, programa o sistema solo tenga acceso a los recursos e información estrictamente necesarios para realizar su función o tarea asignada, y nada más. El objetivo de este principio es limitar el daño potencial si una cuenta o programa se ve comprometido. Al restringir los derechos y permisos, se reduce la superficie de ataque y se puede contener la propagación de actividades maliciosas.

Consideremos el escenario de una base de datos. Muchas veces, las aplicaciones tienen acceso total a una base de datos, lo que representa un gran riesgo de seguridad. En su lugar, aplicando el Principio del Mínimo Privilegio, la aplicación debería tener solo los permisos estrictamente necesarios.

Veamos un ejemplo conceptual usando Node.js y PostgreSQL. Primero, configura PostgreSQL y crea un usuario con privilegios 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;


En tu aplicación Node.js, conéctate usando este usuario con privilegios 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();

});


En el ejemplo anterior, la aplicación solo puede ejecutar consultas SELECT en la tabla sales_products, y no puede modificar datos ni acceder a otras tablas o información sensible. Si un atacante lograra explotar alguna vulnerabilidad en la aplicación, el daño sería limitado, ya que no tendría la capacidad de, por ejemplo, eliminar o modificar registros en la tabla.

Cifrado de contraseñas

El cifrado de contraseñas es una práctica esencial de seguridad en la que una contraseña se transforma en una representación cifrada, de modo que su forma original se oculte. Al almacenar contraseñas, nunca es una buena práctica guardarlas en texto plano. En su lugar, siempre se deben cifrar y almacenar la versión cifrada.

Una técnica ampliamente adoptada para el "cifrado" de contraseñas es el "hashing". Es un proceso unidireccional: una vez que tienes un hash, no puedes "desencriptarlo" para obtener la contraseña original. Cuando un usuario intenta iniciar sesión, tomas la contraseña proporcionada, aplicas la misma función de hash y la comparas con el hash almacenado. Si coinciden, la contraseña es correcta.

Utilizaremos una biblioteca llamada bcrypt muy popular para hacer el hashing de contraseñas. No solo proporciona una función de hash fuerte, sino que también incluye un sal (salt), que es una serie de caracteres aleatorios agregados a la contraseña para garantizar que cada hash sea único, incluso para contraseñas 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.');

        }

    });

});

Salida con la contraseña correcta:

[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!


Salida con la contraseña incorrecta:

[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.


Al ejecutar el código, verás la contraseña en texto plano, su hash correspondiente y una confirmación de si la contraseña en texto plano coincide con el hash generado. Este ejemplo demuestra cómo crear un hash para una contraseña y, posteriormente, cómo verificar una contraseña contra un hash existente utilizando bcrypt. En aplicaciones reales, el hash generado es lo que se almacenaría en una base de datos, en lugar de la contraseña en texto plano.

Pruebas de Seguridad y Herramientas Útiles

Asegurar la seguridad de un software va más allá de las buenas prácticas de codificación. Es esencial ser proactivo en la identificación y corrección de vulnerabilidades. Existen varias herramientas en el mercado que pueden ayudar a los desarrolladores y expertos en seguridad a descubrir y remediar estos problemas:

  • OWASP ZAP (Zed Attack Proxy):
  • ¿Qué es? Es una herramienta de prueba de penetración gratuita y de código abierto, centrada en encontrar vulnerabilidades en aplicaciones web.
  • Características: Actúa como un proxy que intercepta el tráfico entre el navegador y la aplicación web. Esto permite a ZAP inspeccionar, modificar y redirigir el tráfico que entra y sale del navegador. Tiene capacidades de escaneo automático, detección pasiva de problemas y también permite ataques activos.
  • Burp Suite:
  • ¿Qué es? Es una aplicación gráfica para pruebas de seguridad de aplicaciones web. Actúa como un proxy entre navegadores y servidores, lo que permite interceptar, visualizar y modificar el tráfico HTTP y HTTPS. También ofrece funcionalidades de escaneo automático e intrusión. Nikto: ¿Qué es? Un escáner de servidor web de código abierto.
  • Características: Prueba servidores web para identificar software peligroso, configuraciones defectuosas y diversas vulnerabilidades.
  • SQLmap:
  • ¿Qué es? Una herramienta de código abierto para detectar y explotar vulnerabilidades de inyección SQL.
  • Características: Detecta y explota una amplia gama de vulnerabilidades de inyección SQL.
  • W3af:
  • ¿Qué es? Un framework de código abierto para pruebas de seguridad en aplicaciones web.
  • Características: Tiene varios complementos que ayudan a identificar vulnerabilidades, desde detección XSS e inyección SQL hasta problemas CORS y fallas API.
  • Metasploit:
  • ¿Qué es? Una plataforma para el desarrollo, prueba y ejecución de exploits contra máquinas objetivo.
  • Características: Ampliamente utilizado para pruebas de penetración y evaluaciones IDS/IPS.
  • Arachni:
  • ¿Qué es? Un escáner de seguridad para aplicaciones web.
  • Características: Identifica una variedad de vulnerabilidades, incluyendo XSS, inyección SQL, y errores de ejecución de código remoto.

Al elegir herramientas de seguridad, es fundamental considerar el alcance y las características específicas de su proyecto, así como la curva de aprendizaje de cada herramienta. Una combinación de varias herramientas a menudo proporciona una visión más holística de la seguridad de una aplicación.

Conclusión

Integrar prácticas de Ciberseguridad desde el inicio del desarrollo de software no es sólo una buena práctica, sino una necesidad en los tiempos actuales. Las amenazas siempre están cambiando y con frecuencia surgen nuevos riesgos. Como casi todo gira en torno a la tecnología, la seguridad no se puede dejar de lado.

Es fundamental actualizarse y adaptarse continuamente a las nuevas prácticas y tendencias de seguridad. El aprendizaje continuo es nuestra mejor defensa para proteger nuestros valiosos recursos digitales.

Quiero señalar que los ejemplos proporcionados están simplificados con fines ilustrativos. En escenarios reales, la seguridad exige un enfoque más detallado. No basta con aplicar soluciones estándar. Es necesario personalizarlos para el contexto de la aplicación, realizar pruebas exhaustivas y, siempre que sea posible, buscar la opinión de expertos para garantizar una protección sólida.

Referencias

  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/

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.