D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
home
/
ensad
/
dialogos.ensad.edu.pe
/
Filename :
scanner.php
back
Copy
<?php declare(strict_types=1); ini_set('display_errors', '1'); error_reporting(E_ALL); set_time_limit(0); ini_set('memory_limit', '512M'); const APP_VERSION = '6.0.0'; const MAX_READ_BYTES = 512 * 1024; // 512 KB const MAX_RESULTS = 1000; $selfFile = realpath(__FILE__) ?: __FILE__; $selfDir = realpath(__DIR__) ?: __DIR__; $now = time(); /* |-------------------------------------------------------------------------- | Manuel kök dizinler |-------------------------------------------------------------------------- */ $manualRoots = [ // '/home/ensad/public_html', // '/home/ensad/domains/sub.example.com/public_html', ]; /* |-------------------------------------------------------------------------- | Ayarlar |-------------------------------------------------------------------------- */ $highRiskExtensions = [ 'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phar', 'pht', 'phtm', 'inc', 'ico', 'svg', 'htaccess' ]; $skipDirNames = [ '.git', '.svn', 'node_modules', 'vendor', 'cache', 'tmp', 'logs', 'log', 'backup', 'backups', 'storage', 'sessions', 'framework', 'wflogs', 'ai1wm-backups', 'updraft' ]; $patterns = [ ['label' => 'eval + base64_decode', 'regex' => '/eval\s*\(\s*base64_decode\s*\(/i', 'score' => 90, 'category' => 'obfuscated'], ['label' => 'gzinflate + base64_decode', 'regex' => '/(?:gzinflate|gzdecode|gzuncompress)\s*\(\s*base64_decode\s*\(/i', 'score' => 70, 'category' => 'obfuscated'], ['label' => 'request execution', 'regex' => '/(?:eval|assert|system|exec|shell_exec|passthru)\s*\(\s*\$_(?:GET|POST|REQUEST|COOKIE)/i', 'score' => 120, 'category' => 'shell'], ['label' => 'webshell marker', 'regex' => '/(?:FilesMan|WSO|b374k|c99|r57|mini shell|alfa shell|cmd=|Uploader)/i', 'score' => 130, 'category' => 'shell'], ['label' => 'php in non-php file', 'regex' => '/<\?(?:php|=)?/i', 'score' => 80, 'category' => 'shell', 'non_php_only' => true], ['label' => 'long base64 blob', 'regex' => '/[A-Za-z0-9+\/=]{600,}/', 'score' => 20, 'category' => 'obfuscated'], ['label' => 'hidden execution @', 'regex' => '/@\s*(?:eval|assert|system|exec|shell_exec|passthru|include|require)\s*\(/i', 'score' => 30, 'category' => 'manual'], ['label' => 'chr obfuscation', 'regex' => '/(?:chr\s*\(\s*\d+\s*\)\s*\.?){6,}/i', 'score' => 35, 'category' => 'obfuscated'], ]; /* |-------------------------------------------------------------------------- | Yardımcı fonksiyonlar |-------------------------------------------------------------------------- */ function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function norm(string $path): string { $real = realpath($path); return str_replace('\\', '/', $real !== false ? $real : $path); } function extOf(string $path): string { $base = basename($path); if ($base === '.htaccess') { return 'htaccess'; } return strtolower(pathinfo($path, PATHINFO_EXTENSION)); } function fmtBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $size = (float) $bytes; $i = 0; while ($size >= 1024 && $i < count($units) - 1) { $size /= 1024; $i++; } return number_format($size, 2) . ' ' . $units[$i]; } function isReadableDirSafe(string $path): bool { return is_dir($path) && is_readable($path); } function isReadableFileSafe(string $path): bool { return is_file($path) && is_readable($path); } function isWritableDirSafe(string $path): bool { return is_dir($path) && is_writable($path); } function relToRoot(string $path, string $root): string { $path = norm($path); $root = rtrim(norm($root), '/'); if (strpos($path, $root) === 0) { return ltrim(substr($path, strlen($root)), '/'); } return $path; } function insideAllowedRoots(string $path, array $roots): bool { $path = norm($path); foreach ($roots as $root) { $root = rtrim(norm($root), '/'); if ($path === $root || strpos($path, $root . '/') === 0) { return true; } } return false; } function guessAccountBase(string $selfDir): string { $selfDir = norm($selfDir); $parts = explode('/', trim($selfDir, '/')); if (count($parts) >= 2 && $parts[0] === 'home') { return '/' . $parts[0] . '/' . $parts[1]; } return dirname($selfDir); } function discoverRoots(string $selfDir, array $manualRoots): array { $roots = []; $accountBase = guessAccountBase($selfDir); foreach ([$selfDir, $accountBase] as $root) { if (isReadableDirSafe($root)) { $roots[] = norm($root); } } $documentRoot = $_SERVER['DOCUMENT_ROOT'] ?? ''; if ($documentRoot && isReadableDirSafe($documentRoot)) { $roots[] = norm($documentRoot); } $topLevel = @scandir($accountBase); if (is_array($topLevel)) { foreach ($topLevel as $item) { if ($item === '.' || $item === '..') { continue; } $full = norm($accountBase . '/' . $item); if (isReadableDirSafe($full)) { $roots[] = $full; } } } foreach ($manualRoots as $root) { if (isReadableDirSafe($root)) { $roots[] = norm($root); } } $roots = array_values(array_unique($roots)); usort($roots, static fn($a, $b) => strlen($a) <=> strlen($b)); return $roots; } function shouldSkipDir(string $path, array $skipNames): bool { $parts = explode('/', trim(str_replace('\\', '/', $path), '/')); foreach ($parts as $part) { if (in_array($part, $skipNames, true)) { return true; } } return false; } function readChunk(string $path, int $maxBytes): string { $fh = @fopen($path, 'rb'); if (!$fh) { return ''; } $data = @fread($fh, $maxBytes); @fclose($fh); return is_string($data) ? $data : ''; } function isBinaryString(string $data): bool { if ($data === '') { return false; } $sample = substr($data, 0, 2048); $nonPrintable = 0; $len = strlen($sample); for ($i = 0; $i < $len; $i++) { $o = ord($sample[$i]); if (($o < 9 || ($o > 13 && $o < 32)) && $o !== 0) { $nonPrintable++; } } return $nonPrintable > 40; } function categoryLabel(string $category): string { return $category === 'shell' ? 'Muhtemel Shell' : ($category === 'obfuscated' ? 'Obfuscated' : 'Manuel İncele'); } function severityLabel(int $score): array { if ($score >= 120) return ['Critical', '#7f1d1d', '#fee2e2']; if ($score >= 80) return ['High', '#9a3412', '#ffedd5']; if ($score >= 45) return ['Medium', '#854d0e', '#fef3c7']; return ['Low', '#1e3a8a', '#dbeafe']; } function getFileToken(string $path): string { return rawurlencode(base64_encode($path)); } function evaluateFast(string $path, string $content, array $patterns): array { $score = 0; $reasons = []; $categoryHits = ['shell' => 0, 'obfuscated' => 0, 'manual' => 0]; $ext = extOf($path); $phpLike = in_array($ext, ['php','phtml','php3','php4','php5','php7','php8','phar','pht','phtm','inc'], true); if (!$phpLike && preg_match('/<\?(?:php|=)?/i', $content)) { $score += 80; $reasons[] = 'PHP code inside non-PHP file'; $categoryHits['shell'] += 2; } foreach ($patterns as $pattern) { if (!empty($pattern['non_php_only']) && $phpLike) { continue; } if (preg_match($pattern['regex'], $content)) { $score += $pattern['score']; $reasons[] = $pattern['label']; $categoryHits[$pattern['category']]++; } } if (preg_match('/\b(shell_exec|exec|system|passthru|proc_open|popen)\s*\(/i', $content)) { $score += 30; $reasons[] = 'OS command execution function'; $categoryHits['shell']++; } if (preg_match('/\b(base64_decode|gzinflate|gzdecode|gzuncompress|str_rot13)\s*\(/i', $content)) { $score += 20; $reasons[] = 'Encoding/obfuscation function'; $categoryHits['obfuscated']++; } if ( preg_match('/\$_(?:GET|POST|REQUEST|COOKIE|FILES)/i', $content) && preg_match('/\b(eval|assert|include|require|system|exec|shell_exec|passthru|move_uploaded_file|file_put_contents|copy|rename)\s*\(/i', $content) ) { $score += 30; $reasons[] = 'User input with sensitive operation'; $categoryHits['shell'] += 2; } $category = 'manual'; if ($categoryHits['shell'] >= 2 || $score >= 100) { $category = 'shell'; } elseif ($categoryHits['obfuscated'] >= 2) { $category = 'obfuscated'; } elseif ($categoryHits['obfuscated'] > 0) { $category = 'obfuscated'; } return [ 'score' => $score, 'reasons' => array_values(array_unique($reasons)), 'category' => $category, ]; } function scanFast( array $roots, string $selfFile, array $extensions, array $skipDirs, array $patterns, int $daysFilter, string $pathFilter, int $now ): array { $results = []; $stats = [ 'roots_scanned' => 0, 'seen_files' => 0, 'checked_files' => 0, 'flagged_files' => 0, 'errors' => 0, ]; foreach ($roots as $root) { $stats['roots_scanned']++; try { $dirIterator = new RecursiveDirectoryIterator( $root, FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_FILEINFO ); $iterator = new RecursiveIteratorIterator( $dirIterator, RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD ); } catch (Throwable $e) { $stats['errors']++; continue; } foreach ($iterator as $item) { try { /** @var SplFileInfo $item */ $path = norm($item->getPathname()); if ($item->isDir()) { if (shouldSkipDir($path, $skipDirs)) { continue; } continue; } if (!$item->isFile()) continue; if ($path === norm($selfFile)) continue; if (!is_readable($path)) continue; $stats['seen_files']++; $mtime = @filemtime($path) ?: 0; if ($daysFilter > 0 && $mtime < ($now - ($daysFilter * 86400))) continue; if ($pathFilter !== '' && stripos($path, $pathFilter) === false) continue; $ext = extOf($path); if (!in_array($ext, $extensions, true)) continue; $size = @filesize($path); $size = $size === false ? 0 : (int)$size; $content = readChunk($path, MAX_READ_BYTES); if ($content === '') continue; if (isBinaryString($content) && !preg_match('/<\?(?:php|=)?/i', $content)) continue; $stats['checked_files']++; $eval = evaluateFast($path, $content, $patterns); if ($eval['score'] <= 0) continue; $stats['flagged_files']++; $results[] = [ 'root' => $root, 'path' => $path, 'relative' => relToRoot($path, $root), 'size' => $size, 'mtime' => $mtime, 'score' => $eval['score'], 'reasons' => $eval['reasons'], 'category' => $eval['category'], 'sha1' => sha1($content), ]; if (count($results) >= MAX_RESULTS) { break 2; } } catch (Throwable $e) { $stats['errors']++; continue; } } } usort($results, static fn($a, $b) => [$b['score'], $b['mtime']] <=> [$a['score'], $a['mtime']]); return [$results, $stats]; } function renderMessagePage(string $title, array $lines): void { echo '<!doctype html><html><head><meta charset="utf-8"><title>' . h($title) . '</title><style> body{font-family:Arial,sans-serif;background:#f8fafc;color:#0f172a;margin:0;padding:24px} .box{max-width:900px;margin:0 auto;background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:18px} a{display:inline-block;margin-top:12px;background:#0f172a;color:#fff;text-decoration:none;padding:10px 16px;border-radius:10px} code{background:#f1f5f9;padding:2px 6px;border-radius:6px} </style></head><body><div class="box">'; echo '<h2>' . h($title) . '</h2>'; foreach ($lines as $line) { echo '<p>' . $line . '</p>'; } echo '<a href="?scan=1">Geri dön</a>'; echo '</div></body></html>'; exit; } function viewFile(string $encoded, array $roots): void { $decoded = base64_decode($encoded, true); if ($decoded === false || $decoded === '') { http_response_code(400); echo 'Invalid token'; exit; } $path = norm($decoded); if (!insideAllowedRoots($path, $roots) || !isReadableFileSafe($path)) { http_response_code(403); echo 'Access denied'; exit; } $content = @file_get_contents($path); if ($content === false) { http_response_code(404); echo 'Unable to read file'; exit; } $lines = preg_split('/\R/', str_replace(["\r\n", "\r"], "\n", $content)) ?: []; echo '<!doctype html><html><head><meta charset="utf-8"><title>View File</title><style> body{font-family:Consolas,Monaco,monospace;background:#0f172a;color:#e2e8f0;margin:0;padding:18px} a{color:#93c5fd;text-decoration:none} .box{background:#111827;border:1px solid #334155;border-radius:10px;padding:14px;overflow:auto} .line{white-space:pre-wrap;border-bottom:1px solid #1f2937;padding:2px 0} .ln{display:inline-block;width:64px;color:#64748b;user-select:none} </style></head><body>'; echo '<p><a href="?scan=1">← Geri dön</a></p>'; echo '<h2>' . h($path) . '</h2>'; echo '<div class="box">'; foreach ($lines as $i => $line) { echo '<div class="line"><span class="ln">' . ($i + 1) . '</span>' . h($line) . '</div>'; } echo '</div></body></html>'; exit; } function quarantineFile(string $encoded, array $roots, string $selfFile): void { $decoded = base64_decode($encoded, true); if ($decoded === false || $decoded === '') { http_response_code(400); echo 'Invalid token'; exit; } $path = norm($decoded); if (!insideAllowedRoots($path, $roots) || !isReadableFileSafe($path)) { http_response_code(403); echo 'Access denied'; exit; } if ($path === norm($selfFile)) { http_response_code(403); echo 'Scanner file cannot be renamed'; exit; } if (substr($path, -6) === '.iptal') { renderMessagePage('Dosya zaten iptal edilmiş', [ '<code>' . h($path) . '</code>' ]); } $newPath = $path . '.iptal'; if (file_exists($newPath)) { renderMessagePage('Hedef dosya zaten var', [ '<code>' . h($newPath) . '</code>' ]); } $dir = dirname($path); if (!isWritableDirSafe($dir)) { renderMessagePage('Dizin yazılabilir değil', [ '<code>' . h($dir) . '</code>' ]); } if (!@rename($path, $newPath)) { renderMessagePage('Yeniden adlandırma başarısız', [ '<code>' . h($path) . '</code>' ]); } renderMessagePage('Dosya yeniden adlandırıldı', [ '<strong>Eski:</strong> <code>' . h($path) . '</code>', '<strong>Yeni:</strong> <code>' . h($newPath) . '</code>' ]); } function restoreFile(string $encoded, array $roots, string $selfFile): void { $decoded = base64_decode($encoded, true); if ($decoded === false || $decoded === '') { http_response_code(400); echo 'Invalid token'; exit; } $path = norm($decoded); if (!insideAllowedRoots($path, $roots) || !isReadableFileSafe($path)) { http_response_code(403); echo 'Access denied'; exit; } if ($path === norm($selfFile)) { http_response_code(403); echo 'Scanner file cannot be renamed'; exit; } if (substr($path, -6) !== '.iptal') { renderMessagePage('Dosya .iptal ile bitmiyor', [ '<code>' . h($path) . '</code>' ]); } $newPath = substr($path, 0, -6); if (file_exists($newPath)) { renderMessagePage('Orijinal dosya adı zaten mevcut', [ '<code>' . h($newPath) . '</code>' ]); } $dir = dirname($path); if (!isWritableDirSafe($dir)) { renderMessagePage('Dizin yazılabilir değil', [ '<code>' . h($dir) . '</code>' ]); } if (!@rename($path, $newPath)) { renderMessagePage('Geri alma başarısız', [ '<code>' . h($path) . '</code>' ]); } renderMessagePage('Dosya geri alındı', [ '<strong>Eski:</strong> <code>' . h($path) . '</code>', '<strong>Yeni:</strong> <code>' . h($newPath) . '</code>' ]); } /* |-------------------------------------------------------------------------- | Başlat |-------------------------------------------------------------------------- */ $roots = discoverRoots($selfDir, $manualRoots); $daysFilter = isset($_GET['days']) ? (int) $_GET['days'] : 0; if (!in_array($daysFilter, [0, 3, 7, 30], true)) { $daysFilter = 0; } $pathFilter = isset($_GET['path_filter']) ? trim((string) $_GET['path_filter']) : ''; $categoryFilter = isset($_GET['category']) ? trim((string) $_GET['category']) : 'all'; if (!in_array($categoryFilter, ['all', 'shell', 'obfuscated', 'manual'], true)) { $categoryFilter = 'all'; } if (isset($_GET['view'])) { viewFile((string) $_GET['view'], $roots); } if (isset($_GET['quarantine'])) { quarantineFile((string) $_GET['quarantine'], $roots, $selfFile); } if (isset($_GET['restore'])) { restoreFile((string) $_GET['restore'], $roots, $selfFile); } $scanRequested = isset($_GET['scan']); $results = []; $stats = [ 'roots_scanned' => 0, 'seen_files' => 0, 'checked_files' => 0, 'flagged_files' => 0, 'errors' => 0, ]; if ($scanRequested) { [$results, $stats] = scanFast( $roots, $selfFile, $highRiskExtensions, $skipDirNames, $patterns, $daysFilter, $pathFilter, $now ); if ($categoryFilter !== 'all') { $results = array_values(array_filter( $results, static fn($row) => $row['category'] === $categoryFilter )); } } ?> <!doctype html> <html lang="tr"> <head> <meta charset="utf-8"> <title>Fast Scanner</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body{font-family:Arial,sans-serif;background:#f8fafc;color:#0f172a;margin:0;padding:24px} .wrap{max-width:1400px;margin:0 auto} .card{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:18px;margin-bottom:18px} .btn{display:inline-block;background:#0f172a;color:#fff;text-decoration:none;padding:10px 16px;border-radius:10px} .btn2{display:inline-block;background:#334155;color:#fff;text-decoration:none;padding:8px 12px;border-radius:8px} .btnDanger{display:inline-block;background:#991b1b;color:#fff;text-decoration:none;padding:8px 12px;border-radius:8px;margin-top:6px} .btnSuccess{display:inline-block;background:#166534;color:#fff;text-decoration:none;padding:8px 12px;border-radius:8px;margin-top:6px} .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px} .box{background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px} table{width:100%;border-collapse:collapse} th,td{text-align:left;padding:10px;border-bottom:1px solid #e2e8f0;vertical-align:top} th{background:#f8fafc} .badge{display:inline-block;padding:4px 8px;border-radius:999px;font-size:12px;font-weight:bold} .path{word-break:break-all} .small{font-size:12px;color:#475569} form.inline{display:flex;flex-wrap:wrap;gap:10px;align-items:end} input,select{padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px} .actions a{display:block;width:max-content} code{background:#f1f5f9;padding:2px 6px;border-radius:6px} </style> </head> <body> <div class="wrap"> <div class="card"> <h1 style="margin-top:0;">Hızlı Salt-Okunur Shell Tarayıcı</h1> <p>Bu sürüm hız için optimize edildi. Yalnızca riskli dosyalara bakar. Otomatik silme yapmaz. İstersen dosyanın sonuna <code>.iptal</code> ekleyerek pasifleştirebilirsin. ⚡</p> <p class="small">Sürüm: <?php echo h(APP_VERSION); ?> | Kök: <code><?php echo h($selfDir); ?></code></p> </div> <div class="card"> <form method="get" class="inline"> <input type="hidden" name="scan" value="1"> <div> <label class="small">Klasör filtresi</label><br> <input type="text" name="path_filter" value="<?php echo h($pathFilter); ?>" placeholder="örn: uploads"> </div> <div> <label class="small">Değişim süresi</label><br> <select name="days"> <option value="0" <?php echo $daysFilter===0?'selected':''; ?>>Tümü</option> <option value="3" <?php echo $daysFilter===3?'selected':''; ?>>Son 3 gün</option> <option value="7" <?php echo $daysFilter===7?'selected':''; ?>>Son 7 gün</option> <option value="30" <?php echo $daysFilter===30?'selected':''; ?>>Son 30 gün</option> </select> </div> <div> <label class="small">Kategori</label><br> <select name="category"> <option value="all" <?php echo $categoryFilter==='all'?'selected':''; ?>>Tümü</option> <option value="shell" <?php echo $categoryFilter==='shell'?'selected':''; ?>>Muhtemel Shell</option> <option value="obfuscated" <?php echo $categoryFilter==='obfuscated'?'selected':''; ?>>Obfuscated</option> <option value="manual" <?php echo $categoryFilter==='manual'?'selected':''; ?>>Manuel İncele</option> </select> </div> <div> <button class="btn" type="submit">Taramayı Başlat</button> </div> </form> </div> <div class="card"> <strong>Taranan kökler</strong> <ul> <?php foreach ($roots as $root): ?> <li><code><?php echo h($root); ?></code></li> <?php endforeach; ?> </ul> </div> <?php if ($scanRequested): ?> <div class="card"> <div class="grid"> <div class="box"><strong>Kök</strong><br><?php echo (int)$stats['roots_scanned']; ?></div> <div class="box"><strong>Görülen dosya</strong><br><?php echo (int)$stats['seen_files']; ?></div> <div class="box"><strong>Kontrol edilen</strong><br><?php echo (int)$stats['checked_files']; ?></div> <div class="box"><strong>İşaretlenen</strong><br><?php echo count($results); ?></div> <div class="box"><strong>Hata</strong><br><?php echo (int)$stats['errors']; ?></div> </div> </div> <div class="card"> <h2 style="margin-top:0;">Sonuçlar</h2> <?php if (!$results): ?> <p>Sonuç bulunamadı.</p> <?php else: ?> <table> <thead> <tr> <th>Seviye</th> <th>Kategori</th> <th>Dosya</th> <th>Boyut</th> <th>Değişim</th> <th>Neden</th> <th>İşlem</th> </tr> </thead> <tbody> <?php foreach ($results as $row): ?> <?php [$sev, $fg, $bg] = severityLabel((int)$row['score']); $isDisabled = substr($row['path'], -6) === '.iptal'; $token = getFileToken($row['path']); ?> <tr> <td> <span class="badge" style="color:<?php echo h($fg); ?>;background:<?php echo h($bg); ?>;"> <?php echo h($sev); ?> (<?php echo (int)$row['score']; ?>) </span> </td> <td><?php echo h(categoryLabel($row['category'])); ?></td> <td class="path"> <strong><?php echo h($row['path']); ?></strong><br> <span class="small">SHA1: <?php echo h($row['sha1']); ?></span> </td> <td><?php echo h(fmtBytes((int)$row['size'])); ?></td> <td><?php echo $row['mtime'] ? date('Y-m-d H:i:s', (int)$row['mtime']) : '-'; ?></td> <td><?php echo h(implode(' | ', $row['reasons'])); ?></td> <td class="actions"> <a class="btn2" href="?view=<?php echo $token; ?>">İçeriği Aç</a> <?php if (!$isDisabled): ?> <a class="btnDanger" href="?quarantine=<?php echo $token; ?>" onclick="return confirm('Bu dosyanın sonuna .iptal eklensin mi?');"> İptal Et </a> <?php else: ?> <a class="btnSuccess" href="?restore=<?php echo $token; ?>" onclick="return confirm('Bu dosya eski haline döndürülsün mü?');"> Geri Al </a> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <?php endif; ?> </div> </body> </html>