Os 10 riscos mais críticos de segurança de aplicativos da Web e como reduzi-los.
O OWASP Top 10 é o documento de conscientização mais amplamente reconhecido para a segurança de aplicativos da Web. A edição de 2025 reflete o cenário de ameaças mais recente, introduzindo novas categorias, como Falhas na cadeia de suprimentos de software e Manuseio incorreto de condições excepcionais.
O controle de acesso aplica a política de modo que os usuários não possam agir fora das permissões pretendidas. As falhas geralmente levam à divulgação não autorizada de informações, à modificação ou destruição de dados ou à execução de uma função comercial fora dos limites do usuário.
Os invasores podem explorar as falhas de controle de acesso para acessar as contas de outros usuários, visualizar arquivos confidenciais, modificar os dados de outros usuários ou alterar os direitos de acesso.
# User ID taken directly from request without authorization check @app.route('/api/user/<user_id>/profile') def get_profile(user_id): user = db.get_user(user_id) # No ownership check! return jsonify(user.to_dict())
@app.route('/api/user/<user_id>/profile') @login_required def get_profile(user_id): # Verify the requesting user owns this resource if current_user.id != user_id and not current_user.is_admin: abort(403) user = db.get_user(user_id) if not user: abort(404) return jsonify(user.to_dict())
A configuração incorreta da segurança ocorre quando as configurações de segurança são definidas, implementadas ou mantidas incorretamente. Isso inclui a falta de reforço de segurança, recursos desnecessários ativados, contas padrão com senhas inalteradas, mensagens de erro excessivamente detalhadas e cabeçalhos de segurança HTTP mal configurados.
Servidores, estruturas ou serviços em nuvem mal configurados podem expor dados confidenciais, permitir acesso não autorizado ou fornecer aos invasores informações para planejar outros ataques.
# Debug mode enabled in production, verbose errors exposed app = Flask(__name__) app.config['DEBUG'] = True # Exposes stack traces! app.config['SECRET_KEY'] = 'default-secret' # Default key!
import os app = Flask(__name__) app.config['DEBUG'] = False app.config['SECRET_KEY'] = os.environ['SECRET_KEY'] @app.after_request def set_security_headers(response): response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['X-Frame-Options'] = 'DENY' response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' response.headers['Content-Security-Policy'] = "default-src 'self'" return response
Novo em 2025. Concentra-se nos riscos relacionados a componentes de terceiros, dependências e pipelines de CI/CD. Os invasores têm como alvo a cadeia de suprimentos de software comprometendo pacotes, injetando códigos mal-intencionados em bibliotecas de código aberto ou explorando vulnerabilidades de pipeline de compilação.
Uma única dependência comprometida pode afetar milhares de aplicativos. Os ataques à cadeia de suprimentos podem levar ao roubo de dados, à instalação de backdoors ou ao comprometimento total do sistema com detecção mínima.
// package.json with unpinned dependencies { "dependencies": { "express": "*", // Any version! "lodash": "^4.0.0", // Wide range "unknown-pkg": "^1.0.0" // Unvetted package } }
// package.json with pinned versions + lockfile + audit { "dependencies": { "express": "4.21.2", "lodash": "4.17.21" }, "scripts": { "preinstall": "npx npm-audit-resolver", "integrity-check": "npm audit signatures" } } // Also: use package-lock.json, enable Dependabot/Renovate, // generate SBOM, verify package provenance
Falhas relacionadas à criptografia que geralmente levam à exposição de dados confidenciais. Isso inclui o uso de algoritmos obsoletos, geração de chaves fracas, falta de criptografia para dados em trânsito ou em repouso e validação inadequada de certificados.
A criptografia fraca ou ausente pode expor senhas, números de cartão de crédito, registros de saúde, informações pessoais e segredos comerciais, o que pode levar a violações regulamentares (GDPR, PCI DSS).
import hashlib # Storing passwords with weak hashing def store_password(password: str) -> str: return hashlib.md5(password.encode()).hexdigest() # MD5 is broken!
import bcrypt def store_password(password: str) -> bytes: # Use bcrypt with automatic salting salt = bcrypt.gensalt(rounds=12) return bcrypt.hashpw(password.encode(), salt) def verify_password(password: str, hashed: bytes) -> bool: return bcrypt.checkpw(password.encode(), hashed)
As falhas de injeção ocorrem quando dados não confiáveis são enviados a um intérprete como parte de um comando ou consulta. Injeção de SQL, injeção de NoSQL, injeção de comando de sistema operacional e XSS (Cross-Site Scripting) são as formas mais comuns. Dados hostis podem induzir o intérprete a executar comandos não intencionais.
A injeção pode resultar em perda de dados, corrupção, acesso não autorizado, controle total do host ou negação de serviço. A injeção de SQL, por si só, continua sendo um dos vetores de ataque mais perigosos e predominantes.
# SQL injection via string concatenation def get_user(username: str): query = f"SELECT * FROM users WHERE name = '{username}'" return db.execute(query) # username = "' OR '1'='1"
# Parameterized queries prevent SQL injection def get_user(username: str): query = "SELECT * FROM users WHERE name = %s" return db.execute(query, (username,)) # For XSS prevention, escape output from markupsafe import escape def render_comment(comment: str) -> str: return f"<p>{escape(comment)}</p>"
O design inseguro refere-se aos riscos relacionados a falhas de design e arquitetura. Ele exige o uso de modelagem de ameaças, padrões de design seguro e arquiteturas de referência. Um projeto inseguro não pode ser corrigido por uma implementação perfeita; os controles de segurança necessários nunca foram criados para defender-se de ataques específicos.
As falhas de design podem levar a vulnerabilidades de lógica comercial que são difíceis de detectar com ferramentas automatizadas. A falta de limites de taxa em operações confidenciais, a falta de autenticação multifatorial para ações críticas ou a detecção insuficiente de fraudes são falhas no nível do projeto.
# Password reset with no rate limit or verification @app.route('/reset-password', methods=['POST']) def reset_password(): email = request.form['email'] new_pass = request.form['new_password'] user = db.find_by_email(email) user.password = hash_password(new_pass) # No token verification! db.save(user)
from datetime import datetime, timedelta @app.route('/reset-password', methods=['POST']) @rate_limit("3/hour") def reset_password(): token = request.form['token'] new_pass = request.form['new_password'] # Verify time-limited, single-use token reset_req = db.find_reset_token(token) if not reset_req or reset_req.used or reset_req.expires < datetime.utcnow(): abort(400, "Invalid or expired token") # Enforce password complexity if not meets_password_policy(new_pass): abort(400, "Password does not meet requirements") reset_req.user.password = hash_password(new_pass) reset_req.used = True db.save_all([reset_req.user, reset_req])
A confirmação da identidade, da autenticação e do gerenciamento de sessões de um usuário é fundamental. As falhas de autenticação ocorrem quando os aplicativos permitem o preenchimento de credenciais, força bruta, senhas fracas ou têm falhas no gerenciamento de sessões, como IDs de sessão não rotativos após o login.
Os invasores podem obter acesso às contas dos usuários por meio de preenchimento automático de credenciais, força bruta ou sequestro de sessão. Contas comprometidas podem levar a roubo de identidade, fraudes e violações de dados.
# No brute force protection, weak session handling @app.route('/login', methods=['POST']) def login(): user = db.find_by_email(request.form['email']) if user and user.password == request.form['password']: # Plain comparison! session['user'] = user.id # Session ID not rotated return redirect('/dashboard')
from flask_limiter import Limiter limiter = Limiter(app, default_limits=["100/hour"]) @app.route('/login', methods=['POST']) @limiter.limit("5/minute") def login(): user = db.find_by_email(request.form['email']) if not user or not bcrypt.checkpw( request.form['password'].encode(), user.password_hash ): # Generic error to prevent user enumeration return "Invalid credentials", 401 # Rotate session ID after authentication session.regenerate() session['user'] = user.id # Check for MFA requirement if user.mfa_enabled: return redirect('/mfa-verify') return redirect('/dashboard')
As falhas de integridade de software e dados estão relacionadas ao código e à infraestrutura que não protegem contra violações de integridade. Isso inclui desserialização insegura, uso de CDNs ou plug-ins não confiáveis sem verificação de integridade e mecanismos de atualização automática sem atualizações assinadas.
A desserialização insegura pode levar à execução remota de códigos. O carregamento de scripts de CDNs não confiáveis sem Subresource Integrity (SRI) pode permitir que os invasores injetem códigos mal-intencionados em seu aplicativo.
<!-- Loading external scripts without integrity checks --> <script src="https://cdn.example.com/lib.js"></script> <!-- Insecure deserialization in Python --> import pickle data = pickle.loads(user_input) # Arbitrary code execution!
<!-- Use Subresource Integrity (SRI) for external scripts --> <script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K..." crossorigin="anonymous"></script> # Use safe deserialization in Python import json data = json.loads(user_input) # Safe: only parses JSON data
Sem registros, monitoramento e alertas suficientes, as violações não podem ser detectadas em tempo hábil. O registro insuficiente, a integração ineficaz com sistemas de resposta a incidentes e a falta de alertas em tempo real permitem que os invasores ataquem ainda mais os sistemas, mantenham a persistência e adulterem ou extraiam dados.
Sem o registro adequado, os invasores podem operar sem serem detectados por longos períodos. A maioria dos estudos sobre violações mostra que o tempo médio para detectar uma violação é superior a 200 dias, geralmente descoberto por partes externas em vez de monitoramento interno.
# No logging of security-relevant events @app.route('/login', methods=['POST']) def login(): user = authenticate(request.form) if not user: return "Login failed", 401 # No record of failure return redirect('/dashboard')
import logging from datetime import datetime security_log = logging.getLogger('security') @app.route('/login', methods=['POST']) def login(): user = authenticate(request.form) if not user: security_log.warning( "LOGIN_FAILED | ip=%s | email=%s | time=%s", request.remote_addr, request.form.get('email', 'unknown'), datetime.utcnow().isoformat(), ) check_brute_force(request.remote_addr) return "Invalid credentials", 401 security_log.info( "LOGIN_SUCCESS | user_id=%s | ip=%s", user.id, request.remote_addr, ) return redirect('/dashboard')
Novo em 2025. Os aplicativos que tratam erros, exceções e casos extremos de forma inadequada podem expor informações confidenciais, entrar em estados inconsistentes ou criar condições exploráveis. Isso inclui mensagens de erro detalhadas na produção, exceções não capturadas que ignoram os controles de segurança e condições de corrida.
O tratamento inadequado de erros pode expor rastros de pilha, detalhes do banco de dados ou caminhos internos. As condições de corrida nas verificações de segurança podem permitir ataques do tipo tempo de verificação para tempo de uso (TOCTOU), contornando a autorização ou a validação de pagamento.
# Exposing internal details in error messages @app.route('/api/data') def get_data(): try: result = db.query(request.args['q']) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 # Leaks DB details!
import uuid, logging logger = logging.getLogger(__name__) @app.errorhandler(Exception) def handle_exception(e): error_id = str(uuid.uuid4()) logger.error("Unhandled exception [%s]: %s", error_id, e, exc_info=True) # Return generic error with reference ID for support return jsonify({ "error": "An internal error occurred", "reference": error_id, }), 500 @app.route('/api/data') def get_data(): q = request.args.get('q') if not q or not is_valid_query(q): return jsonify({"error": "Invalid query parameter"}), 400 result = db.query(q) return jsonify(result)
| ID | Vulnerabilidade | Gravidade | Principais medidas de mitigação |
|---|---|---|---|
| A01 | Controle de acesso quebrado | Critical | Verificações de acesso no lado do servidor, negação por padrão, propriedade de registros |
| A02 | Configuração incorreta da segurança | High | Processo de fortalecimento, cabeçalhos de segurança, desativação de padrões |
| A03 | Falhas na cadeia de suprimentos de software | High | Dependências fixas, SBOM, varredura de dependências |
| A04 | Falhas criptográficas | High | Algoritmos robustos, TLS 1.2+, gerenciamento de chaves |
| A05 | Injeção | Critical | Consultas parametrizadas, codificação de saída, validação de entrada |
| A06 | Design inseguro | High | Modelagem de ameaças, padrões seguros, testes de casos de abuso |
| A07 | Falhas de autenticação | High | MFA, proteção contra força bruta, rotação de sessão |
| A08 | Falhas de integridade de software ou de dados | High | SRI, atualizações assinadas, desserialização segura |
| A09 | Falhas nos registros e alertas de segurança | Medium | Registro centralizado, alertas em tempo real, resposta a incidentes |
| A10 | Manuseio incorreto de condições excepcionais | Medium | Manipulador de erros global, mensagens genéricas, prevenção de TOCTOU |