Fundamentos de Seguranca Web - OWASP Top 10 e Medidas Praticas de Defesa

Intermediario | 2025.12.02

A Importancia da Seguranca Web

Aplicacoes web estao constantemente expostas a riscos de ataques. A OWASP (Open Web Application Security Project) publica o Top 10, que resume os riscos de seguranca mais graves.

flowchart TB
    subgraph OWASP["OWASP Top 10 2021"]
        A1["1. Broken Access Control<br/>(Falha de Controle de Acesso)"]
        A2["2. Cryptographic Failures<br/>(Falha de Criptografia)"]
        A3["3. Injection<br/>(Injecao)"]
        A4["4. Insecure Design<br/>(Design Inseguro)"]
        A5["5. Security Misconfiguration<br/>(Configuracao Incorreta)"]
        A6["6. Vulnerable Components<br/>(Componentes Vulneraveis)"]
        A7["7. Authentication Failures<br/>(Falha de Autenticacao)"]
        A8["8. Software Integrity Failures<br/>(Falha de Integridade)"]
        A9["9. Logging Failures<br/>(Falha de Log/Monitoramento)"]
        A10["10. SSRF<br/>(Server-Side Request Forgery)"]
    end

XSS (Cross-Site Scripting)

Tipos de Ataques

flowchart TB
    subgraph XSS["Tipos de XSS"]
        subgraph Reflected["1. Reflected XSS (Refletido)"]
            R1["URL: /search?q=script"] --> R2["HTML: Script inserido nos resultados da busca"]
        end

        subgraph Stored["2. Stored XSS (Armazenado)"]
            S1["Postagem de comentario"] --> S2["Salvo no DB"] --> S3["Exibido para outros usuarios"]
            Note1["Codigo de ataque permanece permanentemente"]
        end

        subgraph DOM["3. DOM-based XSS"]
            D1["JS do lado do cliente<br/>insere parametro da URL diretamente no DOM"]
        end
    end

Medidas de Defesa

// 1. Escape de HTML
function escapeHtml(text: string): string {
  const map: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;',
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}

// 2. Usar escape automatico do template engine
// React: JSX faz escape automaticamente
<div>{userInput}</div> // Seguro

// Perigoso: evitar dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // Perigoso!

// 3. Content Security Policy (CSP)
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: `
      default-src 'self';
      script-src 'self' 'unsafe-eval';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https:;
      font-src 'self';
      connect-src 'self' https://api.example.com;
    `.replace(/\n/g, ''),
  },
];

// 4. Uso de Cookie HttpOnly
res.cookie('session', token, {
  httpOnly: true,  // Inacessivel via JavaScript
  secure: true,    // Requer HTTPS
  sameSite: 'strict',
  maxAge: 3600000,
});

CSRF (Cross-Site Request Forgery)

sequenceDiagram
    participant U as Usuario
    participant B as Site do Banco
    participant A as Site do Atacante

    Note over U,B: 1. Usuario faz login no site legitimo
    U->>B: Login
    B->>U: Cookie de Sessao

    Note over U,A: 2. Acessa site armadilha do atacante
    U->>A: Acesso
    A->>U: Formulario oculto<br/>(POST para bank.com/transfer)

    Note over U,B: 3. Requisicao fraudulenta executada com sessao do usuario
    U->>B: POST /transfer<br/>(to=attacker, amount=10000)
    B-->>U: Transferencia concluida

Medidas de Defesa

// 1. Token CSRF
import { randomBytes } from 'crypto';

function generateCsrfToken(): string {
  return randomBytes(32).toString('hex');
}

// Salvar token na sessao
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = generateCsrfToken();
  }
  res.locals.csrfToken = req.session.csrfToken;
  next();
});

// Incluir token no formulario
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value={csrfToken} />
  ...
</form>

// Verificacao da requisicao
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  // Executar processamento
});

// 2. SameSite Cookie
res.cookie('session', token, {
  sameSite: 'strict', // ou 'lax'
  httpOnly: true,
  secure: true,
});

