Você já deve ter visto o apocalipse no LinkedIn: "Programadores juniores estão gerando sistemas inteiros com IA e o vazamento de dados é questão de tempo". Mas será que isso é apenas protecionismo de mercado ou uma realidade técnica?
Na Union Insights, decidimos parar de supor e começar a testar. Pedimos para uma IA gerar um núcleo de autenticação SaaS (Multi-tenant) em PHP puro. Visualmente? Lindo. Código limpo. Moderno.
Mas passamos um pente fino. O resultado? A IA acertou onde muitos humanos erram, mas cometeu um erro silencioso que poderia custar a sua empresa.
Auditoria: O que a IA acertou?
Blindagem de Cookies (10/10)
A configuração foi militar. httponly impede XSS. samesite: Lax impede CSRF. E ainda criou isolamento de Tenant por subdomínio.
Headers de Proteção (9/10)
Headers corretos (X-Frame-Options, etc) protegendo contra Clickjacking e MIME sniffing.
Validação de User Agent (8/10)
Se o navegador mudar durante a sessão, o usuário é deslogado. Ótimo contra Session Hijacking.
A Zona de Perigo: Onde a IA falhou?
Aqui entra o argumento de que "você precisa saber o que está pedindo". Olhe para o código ao lado.
1. O "Kill Switch" desligado (Risco Alto)
A IA implementou a lógica perfeita para derrubar sessões duplicadas. O problema? A linha está comentada! (Veja o comentário // logoutAndRedirect no código).
2. A Porta dos Fundos via URL (Risco Médio)
Olhe a linha if (!isset($_GET['ajax_check'])).
Se um atacante adicionar ?ajax_check=1 na URL, o sistema pula a verificação de segurança do banco. Nunca confie em parâmetros GET para segurança.
3. Tratamento de Erro Silencioso
O bloco catch apenas faz um log. Se o banco cair, o sistema entra em modo Fail-Open (permite acesso) ao invés de Fail-Closed (bloqueia).
Union Allied Team
Auditoria Finalizada
<?php
// auth.php - Núcleo de Segurança da Sessão (Multi-tenant Reforçado)
if (!defined('BASE_URL')) {
require_once __DIR__ . '/db.php';
}
// --- 1. CONFIGURAÇÃO DE COOKIE INTELIGENTE ---
$currentDomain = $_SERVER['HTTP_HOST'];
$cleanDomain = str_replace('www.', '', $currentDomain);
// Verifica se é localhost ou IP (ex: 127.0.0.1) para não quebrar o cookie
// Browsers modernos rejeitam cookies com 'domain' setado em localhost
$isLocalhost = ($cleanDomain === 'localhost' || filter_var($cleanDomain, FILTER_VALIDATE_IP));
$cookieDomain = $isLocalhost ? null : $cleanDomain;
// Nome da sessão único por Tenant
$sessionName = 'SESS_SAAS_' . strtoupper(preg_replace('/[^a-zA-Z0-9]/', '', explode('.', $cleanDomain)[0]));
session_name($sessionName);
// Configuração segura dos parâmetros
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => $cookieDomain,
'secure' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'),
'httponly' => true,
'samesite' => 'Lax'
]);
// Só inicia se ainda não estiver ativa
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// --- 2. CABEÇALHOS DE SEGURANÇA ---
if (!headers_sent()) {
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
}
// --- 3. CONFIGURAÇÕES DE TEMPO ---
define('INACTIVITY_LIMIT', 1800);
// --- 4. FUNÇÃO PRINCIPAL ---
function requireLogin() {
global $pdo;
if (!isset($pdo)) require_once __DIR__ . '/db.php';
// 1. Validação Básica
if (!isset($_SESSION['user']) || empty($_SESSION['user']['id'])) {
logoutAndRedirect();
}
// 2. Timeout
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > INACTIVITY_LIMIT)) {
logoutAndRedirect('expired=true');
}
$_SESSION['LAST_ACTIVITY'] = time();
// 3. User Agent
$ua = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
if (!isset($_SESSION['USER_AGENT'])) {
$_SESSION['USER_AGENT'] = $ua;
} elseif ($_SESSION['USER_AGENT'] !== $ua) {
logoutAndRedirect();
}
// 4. Single Session Check (Ajax skip para performance se necessário)
// ⚠️ FALHA CRÍTICA AQUI ⚠️
if (!isset($_GET['ajax_check'])) {
try {
$stmt = $pdo->prepare("SELECT session_token FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user']['id']]);
$dbToken = $stmt->fetchColumn();
// ⚠️ CÓDIGO COMENTADO - KILL SWITCH DESLIGADO ⚠️
if ($dbToken && $dbToken !== session_id()) {
// logoutAndRedirect('kicked=true');
}
} catch (Exception $e) {
// Log silencioso
}
}
// 5. Force Change Password
if (isset($_SESSION['force_change']) && $_SESSION['force_change'] === true) {
$scriptAtual = basename($_SERVER['PHP_SELF']);
$permitidos = ['perfil.php', 'logout.php', 'update_password.php', 'save_perfil.php'];
if (!in_array($scriptAtual, $permitidos)) {
// Tratamento para API
$isApiCall = (strpos($_SERVER['REQUEST_URI'], '/api/') !== false) ||
(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
if ($isApiCall) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'force_change', 'message' => 'Troca de senha obrigatória']);
exit;
}
header("Location: " . BASE_URL . "/pages/perfil.php?warning=force_change");
exit;
}
}
}
// --- 5. LOGOUT ---
function logoutAndRedirect($query = '') {
// 1. Limpa o array da sessão
$_SESSION = [];
// 2. Mata o Cookie da Sessão (Com configurações agressivas para garantir a exclusão)
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
setcookie(session_name(), '', time() - 42000, '/');
}
// 3. Destrói a sessão no servidor
session_destroy();
$redirectUrl = BASE_URL . '/_auth/login.php';
if (!empty($query)) {
$connector = (strpos($redirectUrl, '?') !== false) ? '&' : '?';
$redirectUrl .= $connector . $query;
}
header("Location: " . $redirectUrl);
exit;
}
?>
AI Generated • Human Audited
Veredito da Auditoria
🔧 Correção Cirúrgica
- Descomente a linha 85: Faça a expulsão (logout) acontecer de verdade.
- Remova o bypass via GET: Use verificação baseada em tempo (session timestamp) para performance, nunca input do usuário.
- Implemente Fail-Closed: Se der Exception no banco, deslogue o usuário imediatamente.
No próximo post: login.php. Será que a IA protegeu contra Brute Force? Fique ligado.
System Buffer Empty
Waiting for input stream...