OWASP Top 10: Visão Geral das Principais Vulnerabilidades Web: Do Básico ao Avançado Já leu

Introdução ao OWASP Top 10 O OWASP Top 10 é uma lista de referência internacional que documenta as dez vulnerabilidades de segurança mais críticas encontradas em aplicações web. Publicado pela Open Web Application Security Project, este documento é atualizado regularmente — a versão mais recente é de 2021 — e serve como base para desenvolvimento seguro, testes de penetração e auditorias de segurança. Ignorar essas vulnerabilidades não é apenas uma questão de compliance, mas de responsabilidade com seus usuários e dados. A maioria das brechas de segurança não acontece porque o atacante descobriu um zero-day, mas porque vulnerabilidades conhecidas e documentadas não foram mitigadas. O OWASP Top 10 existe justamente para evitar que você cometa os mesmos erros que milhares de desenvolvedores cometem todos os dias. Neste artigo, abordaremos cada uma dessas dez categorias com exemplos práticos e soluções reais que você pode implementar imediatamente em seus projetos. Injection (Injeção) Injeção ocorre quando dados não confiáveis são enviados para um

Introdução ao OWASP Top 10

O OWASP Top 10 é uma lista de referência internacional que documenta as dez vulnerabilidades de segurança mais críticas encontradas em aplicações web. Publicado pela Open Web Application Security Project, este documento é atualizado regularmente — a versão mais recente é de 2021 — e serve como base para desenvolvimento seguro, testes de penetração e auditorias de segurança. Ignorar essas vulnerabilidades não é apenas uma questão de compliance, mas de responsabilidade com seus usuários e dados.

A maioria das brechas de segurança não acontece porque o atacante descobriu um zero-day, mas porque vulnerabilidades conhecidas e documentadas não foram mitigadas. O OWASP Top 10 existe justamente para evitar que você cometa os mesmos erros que milhares de desenvolvedores cometem todos os dias. Neste artigo, abordaremos cada uma dessas dez categorias com exemplos práticos e soluções reais que você pode implementar imediatamente em seus projetos.

1. Injection (Injeção)

Injeção ocorre quando dados não confiáveis são enviados para um interpretador como parte de um comando ou query. O atacante injeta código malicioso que é executado no servidor, permitindo acesso não autorizado, modificação de dados ou até compromisso total do sistema. SQL Injection é a forma mais comum, mas você também encontrará injeção em LDAP, OS commands, XML e outras linguagens.

SQL Injection

SQL Injection acontece quando entrada do usuário é concatenada diretamente em uma query SQL sem validação ou sanitização. Veja um exemplo vulnerável:

# VULNERÁVEL - NÃO FAÇA ISTO
import sqlite3

def login_user(username, password):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    # Concatenação direta - permite injeção
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    cursor.execute(query)
    result = cursor.fetchone()
    conn.close()
    return result is not None

Se o usuário inserir admin' -- como username, a query se torna:

SELECT * FROM users WHERE username = 'admin' --' AND password = '...'

O -- comenta o resto da query, ignorando a senha completamente.

A solução é usar prepared statements (consultas parametrizadas):

# SEGURO - Use prepared statements
import sqlite3

def login_user(username, password):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    # Placeholders (?) segregam dados do código SQL
    query = "SELECT * FROM users WHERE username = ? AND password = ?"
    cursor.execute(query, (username, password))
    result = cursor.fetchone()
    conn.close()
    return result is not None

Prepared statements garantem que o input do usuário seja tratado como dado, nunca como código executável. Este é o padrão ouro e funciona em todas as linguagens e databases.

Command Injection

Command injection é similiar, mas ocorre quando você passa input de usuário diretamente para comandos do sistema operacional:

# VULNERÁVEL - NÃO FAÇA ISTO
import os

def generate_report(filename):
    # Se filename for "report.pdf; rm -rf /", você tem um problema sério
    os.system(f"generate_pdf {filename}")

