Skip to content

Instantly share code, notes, and snippets.

@uzielweb
Last active October 18, 2025 23:42
Show Gist options
  • Select an option

  • Save uzielweb/74b81766d2d2eaf67fb531431587e5a0 to your computer and use it in GitHub Desktop.

Select an option

Save uzielweb/74b81766d2d2eaf67fb531431587e5a0 to your computer and use it in GitHub Desktop.
Joomla Security Scanner - Ferramenta completa para detectar malware e verificar saúde dos arquivos do Joomla
<?php
/**
* Joomla Security Scanner
* Verifica a saúde dos arquivos do Joomla e detecta possíveis malwares
*
* DESCRIÇÃO:
* Este scanner realiza uma análise completa de segurança em instalações Joomla,
* detectando potenciais ameaças, vulnerabilidades e problemas de configuração.
*
* FUNCIONALIDADES:
* - Detecta mais de 40 assinaturas de malware conhecidas (shells, backdoors, webshells)
* - Identifica funções PHP perigosas (eval, base64_decode, system, exec, etc.)
* - Analisa código ofuscado e suspeito
* - Verifica permissões de arquivos críticos
* - Valida configurações de segurança (debug mode, error reporting)
* - Examina arquivos .htaccess para regras maliciosas
* - Detecta nomes de arquivos suspeitos (MD5 hashes, nomes curtos)
* - Verifica diretórios temporários e cache
* - Gera relatório detalhado em tempo real e em arquivo TXT
*
* COMO USAR:
* 1. Via CLI (recomendado): php joomla-security-scanner.php
* 2. Via navegador: acesse http://seusite.com/joomla-security-scanner.php
* (Configure senha nas linhas 477-502 para segurança)
*
* IMPORTANTE:
* - DELETE este arquivo após o uso por questões de segurança
* - Faça backup antes de remover arquivos suspeitos
* - Investigue manualmente cada ameaça detectada
* - Mantenha Joomla e extensões sempre atualizados
*
* @version 1.0
* @author Uziel Web (uzielweb)
* @date 2025-10-18
* @license MIT
* @link https://gist.github.com/uzielweb/74b81766d2d2eaf67fb531431587e5a0
*/
// Configurações
error_reporting(E_ALL);
ini_set('display_errors', 1);
set_time_limit(0);
ini_set('memory_limit', '512M');
// Caminho raiz do Joomla (ajuste conforme necessário)
define('JOOMLA_ROOT', dirname(__FILE__));
class JoomlaSecurityScanner {
private $results = [];
private $suspiciousFiles = [];
private $modifiedFiles = [];
private $permissionIssues = [];
private $malwareSignatures = [];
private $scannedFiles = 0;
private $startTime;
public function __construct() {
$this->startTime = microtime(true);
$this->initMalwareSignatures();
}
/**
* Inicializa assinaturas de malware conhecidas
*/
private function initMalwareSignatures() {
$this->malwareSignatures = [
// Funções perigosas
'eval\s*\(',
'base64_decode\s*\(',
'gzinflate\s*\(',
'gzuncompress\s*\(',
'str_rot13\s*\(',
'assert\s*\(',
'system\s*\(',
'exec\s*\(',
'shell_exec\s*\(',
'passthru\s*\(',
'proc_open\s*\(',
'popen\s*\(',
'curl_exec\s*\(',
'curl_multi_exec\s*\(',
'parse_ini_file\s*\(',
'show_source\s*\(',
// Padrões suspeitos
'\$_GET.*eval',
'\$_POST.*eval',
'\$_REQUEST.*eval',
'\$_COOKIE.*eval',
'move_uploaded_file',
'file_put_contents.*\$_(GET|POST|REQUEST)',
'fwrite.*\$_(GET|POST|REQUEST)',
'fputs.*\$_(GET|POST|REQUEST)',
// Backdoors conhecidos
'c99shell',
'r57shell',
'WSO\s*shell',
'FilesMan',
'Mysql\s*interface',
'phpspy',
'angel[\s_]?shell',
'haxor',
'h4x0r',
'c0derz',
'w4ck1ng',
// Codificação suspeita
'(?:[A-Za-z0-9+/]{4}){50,}={0,2}', // Base64 longo
'chr\s*\(\s*\d+\s*\)\s*\.',
'\\\x[0-9a-fA-F]{2}',
// Backdoor específico
'preg_replace.*\/e',
'create_function',
'\$\{["\']?_[A-Z]+\}',
'ob_get_contents',
// Funções de rede suspeitas
'fsockopen\s*\(',
'socket_create\s*\(',
'socket_connect\s*\(',
// Ofuscação
'@\$_\\\[\\\]',
'\$\{"\w+"\}',
'\$\{\$\w+\}',
// Webshells
'uploadfile',
'cmd\s*=',
'uname\s*-a',
'whoami',
'\/etc\/passwd',
];
}
/**
* Executa o scan completo
*/
public function scan() {
$this->log("=== JOOMLA SECURITY SCANNER ===", 'info');
$this->log("Iniciando scan em: " . JOOMLA_ROOT, 'info');
$this->log("Data/Hora: " . date('Y-m-d H:i:s'), 'info');
$this->log("", 'info');
// Verifica se é um diretório válido do Joomla
if (!$this->isJoomlaDirectory()) {
$this->log("ERRO: Diretório não parece ser uma instalação válida do Joomla!", 'error');
return false;
}
// Scan de arquivos
$this->log("1. Iniciando scan de arquivos...", 'info');
$this->scanDirectory(JOOMLA_ROOT);
// Verifica permissões
$this->log("2. Verificando permissões de arquivos críticos...", 'info');
$this->checkPermissions();
// Verifica configuration.php
$this->log("3. Analisando configuration.php...", 'info');
$this->checkConfiguration();
// Verifica arquivos .htaccess
$this->log("4. Verificando arquivos .htaccess...", 'info');
$this->checkHtaccess();
// Verifica diretórios temporários
$this->log("5. Verificando diretórios temporários...", 'info');
$this->checkTempDirectories();
// Gera relatório
$this->generateReport();
return true;
}
/**
* Verifica se é um diretório Joomla válido
*/
private function isJoomlaDirectory() {
$requiredFiles = [
'index.php',
'configuration.php',
];
$requiredDirs = [
'administrator',
'components',
'modules',
'plugins',
'templates',
];
foreach ($requiredFiles as $file) {
if (!file_exists(JOOMLA_ROOT . '/' . $file)) {
return false;
}
}
foreach ($requiredDirs as $dir) {
if (!is_dir(JOOMLA_ROOT . '/' . $dir)) {
return false;
}
}
return true;
}
/**
* Scan recursivo de diretórios
*/
private function scanDirectory($dir) {
$skipDirs = ['.git', '.svn', 'node_modules', 'cache'];
if (!is_dir($dir)) {
return;
}
$items = @scandir($dir);
if (!$items) {
return;
}
foreach ($items as $item) {
if ($item == '.' || $item == '..') {
continue;
}
$path = $dir . '/' . $item;
if (is_dir($path)) {
// Pula diretórios específicos
if (in_array($item, $skipDirs)) {
continue;
}
$this->scanDirectory($path);
} else {
$this->scanFile($path);
}
}
}
/**
* Scan de arquivo individual
*/
private function scanFile($file) {
$this->scannedFiles++;
// Mostra progresso a cada 100 arquivos
if ($this->scannedFiles % 100 == 0) {
$this->log("Arquivos escaneados: {$this->scannedFiles}", 'progress');
}
// Verifica apenas arquivos PHP
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'inc'])) {
return;
}
// Lê o conteúdo do arquivo
$content = @file_get_contents($file);
if ($content === false) {
$this->permissionIssues[] = [
'file' => $file,
'issue' => 'Não foi possível ler o arquivo'
];
return;
}
// Verifica assinaturas de malware
$threats = $this->detectMalware($content, $file);
if (!empty($threats)) {
$this->suspiciousFiles[] = [
'file' => $file,
'threats' => $threats,
'size' => filesize($file),
'modified' => date('Y-m-d H:i:s', filemtime($file))
];
}
// Verifica arquivos ocultos ou suspeitos
$this->checkSuspiciousFilenames($file);
// Verifica permissões
$this->checkFilePermissions($file);
}
/**
* Detecta malware no conteúdo do arquivo
*/
private function detectMalware($content, $file) {
$threats = [];
foreach ($this->malwareSignatures as $pattern) {
// Usa @ para suprimir warnings de regex inválida e continua
if (@preg_match('/' . $pattern . '/i', $content, $matches)) {
$threats[] = [
'pattern' => $pattern,
'match' => isset($matches[0]) ? substr($matches[0], 0, 100) : ''
];
}
}
// Verifica shells conhecidos por conteúdo
if (stripos($content, 'c99shell') !== false) {
$threats[] = ['pattern' => 'c99shell', 'match' => 'C99 Shell detectado'];
}
if (stripos($content, 'r57shell') !== false) {
$threats[] = ['pattern' => 'r57shell', 'match' => 'R57 Shell detectado'];
}
// Verifica funções perigosas múltiplas
$dangerousFunctions = ['eval', 'base64_decode', 'gzinflate', 'system', 'exec'];
$dangerCount = 0;
foreach ($dangerousFunctions as $func) {
if (preg_match_all('/' . $func . '\s*\(/i', $content) > 2) {
$dangerCount++;
}
}
if ($dangerCount >= 3) {
$threats[] = ['pattern' => 'multiple_dangerous', 'match' => 'Múltiplas funções perigosas detectadas'];
}
return $threats;
}
/**
* Verifica nomes de arquivos suspeitos
*/
private function checkSuspiciousFilenames($file) {
$basename = basename($file);
$suspicious = [
'/^[a-f0-9]{32}\.php$/', // MD5 hash como nome
'/^[a-z]{1,2}\.php$/', // Nomes muito curtos
'/^\d+\.php$/', // Apenas números
'/^(temp|tmp|cache).*\.php$/i',
'/^(shell|backdoor|hack|exploit).*\.php$/i',
'/^\..*\.php$/', // Começa com ponto
'/^(wso|c99|r57|b374k).*\.php$/i',
];
foreach ($suspicious as $pattern) {
if (preg_match($pattern, $basename)) {
$this->suspiciousFiles[] = [
'file' => $file,
'threats' => [['pattern' => 'suspicious_filename', 'match' => 'Nome de arquivo suspeito']],
'size' => filesize($file),
'modified' => date('Y-m-d H:i:s', filemtime($file))
];
break;
}
}
}
/**
* Verifica permissões de arquivo
*/
private function checkFilePermissions($file) {
$perms = fileperms($file);
$octal = substr(sprintf('%o', $perms), -4);
// Verifica se está gravável pelo mundo
if ($perms & 0x0002) {
$this->permissionIssues[] = [
'file' => $file,
'issue' => "Gravável por todos ({$octal})",
'permissions' => $octal
];
}
// Verifica se está executável
if ($perms & 0x0040) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml'])) {
$this->permissionIssues[] = [
'file' => $file,
'issue' => "Arquivo PHP executável ({$octal})",
'permissions' => $octal
];
}
}
}
/**
* Verifica permissões de arquivos críticos
*/
private function checkPermissions() {
$criticalFiles = [
'configuration.php',
'.htaccess',
'administrator/.htaccess',
];
foreach ($criticalFiles as $file) {
$fullPath = JOOMLA_ROOT . '/' . $file;
if (file_exists($fullPath)) {
$perms = fileperms($fullPath);
$octal = substr(sprintf('%o', $perms), -4);
if ($octal != '0444' && $octal != '0644') {
$this->log("AVISO: {$file} tem permissões {$octal} (recomendado: 0444)", 'warning');
}
}
}
}
/**
* Verifica configuration.php
*/
private function checkConfiguration() {
$configFile = JOOMLA_ROOT . '/configuration.php';
if (!file_exists($configFile)) {
$this->log("ERRO: configuration.php não encontrado!", 'error');
return;
}
$content = file_get_contents($configFile);
// Verifica se há código suspeito
$threats = $this->detectMalware($content, $configFile);
if (!empty($threats)) {
$this->log("ALERTA: configuration.php contém código suspeito!", 'error');
$this->suspiciousFiles[] = [
'file' => $configFile,
'threats' => $threats,
'size' => filesize($configFile),
'modified' => date('Y-m-d H:i:s', filemtime($configFile))
];
}
// Verifica configurações de segurança
if (preg_match('/public \$debug = \'?1\'?;/', $content)) {
$this->log("AVISO: Debug mode está ativado no configuration.php", 'warning');
}
if (preg_match('/public \$error_reporting = \'?development\'?;/i', $content)) {
$this->log("AVISO: Error reporting está em modo development", 'warning');
}
}
/**
* Verifica arquivos .htaccess
*/
private function checkHtaccess() {
$htaccessFiles = [
JOOMLA_ROOT . '/.htaccess',
JOOMLA_ROOT . '/administrator/.htaccess',
];
foreach ($htaccessFiles as $file) {
if (file_exists($file)) {
$content = file_get_contents($file);
// Verifica regras suspeitas
if (preg_match('/RewriteRule.*\.(jpg|jpeg|gif|png|ico).*\.php/i', $content)) {
$this->log("ALERTA: Regra suspeita em {$file} - redireciona imagens para PHP", 'error');
}
if (preg_match('/auto_prepend_file|auto_append_file/i', $content)) {
$this->log("ALERTA: Configuração suspeita em {$file} - auto_prepend/append_file", 'error');
}
}
}
}
/**
* Verifica diretórios temporários
*/
private function checkTempDirectories() {
$tempDirs = [
JOOMLA_ROOT . '/tmp',
JOOMLA_ROOT . '/cache',
JOOMLA_ROOT . '/administrator/cache',
];
foreach ($tempDirs as $dir) {
if (is_dir($dir)) {
$this->scanDirectory($dir);
}
}
}
/**
* Gera relatório final
*/
private function generateReport() {
$endTime = microtime(true);
$duration = round($endTime - $this->startTime, 2);
$this->log("", 'info');
$this->log("=== RELATÓRIO FINAL ===", 'info');
$this->log("Tempo de execução: {$duration} segundos", 'info');
$this->log("Total de arquivos escaneados: {$this->scannedFiles}", 'info');
$this->log("", 'info');
// Arquivos suspeitos
$this->log("ARQUIVOS SUSPEITOS ENCONTRADOS: " . count($this->suspiciousFiles), 'info');
if (!empty($this->suspiciousFiles)) {
foreach ($this->suspiciousFiles as $item) {
$this->log("", 'warning');
$this->log("Arquivo: " . str_replace(JOOMLA_ROOT, '', $item['file']), 'warning');
$this->log("Tamanho: " . $item['size'] . " bytes", 'warning');
$this->log("Modificado: " . $item['modified'], 'warning');
$this->log("Ameaças detectadas:", 'warning');
foreach ($item['threats'] as $threat) {
$this->log(" - Padrão: {$threat['pattern']}", 'warning');
if (!empty($threat['match'])) {
$this->log(" Match: " . htmlspecialchars(substr($threat['match'], 0, 100)), 'warning');
}
}
}
} else {
$this->log("Nenhum arquivo suspeito detectado!", 'success');
}
$this->log("", 'info');
// Problemas de permissão
$this->log("PROBLEMAS DE PERMISSÃO: " . count($this->permissionIssues), 'info');
if (!empty($this->permissionIssues)) {
$shown = 0;
foreach ($this->permissionIssues as $item) {
if ($shown >= 20) {
$remaining = count($this->permissionIssues) - $shown;
$this->log("... e mais {$remaining} problemas de permissão", 'info');
break;
}
$this->log("Arquivo: " . str_replace(JOOMLA_ROOT, '', $item['file']), 'warning');
$this->log("Problema: " . $item['issue'], 'warning');
$shown++;
}
} else {
$this->log("Nenhum problema de permissão crítico detectado!", 'success');
}
$this->log("", 'info');
// Recomendações
$this->log("=== RECOMENDAÇÕES ===", 'info');
$this->log("1. Investigue todos os arquivos suspeitos listados acima", 'info');
$this->log("2. Faça backup antes de remover qualquer arquivo", 'info');
$this->log("3. Mantenha o Joomla e extensões sempre atualizados", 'info');
$this->log("4. Use senhas fortes e autenticação de dois fatores", 'info');
$this->log("5. Configure permissões adequadas (644 para arquivos, 755 para diretórios)", 'info');
$this->log("6. Considere usar extensões de segurança como Akeeba Admin Tools ou RSFirewall", 'info');
// Salva relatório em arquivo
$this->saveReport();
}
/**
* Salva relatório em arquivo
*/
private function saveReport() {
$reportFile = JOOMLA_ROOT . '/security-scan-' . date('Y-m-d-His') . '.txt';
ob_start();
echo "=== JOOMLA SECURITY SCAN REPORT ===\n";
echo "Data: " . date('Y-m-d H:i:s') . "\n";
echo "Diretório: " . JOOMLA_ROOT . "\n\n";
echo "RESUMO:\n";
echo "- Arquivos escaneados: {$this->scannedFiles}\n";
echo "- Arquivos suspeitos: " . count($this->suspiciousFiles) . "\n";
echo "- Problemas de permissão: " . count($this->permissionIssues) . "\n\n";
if (!empty($this->suspiciousFiles)) {
echo "ARQUIVOS SUSPEITOS:\n";
echo str_repeat("=", 80) . "\n";
foreach ($this->suspiciousFiles as $item) {
echo "\nArquivo: {$item['file']}\n";
echo "Tamanho: {$item['size']} bytes\n";
echo "Modificado: {$item['modified']}\n";
echo "Ameaças:\n";
foreach ($item['threats'] as $threat) {
echo " - {$threat['pattern']}\n";
if (!empty($threat['match'])) {
echo " " . substr($threat['match'], 0, 100) . "\n";
}
}
}
}
if (!empty($this->permissionIssues)) {
echo "\n\nPROBLEMAS DE PERMISSÃO:\n";
echo str_repeat("=", 80) . "\n";
foreach ($this->permissionIssues as $item) {
echo "{$item['file']} - {$item['issue']}\n";
}
}
$reportContent = ob_get_clean();
file_put_contents($reportFile, $reportContent);
$this->log("", 'info');
$this->log("Relatório completo salvo em: {$reportFile}", 'success');
}
/**
* Log de mensagens
*/
private function log($message, $type = 'info') {
$colors = [
'info' => "\033[0m", // Normal
'success' => "\033[32m", // Verde
'warning' => "\033[33m", // Amarelo
'error' => "\033[31m", // Vermelho
'progress' => "\033[36m", // Ciano
];
$reset = "\033[0m";
// Se não estiver no CLI, não usa cores
if (php_sapi_name() !== 'cli') {
echo htmlspecialchars($message) . "<br>\n";
} else {
$color = isset($colors[$type]) ? $colors[$type] : $colors['info'];
echo $color . $message . $reset . "\n";
}
flush();
}
}
// Execução
if (php_sapi_name() === 'cli') {
// Modo CLI
echo "\n";
$scanner = new JoomlaSecurityScanner();
$scanner->scan();
echo "\n";
} else {
// Modo web - adiciona proteção básica
// DESCOMENTE AS LINHAS ABAIXO E CONFIGURE UMA SENHA
/*
session_start();
$validPassword = 'SUA_SENHA_AQUI'; // MUDE ISSO!
if (!isset($_SESSION['authenticated'])) {
if (isset($_POST['password']) && $_POST['password'] === $validPassword) {
$_SESSION['authenticated'] = true;
} else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Joomla Security Scanner - Login</title>
<style>
body { font-family: Arial, sans-serif; margin: 50px; }
input { padding: 5px; margin: 5px; }
</style>
</head>
<body>
<h2>Joomla Security Scanner</h2>
<form method="post">
<input type="password" name="password" placeholder="Senha" required>
<button type="submit">Acessar</button>
</form>
</body>
</html>
<?php
exit;
}
}
*/
// HTML output
?>
<!DOCTYPE html>
<html>
<head>
<title>Joomla Security Scanner</title>
<meta charset="UTF-8">
<style>
body {
font-family: 'Courier New', monospace;
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: #252526;
padding: 20px;
border-radius: 5px;
}
h1 {
color: #4ec9b0;
border-bottom: 2px solid #4ec9b0;
padding-bottom: 10px;
}
.info { color: #d4d4d4; }
.success { color: #4ec9b0; }
.warning { color: #dcdcaa; }
.error { color: #f48771; }
.progress { color: #9cdcfe; }
pre {
background: #1e1e1e;
padding: 15px;
border-radius: 3px;
overflow-x: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>🔍 Joomla Security Scanner</h1>
<pre><?php
$scanner = new JoomlaSecurityScanner();
$scanner->scan();
?></pre>
</div>
</body>
</html>
<?php
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment