IA vs Segurança (Parte 2): A Ilusão da Segurança Visual .
A Ilusão da Segurança Visual
Quando o Backend esconde segredos obscuros sob uma interface perfeitamente polida.
Se tem uma coisa que as Inteligências Artificiais aprenderam a fazer muito bem, é impressionar os olhos. O arquivo login.php que estamos auditando hoje é um excelente exemplo disso. Ao abrirmos a página, somos recebidos por uma interface construída com Tailwind CSS, animações fluidas, efeitos glassmorphism e cores dinâmicas. Pareceria impecável... se não fôssemos auditar o motor debaixo do capô.
Em nossa segunda análise da série IA vs Segurança, vamos dissecar um script que mistura acertos brilhantes com falhas arquitetônicas e um "pecado capital" na verificação de senhas.
Os Acertos: A IA Fez a Lição de Casa
O código gerado implementa conceitos avançados que muitos desenvolvedores esquecem:
login_attempts.
session_regenerate_id() logo após o login.
O Perigo Mora no "Fallback"
Apesar dos acertos, a IA cometeu um erro clássico. O código tenta usar password_verify(). Porém, se isso falhar, ele faz um fallback e aceita validar a senha em MD5/SHA256 ou até mesmo em texto puro.
Geralmente, a IA gera esse tipo de código para lidar com "sistemas legados". Mas em um ambiente de produção moderno, isso é uma porta escancarada.
Atenção extra: A arquitetura Spaghetti dificulta a manutenção, e a captura de IP via $_SERVER['REMOTE_ADDR'] é ineficaz se a aplicação estiver atrás de um Cloudflare ou Proxy.
Na próxima semana...
Vamos mergulhar no coração dessa aplicação analisando o arquivo db.php. A IA configurou o PDO corretamente? Fique ligado!
Union Allied Team
Auditoria Finalizada
<?php
// _auth/login.php
ob_start(); // [AUDITORIA] Ótima prática. Previne o erro "Headers already sent" ao manipular redirecionamentos.
require_once __DIR__ . '/../_config/db.php';
require_once __DIR__ . '/../_config/auth.php';
// Carrega configurações do banco pois a sessão ainda não existe
$sysConfig = [];
try {
$stmtConfig = $pdo->query("SELECT * FROM configuracoes WHERE id = 1 LIMIT 1");
$dbConfig = $stmtConfig->fetch(PDO::FETCH_ASSOC);
if ($dbConfig) {
$sysConfig = $dbConfig;
}
} catch (Exception $e) {
// [AUDITORIA ALERTA] Bloco catch vazio (Silent Failure). Se o banco cair, a UI carrega "quebrada" ou com defaults, mas o erro é mascarado.
}
// Redireciona se já logado
if (isset($_SESSION['user']['id'])) {
header("Location: " . BASE_URL . "/_pages/dashboard.php");
exit;
}
// Configurações Visuais & Mapeamento (omitidas para brevidade no comentário)
$nomeClinica = !empty($sysConfig['nome_clinica']) ? $sysConfig['nome_clinica'] : 'Sistema Médico';
$logoDb = $sysConfig['logo_path'] ?? '';
$logoPath = !empty($logoDb) ? '../' . $logoDb : '';
$corTema = !empty($sysConfig['cor_tema']) ? $sysConfig['cor_tema'] : '#4f46e5';
$corSide1 = !empty($sysConfig['sidebar_cor_1']) ? $sysConfig['sidebar_cor_1'] : '#312e81';
$corSide2 = !empty($sysConfig['sidebar_cor_2']) ? $sysConfig['sidebar_cor_2'] : '#4338ca';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// [AUDITORIA] Excelente. Sanitiza o e-mail logo na entrada.
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$senha = $_POST['senha'] ?? '';
// [AUDITORIA ALERTA] 'REMOTE_ADDR' pega o IP local se estiver atrás de um Load Balancer/Cloudflare. Requer tratamento com X-Forwarded-For.
$ip = $_SERVER['REMOTE_ADDR'];
try {
// 1. Anti-Brute Force
// [AUDITORIA] Excelente lógica de bloqueio baseada no tempo e IP.
$stmtCheck = $pdo->prepare("SELECT COUNT(*) FROM login_attempts WHERE ip_address = ? AND attempt_time > (NOW() - INTERVAL 15 MINUTE)");
$stmtCheck->execute([$ip]);
if ($stmtCheck->fetchColumn() >= 5) {
$error = 'Muitas tentativas. Aguarde 15 minutos.';
} elseif (empty($email) || empty($senha)) {
$error = 'Preencha todos os campos.';
} else {
// 2. Busca Usuário
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? LIMIT 1");
$stmt->execute([$email]);
$user = $stmt->fetch();
// 3. Verifica Senha
$senhaCorreta = false;
if ($user) {
$stored = $user['password'];
// [AUDITORIA CRÍTICA] Aqui está o problema! A IA criou um "fallback".
// Se não for bcrypt ($2y$), ela tenta SHA256 e, pior, texto puro ($senha === $stored).
// Isso nunca deve existir em produção moderna.
if (strpos($stored, '$2y$') === 0 || strpos($stored, '$2b$') === 0) {
if (password_verify($senha, $stored)) $senhaCorreta = true;
} elseif (hash('sha256', $senha) === $stored) {
$senhaCorreta = true;
} elseif ($senha === $stored) {
$senhaCorreta = true;
}
}
if ($user && $senhaCorreta) {
// --- LOGIN BEM SUCEDIDO ---
if ((int)$user['is_active'] === 0) {
$error = 'Conta desativada.';
} else {
// Limpa brute force
$pdo->prepare("DELETE FROM login_attempts WHERE ip_address = ?")->execute([$ip]);
// Prepara Sessão
unset($user['password']); // [AUDITORIA] Ótimo! Nunca trafegar o hash na $_SESSION.
$_SESSION['user'] = $user;
// --- Geração de Token ---
session_regenerate_id(true); // [AUDITORIA] Impede ataque de Session Fixation. Brilhante.
$newSessionId = session_id();
// Grava o token no banco
$pdo->prepare("UPDATE users SET session_token = ? WHERE id = ?")->execute([$newSessionId, $user['id']]);
// Logs e Limpeza
$pdo->prepare("UPDATE login_logs SET status = 'offline', logout_at = NOW() WHERE user_id = ? AND status IN ('active','online')")->execute([$user['id']]);
$stmtLog = $pdo->prepare("INSERT INTO login_logs (user_id, ip_address, user_agent, status) VALUES (?, ?, ?, 'active')");
$stmtLog->execute([$user['id'], $ip, $_SERVER['HTTP_USER_AGENT']]);
$_SESSION['login_log_id'] = $pdo->lastInsertId();
// --- TRATAMENTO DE REDIRECIONAMENTO ---
$redirectUrl = BASE_URL . '/_pages/dashboard.php';
// Roles Específicas
if (($user['role'] ?? '') === 'medico') {
try {
$stmtMed = $pdo->prepare("SELECT id, especialidade FROM medicos WHERE email = ?");
$stmtMed->execute([$user['email']]);
$medData = $stmtMed->fetch();
if($medData) $_SESSION['medico_id'] = $medData['id'];
} catch(Exception $e){} // [AUDITORIA ALERTA] Mais um catch vazio.
$redirectUrl = BASE_URL . '/_pages/_medico/dashboard.php';
}
// Forçar troca de senha (prioridade sobre dashboard)
if ((int)($user['force_change_password'] ?? 0) === 1) {
$_SESSION['force_change'] = true;
$redirectUrl = BASE_URL . "/_pages/perfil.php?msg=troca_obrigatoria";
}
session_write_close(); // [AUDITORIA] Destrava a sessão para requisições paralelas. Excelente em sistemas pesados.
header('Location: ' . $redirectUrl);
exit;
}
} else {
$error = 'Credenciais inválidas.';
$pdo->prepare("INSERT INTO login_attempts (ip_address) VALUES (?)")->execute([$ip]);
}
}
} catch (Exception $e) {
error_log("Login System Error: " . $e->getMessage()); // [AUDITORIA] Boa prática registrar em log interno em vez de expor ao usuário.
$error = "Erro interno. Tente novamente.";
}
}
ob_end_flush();
?>
<!-- O restante do código é HTML/Tailwind e não possui falhas lógicas de segurança. -->
AI Generated • Human Audited
Veredito da Auditoria
"Uma interface deslumbrante escondendo um pecado capital. A validação de senhas compromete um trabalho que tinha tudo para ser perfeito."
Pontos Fortes
-
Proteção Anti-Brute Force por IP
-
Prevenção robusta de Session Fixation
-
Tratamento visual premium (Tailwind)
Vulnerabilidades
-
Fallback para senhas em texto puro
-
Captura de IP frágil (vulnerável a Proxies)
-
Blocos
catchsilenciosos mascaram erros
System Buffer Empty
Waiting for input stream...