A solução é usar subprocess com uma lista de argumentos e shell=False:

# SEGURO
import subprocess

def generate_report(filename):
    # Lista de argumentos é mais segura; shell=False impede interpretação de shell metacharacters
    subprocess.run(['generate_pdf', filename], shell=False, check=True)

2. Broken Authentication (Autenticação Quebrada)

Autenticação quebrada permite que atacantes comprometam senhas, tokens de sessão ou implementem ataques de força bruta. Isso inclui falhas em gerenciamento de sessão, senhas fracas, recovery flows inadequados e falta de proteção em endpoints sensíveis.

Uma das falhas mais comuns é armazenar senhas em plain text ou com hash fraco. Senhas devem ser hasheadas com algoritmos modernos como bcrypt, scrypt ou PBKDF2 com salt:

# VULNERÁVEL - armazena em plain text
def register_user(username, password):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", 
                   (username, password))  # NUNCA FAÇA ISTO
    conn.commit()
    conn.close()
# SEGURO - usa bcrypt
import bcrypt

def register_user(username, password):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    # bcrypt gera um salt aleatório e hash seguro automaticamente
    hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
    cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", 
                   (username, hashed_password))
    conn.commit()
    conn.close()

def login_user(username, password):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT password FROM users WHERE username = ?", (username,))
    result = cursor.fetchone()
    conn.close()

    if result:
        # bcrypt.checkpw compara senhas com segurança
        return bcrypt.checkpw(password.encode('utf-8'), result[0])
    return False

Além disso, implemente proteção contra força bruta com rate limiting e bloqueio temporário de contas após múltiplas tentativas de login falhadas. Use session tokens seguros (gerados com secrets em Python), defina expiração apropriada e considere implementar autenticação multifator (MFA) para contas sensíveis.

3. Sensitive Data Exposure (Exposição de Dados Sensíveis)

Dados sensíveis — senhas, tokens, informações financeiras, dados de saúde — precisam ser protegidos tanto em trânsito quanto em repouso. Isso inclui usar HTTPS/TLS, criptografia de dados armazenados e implementar controles de acesso apropriados.

Proteção em Trânsito

Sempre use HTTPS em produção. HTTP trafega em plain text e qualquer um na rede (especialmente em WiFi público) pode interceptar seus dados:

# Flask - força HTTPS em produção
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app, force_https=True)  # Redireciona HTTP para HTTPS automaticamente

@app.route('/api/user')
def get_user():
    return {'user': 'data'}  # Trafega encriptado via HTTPS

Proteção em Repouso

Dados sensíveis armazenados devem ser criptografados:

# Criptografia de dados sensíveis
from cryptography.fernet import Fernet
import os

# Gere uma chave uma vez e armazene-a com segurança (variável de ambiente, key management service, etc)
encryption_key = os.getenv('ENCRYPTION_KEY', Fernet.generate_key())
cipher = Fernet(encryption_key)

def store_credit_card(card_number):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    # Criptografe antes de armazenar
    encrypted_card = cipher.encrypt(card_number.encode())
    cursor.execute("INSERT INTO payments (card) VALUES (?)", (encrypted_card,))
    conn.commit()
    conn.close()

def retrieve_credit_card(card_id):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT card FROM payments WHERE id = ?", (card_id,))
    encrypted_card = cursor.fetchone()[0]
    conn.close()
    # Descriptografe quando necessário
    return cipher.decrypt(encrypted_card).decode()

Nunca log dados sensíveis. Não armazene mais dados do que necessário. Implemente controle de acesso para que apenas usuários autorizados vejam dados sensíveis. Considere tokenização para dados como números de cartão — armazene um token em vez do número real.

4. XML External Entity (XXE) e Entity Parsing Attacks

XXE ocorre quando um parser XML processa entidades externas não confiáveis. Um atacante pode ler arquivos do servidor, fazer requisições para serviços internos ou provocar negação de serviço. Muitas aplicações ainda processam XML sem desabilitar features perigosas.