// 3. Verificacao do cabecalho Origin
app.use((req, res, next) => {
  const origin = req.headers.origin;
  const allowedOrigins = ['https://myapp.com'];

  if (req.method !== 'GET' && !allowedOrigins.includes(origin)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
});

SQL Injection

Codigo Vulneravel

// Exemplo perigoso
app.get('/users', async (req, res) => {
  const { name } = req.query;
  // SQL Injection possivel!
  const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
  res.json(result);
});

// Exemplo de ataque: ?name=' OR '1'='1
// SELECT * FROM users WHERE name = '' OR '1'='1'
// → Todos os usuarios sao obtidos

Medidas de Defesa

// 1. Prepared Statement (consulta parametrizada)
app.get('/users', async (req, res) => {
  const { name } = req.query;
  const result = await db.query(
    'SELECT * FROM users WHERE name = $1',
    [name]
  );
  res.json(result);
});

// 2. Uso de ORM
// Prisma
const users = await prisma.user.findMany({
  where: { name },
});

// Drizzle
const users = await db.select().from(usersTable).where(eq(usersTable.name, name));

// 3. Validacao de entrada
import { z } from 'zod';

const QuerySchema = z.object({
  name: z.string().max(100).regex(/^[a-zA-Z0-9\s]+$/),
});

app.get('/users', async (req, res) => {
  const result = QuerySchema.safeParse(req.query);
  if (!result.success) {
    return res.status(400).json({ error: 'Invalid input' });
  }
  // ...
});

Seguranca de Autenticacao

// Hash de senha seguro
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// Uso seguro de JWT
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_EXPIRES_IN = '15m';
const REFRESH_TOKEN_EXPIRES_IN = '7d';

function generateTokens(userId: string) {
  const accessToken = jwt.sign({ userId }, JWT_SECRET, {
    expiresIn: JWT_EXPIRES_IN,
    algorithm: 'HS256',
  });

  const refreshToken = jwt.sign({ userId, type: 'refresh' }, JWT_SECRET, {
    expiresIn: REFRESH_TOKEN_EXPIRES_IN,
    algorithm: 'HS256',
  });

  return { accessToken, refreshToken };
}

// Contramedidas para forca bruta
import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 5, // Ate 5 vezes
  message: { error: 'Too many login attempts' },
  standardHeaders: true,
  legacyHeaders: false,
});

app.post('/login', loginLimiter, async (req, res) => {
  // Processamento de login
});

Cabecalhos de Seguranca

// middleware/security.ts
import helmet from 'helmet';

app.use(helmet());

// Ou configuracao individual
app.use((req, res, next) => {
  // Filtro XSS
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Prevencao de Content-Type sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Prevencao de clickjacking
  res.setHeader('X-Frame-Options', 'DENY');

  // Forcar HTTPS
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

  // Politica de referrer
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Politica de permissoes
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  next();
});

Validacao de Entrada

import { z } from 'zod';
import DOMPurify from 'dompurify';

// Definicao de schema
const UserInputSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(2).max(100).regex(/^[\p{L}\s]+$/u),
  bio: z.string().max(500).optional(),
  website: z.string().url().optional(),
});

// Sanitizacao
function sanitizeHtml(dirty: string): string {
  return DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href'],
  });
}

// Validacao de upload de arquivo
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];

function validateFile(file: File): boolean {
  if (file.size > MAX_FILE_SIZE) {
    throw new Error('File too large');
  }
  if (!ALLOWED_TYPES.includes(file.type)) {
    throw new Error('Invalid file type');
  }
  return true;
}

Checklist de Seguranca

CategoriaItem de Verificacao
AutenticacaoHash de senha (bcrypt/Argon2)
AutenticacaoContramedidas para fixacao de sessao
AutenticacaoSuporte a MFA
EntradaValidacao de todas as entradas
EntradaConsulta SQL parametrizada
SaidaEscape de HTML
ComunicacaoHTTPS obrigatorio
CookieHttpOnly, Secure, SameSite
CabecalhosConfiguracao CSP, HSTS
DependenciasVerificacao de vulnerabilidades (npm audit)
← Voltar para a lista