/
jromka
/
shell_scan
Обзор
Документация
Войти
/
jromka
/
shell_scan
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
rv.php
535 строк
20 KB
jromka
update rv.php
29 май 2025, 17:17
29 май 2025, 17:17
9e0604a
Код
Авторство
О чём код?
<?php session_start(); // Конфигурация безопасности define('ANTIVIRUS_KEY', '12345'); // Замените на свой секретный ключ define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5MB define('QUARANTINE_DIR', '__quarantine'); define('LOG_FILE', 'antivirus.log'); // Проверка доступа if (!isset($_GET['key']) || $_GET['key'] !== ANTIVIRUS_KEY) { header('HTTP/1.0 403 Forbidden'); die('Access denied. Invalid security key.'); } ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); ini_set('memory_limit', '2058M'); set_time_limit(1600); $rootDir = __DIR__; date_default_timezone_set('Europe/Moscow'); $msg = $msgType = ''; // Генерация CSRF-токена if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } function logMessage($message) { $timestamp = date('[Y-m-d H:i:s]'); file_put_contents(LOG_FILE, $timestamp . ' ' . $message . PHP_EOL, FILE_APPEND); } function formatRussianDate($timestamp) { $months = [ 1 => 'января', 2 => 'февраля', 3 => 'марта', 4 => 'апреля', 5 => 'мая', 6 => 'июня', 7 => 'июля', 8 => 'августа', 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря' ]; return date('j', $timestamp) . ' ' . $months[(int)date('n', $timestamp)] . ' ' . date('Y в H:i', $timestamp); } function isWhitelisted($filePath, $whitelist) { foreach ($whitelist as $pattern) { if (preg_match('#' . $pattern . '#i', $filePath)) { return true; } } return false; } function isFalsePositive($code) { // Примеры "безопасных" шаблонов — подойдут для исключения некоторых ложных срабатываний $safePatterns = [ '/echo\s+["\']<script.*?><\/script>["\']?/is', '/base64_encode\s*\(/i', // base64_encode — обычно не вредоносно, а base64_decode — да '/json_encode\s*\(/i', '/array\s*\(/i', '/shell_exec\s*\(\s*["\'].*["\']\s*\)/i', // вызов с фиксированной строкой ]; foreach ($safePatterns as $pattern) { if (preg_match($pattern, $code)) { return true; } } return false; } function getSeverityLevel($pattern) { $levels = [ 'critical' => [ '/\beval\s*\(/i', '/base64_decode\s*\(/i', '/shell_exec\s*\(/i', ], 'medium' => [ '/create_function\s*\(/i', '/gzinflate\s*\(/i', '/gzuncompress\s*\(/i', ], 'low' => [ '/str_rot13\s*\(/i', '/array\s*\(/i', ], ]; foreach ($levels as $level => $patterns) { foreach ($patterns as $lvlPattern) { if ($pattern === $lvlPattern) return $level; } } return 'medium'; } function getCodePreview($code, $pattern) { if (preg_match($pattern, $code, $matches, PREG_OFFSET_CAPTURE)) { $offset = $matches[0][1]; $preview = substr($code, max(0, $offset - 30), 80); return htmlspecialchars($preview); } return ''; } function scanDirectoryForMalware($dir) { $results = []; $allowedExtensions = ['php', 'html', 'htaccess', 'js']; $quarantinePath = realpath($dir . '/' . QUARANTINE_DIR); $forbiddenFiles = [ 'wp-load.php', 'xmlrpc.php', 'adminer.php', 'config.ini', 'settings.inc.php' ]; $patterns = [ '/\beval\s*\((?!\s*["\'].*["\']\s*\))/i', '/\b(base64_decode|system|exec|passthru|shell_exec|proc_open|popen)\s*\(/i', '/\$\w+\s*=\s*base64_decode\(/i', '/gzuncompress\s*\(/i', '/gzinflate\s*\(/i', '/str_rot13\s*\(/i', '/(?:file_put_contents|fwrite)\s*\(\s*.*(\$_POST|\$_GET|\$_REQUEST)/i', '/create_function\s*\(/i', ]; $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); foreach ($rii as $file) { if ($file->isDir()) continue; $filePath = $file->getPathname(); // 🛡️ Отдельная угроза: повторяющиеся папки if (preg_match('#/(.+?)(/\1){1,}(/|$)#', $filePath)) { $results[] = [ 'path' => $filePath, 'pattern' => 'Повторяющаяся директория', 'severity' => 'critical', // или 'suspicious', 'threat_type' => 'repeated_directory' ]; } $extension = pathinfo($filePath, PATHINFO_EXTENSION); if (!in_array(strtolower($extension), $allowedExtensions)) continue; if (strpos($filePath, QUARANTINE_DIR) !== false) continue; $code = file_get_contents($filePath); if (strlen($code) > MAX_FILE_SIZE) continue; if (isWhitelisted($filePath, $forbiddenFiles)) continue; foreach ($patterns as $pattern) { if (preg_match($pattern, $code)) { if (!isFalsePositive($code)) { $results[] = [ 'path' => $filePath, 'pattern' => $pattern, 'preview' => getCodePreview($code, $pattern), 'severity' => getSeverityLevel($pattern), 'threat_type' => 'malicious_code' ]; break; } } } } return $results; } // Обработка действий if ($_SERVER['REQUEST_METHOD'] === 'POST') { // CSRF защита if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { die('CSRF validation failed'); } if (!empty($_POST['action']) && !empty($_POST['filepath'])) { $filepath = $_POST['filepath']; $quarantineDir = $rootDir . '/' . QUARANTINE_DIR; if ($_POST['action'] === 'delete') { if (file_exists($filepath)) { if (unlink($filepath)) { $msg = "Файл удалён: <code>" . htmlspecialchars($filepath) . "</code>"; $msgType = "success"; logMessage("Deleted file: $filepath"); $_SESSION['scan_results'] = array_filter($_SESSION['scan_results'], fn($item) => $item['path'] !== $filepath); } else { $msg = "Ошибка при удалении файла."; $msgType = "error"; logMessage("Failed to delete file: $filepath"); } } } elseif ($_POST['action'] === 'quarantine') { if (!file_exists($quarantineDir)) { mkdir($quarantineDir, 0755, true); } if (file_exists($filepath)) { $basename = basename($filepath); $quarantinePath = $quarantineDir . '/' . $basename; $i = 1; while (file_exists($quarantinePath)) { $quarantinePath = $quarantineDir . '/' . pathinfo($basename, PATHINFO_FILENAME) . "_$i." . pathinfo($basename, PATHINFO_EXTENSION); $i++; } if (rename($filepath, $quarantinePath)) { $msg = "Файл перемещён в карантин: <code>" . htmlspecialchars($quarantinePath) . "</code>"; $msgType = "success"; logMessage("Quarantined file: $filepath to $quarantinePath"); $_SESSION['scan_results'] = array_filter($_SESSION['scan_results'], fn($item) => $item['path'] !== $filepath); } else { $msg = "Ошибка при перемещении файла."; $msgType = "error"; logMessage("Failed to quarantine file: $filepath"); } } } elseif ($_POST['action'] === 'restore' && !empty($_POST['original_path'])) { $originalPath = $_POST['original_path']; $quarantinePath = $filepath; if (file_exists($quarantinePath) && !file_exists($originalPath)) { if (rename($quarantinePath, $originalPath)) { $msg = "Файл восстановлен: <code>" . htmlspecialchars($originalPath) . "</code>"; $msgType = "success"; logMessage("Restored file: $quarantinePath to $originalPath"); } else { $msg = "Ошибка при восстановлении файла."; $msgType = "error"; logMessage("Failed to restore file: $quarantinePath"); } } } } // Запуск сканирования if (isset($_POST['scan'])) { $_SESSION['scan_results'] = scanDirectoryForMalware($rootDir); $_SESSION['scan_time'] = formatRussianDate(time()); logMessage("Scan completed. Found " . count($_SESSION['scan_results']) . " suspicious files"); } } $scanResults = $_SESSION['scan_results'] ?? []; $scanTime = $_SESSION['scan_time'] ?? '—'; // Получение файлов в карантине $quarantineFiles = []; $quarantineDir = $rootDir . '/' . QUARANTINE_DIR; if (file_exists($quarantineDir)) { $iterator = new DirectoryIterator($quarantineDir); foreach ($iterator as $file) { if ($file->isFile()) { $quarantineFiles[] = $file->getPathname(); } } } ?> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Антивирус Bitrix</title> <style> body { font-family: "Segoe UI", sans-serif; margin: 20px; background: #f4f4f4; color: #333; line-height: 1.6; } .container { background: #fff; padding: 20px 30px; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); max-width: 1200px; margin: 0 auto; } h1 { font-size: 22px; margin-bottom: 15px; } h2 { font-size: 18px; margin: 20px 0 10px; } button, .btn { padding: 8px 14px; border: none; background: #007bff; color: #fff; border-radius: 4px; cursor: pointer; font-size: 14px; text-decoration: none; display: inline-block; } button:hover, .btn:hover { background: #0056b3; } .btn-danger { background: #dc3545; } .btn-danger:hover { background: #a71d2a; } .btn-warning { background: #ffc107; color: #000; } .btn-warning:hover { background: #d39e00; } .btn-success { background: #28a745; } .btn-success:hover { background: #1e7e34; } .msg { padding: 10px 15px; border-radius: 4px; margin-bottom: 20px; } .success { background: #d4edda; color: #155724; } .error { background: #f8d7da; color: #721c24; } .file-box { background: #f9f9f9; border: 1px solid #ddd; padding: 10px 15px; margin-bottom: 15px; border-radius: 4px; } .file-box code { display: block; background: #eef; padding: 6px; margin-top: 5px; font-family: Consolas, monospace; overflow-x: auto; white-space: pre-wrap; word-break: break-all; } .actions { margin-top: 8px; } .scan-time { font-size: 12px; color: #777; margin-bottom: 15px; } .tab-content { display: none; } .tab-content.active { display: block; } .tabs { display: flex; border-bottom: 1px solid #ddd; margin-bottom: 15px; } .tab { padding: 10px 15px; cursor: pointer; border: 1px solid transparent; margin-bottom: -1px; } .tab.active { border-color: #ddd #ddd #fff; border-top-left-radius: 4px; border-top-right-radius: 4px; background: #fff; font-weight: bold; } pre { background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; } .stats { background: #e9ecef; padding: 10px; border-radius: 4px; margin-bottom: 15px; } .threat-table { width: 100%; border-collapse: collapse; margin-top: 10px; } .threat-table th, .threat-table td { border: 1px solid #ccc; padding: 8px; } .severity-low { background-color: #e0f7fa; } .severity-medium { background-color: #fff3cd; } .severity-critical { background-color: #f8d7da; } .legend { margin-top: 20px; } .legend span { display: inline-block; padding: 5px 10px; margin-right: 10px; border-radius: 4px; } .legend .critical { background-color: #f8d7da; } .legend .medium { background-color: #fff3cd; } .legend .low { background-color: #e0f7fa; } </style> </head> <body> <div class="container"> <h1>Антивирус сканер для Bitrix</h1> <?php if ($msg): ?> <div class="msg <?= $msgType ?>"><?= $msg ?></div> <?php endif; ?> <div class="stats"> <strong>Статистика:</strong><br> - Найдено подозрительных файлов: <?= count($scanResults) ?><br> - Файлов в карантине: <?= count($quarantineFiles) ?><br> - Последнее сканирование: <?= $scanTime ?> </div> <div class="tabs"> <div class="tab active" data-tab="scan">Сканирование</div> <div class="tab" data-tab="quarantine">Карантин</div> <div class="tab" data-tab="logs">Логи</div> </div> <div id="scan" class="tab-content active"> <form method="post" style="margin-bottom: 20px;"> <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> <button type="submit" name="scan">Запустить сканирование</button> </form> <?php if ($scanResults): ?> <h3>Найдено подозрительных файлов: <?= count($scanResults) ?></h3> <?php foreach ($scanResults as $item): ?> <?php $severity = $item['severity'] ?? 'low'; // по умолчанию — low $severityLabel = match ($severity) { 'critical' => 'Критическая угроза', 'medium' => 'Средняя угроза', default => 'Низкая угроза', }; $severityClass = 'severity-' . $severity; ?> <div class="file-box <?= $severityClass ?>"> <strong><?= htmlspecialchars($item['path']) ?></strong><br> Уровень угрозы: <strong><?= $severityLabel ?></strong><br> Обнаружено: <code><?= $item['pattern'] ?></code> <div> Фрагмент кода: <details class="code-preview"> <summary style="cursor:pointer; color:#007BFF;">Показать код</summary> <pre><code><?= isset($item['preview']) ? htmlspecialchars($item['preview']) : 'Нет превью' ?></code></pre> </details> <!--<code><?= isset($item['preview']) ? htmlspecialchars($item['preview']) : 'Нет превью' ?></code> --> </div> <div class="actions"> <form method="post" style="display:inline;"> <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> <input type="hidden" name="filepath" value="<?= htmlspecialchars($item['path']) ?>"> <?php if ($item['threat_type'] !== 'repeated_directory'): ?> <button type="submit" name="action" value="delete" class="btn-danger">Удалить</button> <button type="submit" name="action" value="quarantine" class="btn-warning">Карантин</button> <?php endif; ?> </form> </div> <div class="legend"> <strong>Уровни угроз:</strong><br> <span class="low">Слабая</span> <span class="medium">Средняя</span> <span class="critical">Критическая</span> </div> </div> <?php endforeach; ?> <?php elseif (isset($_POST['scan'])): ?> <p>Вредоносных файлов не обнаружено.</p> <?php endif; ?> </div> <div id="quarantine" class="tab-content"> <?php if ($quarantineFiles): ?> <h3>Файлы в карантине</h3> <?php foreach ($quarantineFiles as $file): ?> <div class="file-box"> <strong><?= htmlspecialchars($file) ?></strong> <div class="actions"> <form method="post" style="display:inline;"> <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> <input type="hidden" name="filepath" value="<?= htmlspecialchars($file) ?>"> <input type="hidden" name="original_path" value="<?= htmlspecialchars(str_replace('/' . QUARANTINE_DIR . '/', '/', $file)) ?>"> <button type="submit" name="action" value="restore" class="btn-success">Восстановить</button> <button type="submit" name="action" value="delete" class="btn-danger">Удалить</button> </form> </div> </div> <?php endforeach; ?> <?php else: ?> <p>Карантин пуст.</p> <?php endif; ?> </div> <div id="logs" class="tab-content"> <h3>Логи сканера</h3> <?php if (file_exists(LOG_FILE)): ?> <pre><?= htmlspecialchars(file_get_contents(LOG_FILE)) ?></pre> <form method="post"> <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> <button type="submit" name="action" value="clear_logs" class="btn-danger">Очистить логи</button> </form> <?php else: ?> <p>Лог-файл не найден.</p> <?php endif; ?> </div> </div> <script> // Простая таб-система document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById(tab.dataset.tab).classList.add('active'); }); }); </script> </body> </html>