Veja um exemplo vulnerável:

# VULNERÁVEL - processa entidades externas
import xml.etree.ElementTree as ET

def parse_user_xml(xml_string):
    # O parser padrão permite entidades externas
    root = ET.fromstring(xml_string)
    return root.tag

Se alguém enviar este XML malicioso:

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<user>
    <name>&xxe;</name>
</user>

O parser vai tentar ler /etc/passwd do servidor.

A solução é desabilitar explicitamente entidades externas:

# SEGURO - desabilita XXE
import xml.etree.ElementTree as ET

def parse_user_xml(xml_string):
    # Desabilita DOCTYPE, entidades externas e DTD
    parser = ET.XMLParser()
    parser.parser.EntityParser = None

    # Melhor ainda: use defusedxml
    from defusedxml import ElementTree as DefusedET
    root = DefusedET.fromstring(xml_string)
    return root.tag

A biblioteca defusedxml é especializada em mitigar vulnerabilidades XML. Use-a sempre que processar XML de fontes não confiáveis:

# Recomendado
from defusedxml.ElementTree import parse as safe_parse

def parse_xml_file(filepath):
    tree = safe_parse(filepath)
    return tree.getroot()

5. Broken Access Control (Controle de Acesso Quebrado)

Controle de acesso quebrado significa que usuários conseguem acessar recursos ou executar ações para as quais não têm permissão. Isso inclui acesso a dados de outros usuários, escalação de privilégios, manipulação de URLs/parâmetros para acessar conteúdo protegido.

Um erro comum é confiar apenas em verificações no frontend ou em ofuscação de IDs:

# VULNERÁVEL - não valida permissões no backend
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/users/<user_id>/profile')
def get_user_profile(user_id):
    # Qualquer pessoa pode acessar qualquer perfil
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    return jsonify(user)

A solução é sempre validar permissões no servidor:

# SEGURO - valida permissões
from flask import Flask, request, jsonify, session

app = Flask(__name__)

def get_current_user():
    # Obtém usuário autenticado da sessão
    return session.get('user_id')

@app.route('/api/users/<user_id>/profile')
def get_user_profile(user_id):
    current_user_id = get_current_user()

    if not current_user_id:
        return jsonify({'error': 'Não autenticado'}), 401

    # Valida que o usuário só pode acessar seu próprio perfil
    if int(user_id) != current_user_id:
        return jsonify({'error': 'Acesso negado'}), 403

    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    return jsonify(user)

Implemente role-based access control (RBAC) onde necessário:

# RBAC - controle baseado em papéis
def require_role(*allowed_roles):
    def decorator(f):
        def wrapper(*args, **kwargs):
            current_user = get_current_user()
            user_role = get_user_role(current_user)

            if user_role not in allowed_roles:
                return jsonify({'error': 'Acesso negado'}), 403

            return f(*args, **kwargs)
        wrapper.__name__ = f.__name__
        return wrapper
    return decorator

@app.route('/api/admin/users')
@require_role('admin')
def list_all_users():
    # Apenas administradores podem acessar
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    users = cursor.fetchall()
    conn.close()
    return jsonify(users)

Sempre valide permissões no servidor. Não confie em tokens de sessão ou IDs que vêm do cliente. Implemente principle of least privilege — dê aos usuários apenas as permissões mínimas necessárias.

6. Security Misconfiguration (Configuração Segurança Inadequada)

Misconfigurações incluem defaults inseguros, permissões inadequadas, desativação de recursos de segurança, falta de patches, headers de segurança ausentes e exposição de informações sensíveis em mensagens de erro.

Headers de Segurança

Muitos servidores não enviam headers HTTP essenciais de segurança. Adicione-os:

# Flask - adiciona headers de segurança
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)

# Talisman configurado corretamente
Talisman(app, 
    force_https=True,
    strict_transport_security=True,
    strict_transport_security_max_age=31536000,  # 1 ano
    content_security_policy={
        'default-src': "'self'",
        'script-src': ["'self'", "'unsafe-inline'"],
        'style-src': ["'self'", "'unsafe-inline'"],
    }
)

@app.route('/')
def index():
    return 'Seguro'

Os headers mais importantes:
- Strict-Transport-Security: força HTTPS
- Content-Security-Policy: previne XSS e injection
- X-Content-Type-Options: previne MIME sniffing
- X-Frame-Options: previne clickjacking

Mensagens de Erro Genéricas

Não exponha detalhes técnicos em mensagens de erro:

# VULNERÁVEL - expõe stack trace
@app.route('/api/data')
def get_data():
    try:
        result = 1 / 0  # Simula erro
    except Exception as e:
        return jsonify({'error': str(e)}), 500  # Expõe detalhes

# SEGURO - mensagem genérica
@app.route('/api/data')
def get_data():
    try:
        result = 1 / 0
    except Exception as e:
        # Log o erro internamente
        app.logger.error(f"Erro: {str(e)}", exc_info=True)
        # Retorna mensagem genérica
        return jsonify({'error': 'Erro interno do servidor'}), 500

Desabilite directory listing, remova softwares desnecessários, aplique patches regularmente, mude defaults (senhas, portas, configurações), desabilite debug em produção e implemente logging de eventos de segurança.

7. Cross-Site Scripting (XSS)

XSS ocorre quando a aplicação inclui dados não confiáveis em páginas web sem validação ou escape apropriados. O atacante injeta código JavaScript que executa no navegador da vítima, roubando sessões, credentials ou malware.

Existem três tipos: Stored (armazenado no banco), Reflected (na URL/resposta) e DOM-based (manipulação de DOM via JavaScript).

XSS Reflected

# VULNERÁVEL - renderiza entrada sem escape
from flask import Flask, request

app = Flask(__name__)

@app.route('/search')
def search():
    query = request.args.get('q', '')
    # Se q for "<script>alert('XSS')</script>", ele executará
    return f"<h1>Resultados para: {query}</h1>"

Se alguém visitar /search?q=<script>alert('roubando cookies')</script>, o script executará.

A solução é fazer escape de HTML:

# SEGURO - faz escape de HTML
from flask import Flask, request, escape

app = Flask(__name__)

@app.route('/search')
def search():
    query = request.args.get('q', '')
    # escape converte < para &lt;, > para &gt;, etc
    safe_query = escape(query)
    return f"<h1>Resultados para: {safe_query}</h1>"

Em templates Jinja2, use {{ variable }} em vez de {{ variable|safe }}:

<!-- VULNERÁVEL -->
<h1>{{ user_input|safe }}</h1>

<!-- SEGURO - Jinja2 faz escape automaticamente -->
<h1>{{ user_input }}</h1>

XSS Stored

# VULNERÁVEL - armazena e renderiza sem escape
@app.route('/comment', methods=['POST'])
def add_comment():
    comment = request.form.get('text')
    # Armazena o que o usuário escreveu
    save_to_db(comment)
    return redirect('/page')

@app.route('/page')
def show_page():
    comments = get_from_db()
    html = ""
    for comment in comments:
        # Renderiza sem escape - XSS armazenado
        html += f"<p>{comment}</p>"
    return html

A solução é fazer escape na renderização:

from flask import Flask, escape

# Armazene como está
@app.route('/comment', methods=['POST'])
def add_comment():
    comment = request.form.get('text')
    save_to_db(comment)
    return redirect('/page')

# Faça escape na renderização
@app.route('/page')
def show_page():
    comments = get_from_db()
    html = ""
    for comment in comments:
        # escape previne execução
        html += f"<p>{escape(comment)}</p>"
    return html

Valide e faça escape de todo input do usuário antes de renderizar em HTML. Use Content Security Policy (CSP) para limitar fontes de scripts. Implemente sanitização de HTML se precisar permitir algumas tags (use bibliotecas como bleach).

8. Insecure Deserialization (Desserialização Insegura)

Desserialização é converter dados serializados (como JSON ou pickle) de volta em objetos. Se atacantes podem manipular dados serializados, podem criar objetos maliciosos que executam código no servidor.

Este é um problema especialmente crítico com pickle em Python:

# MUITO VULNERÁVEL - nunca use pickle com dados não confiáveis
import pickle

def load_user_data(serialized_data):
    # Se serialized_data for malicioso, pode executar código arbitrário
    user = pickle.loads(serialized_data)  # NUNCA FAÇA ISTO com dados do usuário
    return user

Pickle permite serializar qualquer objeto Python, incluindo funções. Um atacante pode criar um objeto que executa código ao ser desserializado:

# Como um atacante criaria payload malicioso
import pickle
import os

class Exploit:
    def __reduce__(self):
        # Executa comando ao desserializar
        return (os.system, ('rm -rf /',))

malicious_payload = pickle.dumps(Exploit())
# Se essa payload for desserializado, deleta tudo

A solução é usar JSON, que é seguro por natureza:

# SEGURO - use JSON
import json

def load_user_data(json_string):
    # JSON não permite execução de código arbitrário
    user = json.loads(json_string)
    return user

def save_user_data(user):
    return json.dumps(user)

Se você absolutamente precisa usar pickle, faça com dados que você controla. Se receber dados serializados de usuários, valide assinatura com HMAC:

# Se deve usar pickle, implemente assinatura
import pickle
import hmac
import hashlib

SECRET_KEY = 'sua-chave-secreta-muito-segura'

def serialize_safely(obj):
    serialized = pickle.dumps(obj)
    signature = hmac.new(SECRET_KEY.encode(), serialized, hashlib.sha256).digest()
    return serialized + signature

def deserialize_safely(data):
    serialized = data[:-32]  # Últimos 32 bytes são a assinatura
    signature = data[-32:]

    expected_signature = hmac.new(SECRET_KEY.encode(), serialized, hashlib.sha256).digest()

    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError("Dados foram modificados!")

    return pickle.loads(serialized)

Prefira JSON ou outros formatos seguros. Se deve desserializar, valide assinatura. Nunca desserialize dados de usuários sem verificação. Implemente object type allowlisting se possível.

9. Using Components with Known Vulnerabilities (Componentes com Vulnerabilidades Conhecidas)

Muitos projetos dependem de bibliotecas e frameworks de terceiros. Se essas dependências têm vulnerabilidades conhecidas, sua aplicação herda o risco. Manter dependências atualizadas é essencial.

Ferramentas como pip-audit em Python verificam vulnerabilidades em suas dependências:

# Instale pip-audit
pip install pip-audit

# Verifique vulnerabilidades nas suas dependências
pip-audit

# Output exemplo:
# Found 2 known security vulnerabilities in 2 packages
# Name: django
# Version: 2.1.0
# VULN-ID: GHSA-xxxx
# Fixed Version: 2.1.11

Mantenha um requirements.txt atualizado e revise regularmente:

# requirements.txt - verifique versões periodicamente
Flask==2.3.2
requests==2.31.0
cryptography==41.0.0
bcrypt==4.0.1

Crie um processo de atualização automático:

# Exemplo usando GitHub Dependabot
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    allow:
      - dependency-type: "all"

Monitore CVEs de suas dependências regularmente. Use pip-audit, OWASP Dependency-Check ou Snyk. Defina política de atualização: atualize patches de segurança imediatamente, updates regulares para versões menores, planejar major version updates. Considere usar containers com imagens atualizadas (docker pull regularmente).

10. Insufficient Logging and Monitoring (Logging e Monitoramento Insuficientes)

Sem logging adequado, você não consegue detectar ataques, investigar incidentes ou cumprir conformidade regulatória. Sem monitoramento, ataques acontecem silenciosamente.

Implementando Logging Seguro

import logging
from logging.handlers import RotatingFileHandler
import os

# Configure logging estruturado
def setup_logging(app):
    if not os.path.exists('logs'):
        os.mkdir('logs')

    # Registre eventos de segurança
    file_handler = RotatingFileHandler('logs/security.log', 
                                       maxBytes=10485760,  # 10MB
                                       backupCount=10)

    formatter = logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [%(pathname)s:%(lineno)d]'
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.WARNING)

    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.WARNING)

# Registre eventos importantes
from flask import Flask, request, session

app = Flask(__name__)
setup_logging(app)

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')

    if authenticate(username, password):
        app.logger.warning(f"Login bem-sucedido para usuário: {username}")
        session['user_id'] = get_user_id(username)
        return redirect('/')
    else:
        # Registre tentativa falhada
        app.logger.warning(f"Login falhou para usuário: {username} de IP: {request.remote_addr}")
        return "Login inválido", 401

@app.route('/api/users', methods=['POST'])
def create_user():
    # Registre mudanças sensíveis
    user_id = request.json.get('id')
    action_user = session.get('user_id')
    app.logger.warning(f"Usuário {action_user} criou novo usuário {user_id}")
    # ... criar usuário
    return {'status': 'criado'}

Monitoramento Proativo

# Alerte sobre comportamento suspeito
import time
from collections import defaultdict

# Rastreie tentativas de login falhadas
failed_logins = defaultdict(list)

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    remote_ip = request.remote_addr

    if not authenticate(username, password):
        failed_logins[remote_ip].append(time.time())

        # Se mais de 5 tentativas em 5 minutos, bloqueie
        recent_failures = [t for t in failed_logins[remote_ip] 
                          if time.time() - t < 300]

        if len(recent_failures) > 5:
            app.logger.critical(f"Possível brute force de IP: {remote_ip}")
            return "IP bloqueado por segurança", 429

        return "Login inválido", 401

Registre: falhas de autenticação, mudanças de permissões, acesso a dados sensíveis, mudanças de configuração, eventos de segurança. Use timestamps UTC precisos. Não registre senhas, tokens ou dados sensíveis. Centralizar logs em SIEM (Security Information and Event Management) como ELK Stack. Implemente alertas em tempo real para eventos críticos.

Conclusão

O OWASP Top 10 não é um conjunto estático de vulnerabilidades para memorizar, mas um reflexo das ameaças mais prevalentes encontradas em aplicações web reais. Neste artigo, cobri as dez categorias com exemplos funcionais que você pode implementar hoje: Injection é evitada com prepared statements, Broken Authentication com bcrypt e rate limiting, Sensitive Data Exposure com HTTPS e criptografia, XXE com defusedxml, Broken Access Control com validação server-side, Misconfiguration com headers de segurança, XSS com escape de HTML, Deserialization com JSON, Vulnerable Components com monitoramento de dependências e Insufficient Logging com eventos estruturados.

A segunda aprendizagem essencial é que segurança não é um checklist final, mas um processo contínuo. Vulnerabilidades são descobertas regularmente, frameworks são atualizados, novas técnicas de ataque surgem. Mantenha-se informado lendo CVEs, participando de comunidades de segurança e revisando código regularmente. A maioria dos ataques explora vulnerabilidades conhecidas que já foram documentadas e corrigidas — não ser vítima dessas vulnerabilidades é principalmente questão de diligência e implementação adequada.

Finalmente, lembre-se que segurança é responsabilidade de todo desenvolvedor, não apenas do time de segurança. Integre pensamento de segurança no seu ciclo de desenvolvimento desde o início: design seguro antes de codificar, code review focado em segurança, testes de segurança automatizados em CI/CD. Quando você trata segurança como parte integral do desenvolvimento, não como algo "bolado depois", você constrói aplicações significativamente mais robustas.

Referências


Artigos relacionados