diff --git a/webui/assets/script.js b/webui/assets/script.js index b87712d..fa25768 100644 --- a/webui/assets/script.js +++ b/webui/assets/script.js @@ -116,22 +116,25 @@ async function loadWorkspaces() { if (result.workspaces && result.workspaces.length > 0) { container.innerHTML = result.workspaces .map( - (ws) => ` + (ws) => { + const name = ws.name || ws; + const size = ws.size || ''; + const modified = ws.modified || ''; + return `
- ${escapeHtml(ws)} +
+ ${escapeHtml(name)} + ${size ? `${size}` : ''} + ${modified ? `| ${modified}` : ''} +
- - - + + +
- ` + `; + } ) .join(""); } else { @@ -152,8 +155,10 @@ async function viewWorkspace(name) { ); const result = await response.json(); - if (result.reportPath) { - window.open(result.reportPath, "_blank"); + if (result.reportUrl) { + window.open(result.reportUrl, "_blank"); + } else if (result.error) { + showNotification(result.error, "error"); } else { showNotification("No report found for this workspace.", "warning"); } @@ -163,7 +168,7 @@ async function viewWorkspace(name) { } async function exportWorkspace(name) { - showNotification("Exporting workspace: " + name, "info"); + showNotification("Creating export for: " + name + "...", "info"); try { const response = await fetch("workspaces.php", { method: "POST", @@ -172,10 +177,12 @@ async function exportWorkspace(name) { }); const result = await response.json(); - if (result.success) { - showNotification("Workspace exported: " + result.path, "success"); + if (result.success && result.downloadUrl) { + showNotification(`Export ready: ${result.filename} (${result.size})`, "success"); + // Trigger download + window.open(result.downloadUrl, "_blank"); } else { - showNotification("Export failed: " + result.error, "error"); + showNotification("Export failed: " + (result.error || "Unknown error"), "error"); } } catch (error) { showNotification("Export failed.", "error"); diff --git a/webui/report.php b/webui/report.php new file mode 100644 index 0000000..528bed1 --- /dev/null +++ b/webui/report.php @@ -0,0 +1,489 @@ + $name, + 'created' => date('Y-m-d H:i:s', filectime($workspaceDir)), + 'modified' => date('Y-m-d H:i:s', filemtime($workspaceDir)), + 'hosts' => [], + 'ports' => [], + 'vulnerabilities' => [], + 'screenshots' => [], + 'files' => [] +]; + +// Get all files in workspace +function scanWorkspace($dir, $prefix = '') +{ + $files = []; + if (is_dir($dir)) { + $items = scandir($dir); + foreach ($items as $item) { + if ($item === '.' || $item === '..') continue; + $path = $dir . '/' . $item; + if (is_dir($path)) { + $files = array_merge($files, scanWorkspace($path, $prefix . $item . '/')); + } else { + $files[] = [ + 'name' => $item, + 'path' => $prefix . $item, + 'size' => filesize($path), + 'modified' => filemtime($path) + ]; + } + } + } + return $files; +} + +$data['files'] = scanWorkspace($workspaceDir); + +// Parse hosts from nmap directory +$nmapDir = $workspaceDir . '/nmap'; +if (is_dir($nmapDir)) { + $nmapFiles = glob($nmapDir . '/*.nmap'); + foreach ($nmapFiles as $file) { + $content = file_get_contents($file); + // Extract host info + if (preg_match('/Nmap scan report for (.+)/', $content, $matches)) { + $host = trim($matches[1]); + if (!in_array($host, $data['hosts'])) { + $data['hosts'][] = $host; + } + } + // Extract ports + if (preg_match_all('/(\d+)\/(tcp|udp)\s+open\s+(\S+)/', $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $data['ports'][] = [ + 'port' => $match[1], + 'protocol' => $match[2], + 'service' => $match[3] + ]; + } + } + } +} + +// Get screenshots +$screenshotDir = $workspaceDir . '/screenshots'; +if (is_dir($screenshotDir)) { + $screenshots = glob($screenshotDir . '/*.{png,jpg,jpeg,gif}', GLOB_BRACE); + foreach ($screenshots as $ss) { + $data['screenshots'][] = basename($ss); + } +} + +// Parse vulnerabilities from output files +$outputDir = $workspaceDir . '/output'; +if (is_dir($outputDir)) { + $vulnFiles = glob($outputDir . '/*nuclei*.txt'); + foreach ($vulnFiles as $file) { + $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($lines as $line) { + if (strpos($line, '[') !== false && strpos($line, ']') !== false) { + $data['vulnerabilities'][] = trim($line); + } + } + } +} + +// Unique ports +$data['ports'] = array_unique($data['ports'], SORT_REGULAR); +$data['vulnerabilities'] = array_unique($data['vulnerabilities']); + +?> + + + + + + + UltyScan Report - <?php echo htmlspecialchars($name); ?> + + + + + +
+
+

🔍

+

+ Created: | + Modified: +

+
+ +
+
+
+
Hosts Discovered
+
+
+
+
Open Ports
+
+
+
+
Findings
+
+
+
+
Files Generated
+
+
+ + +
+

Discovered Hosts

+ + + + + + + + + + + + + +
Host
+
+ + + +
+

Open Ports

+ + + + + + + + + + + + + + + + + +
PortProtocolService
+
+ + + +
+

Vulnerability Findings

+ +
+ +
+ + + +
+

Screenshots

+
+ +
+ <?php echo htmlspecialchars($ss); ?> +
+
+ +
+
+ + +
+

Generated Files

+ +
No files found in this workspace.
+ +
+ +
+ 📄 + + KB +
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/webui/workspaces.php b/webui/workspaces.php index aa47d82..89aa8d3 100644 --- a/webui/workspaces.php +++ b/webui/workspaces.php @@ -4,22 +4,32 @@ * UltyScan Web Interface - Workspace Management */ -header('Content-Type: application/json'); - define('WORKSPACE_DIR', '/usr/share/sniper/loot/workspace'); -define('SNIPER_PATH', '/usr/share/sniper/sniper'); +define('EXPORT_DIR', '/var/www/html/ultyscan/exports'); -// Handle GET requests (list, view) +// Create export directory if it doesn't exist +if (!is_dir(EXPORT_DIR)) { + mkdir(EXPORT_DIR, 0755, true); +} + +// Handle GET requests (list, view, download) if ($_SERVER['REQUEST_METHOD'] === 'GET') { $action = $_GET['action'] ?? 'list'; if ($action === 'list') { + header('Content-Type: application/json'); $workspaces = []; if (is_dir(WORKSPACE_DIR)) { $dirs = scandir(WORKSPACE_DIR); foreach ($dirs as $dir) { if ($dir !== '.' && $dir !== '..' && is_dir(WORKSPACE_DIR . '/' . $dir)) { - $workspaces[] = $dir; + $wsPath = WORKSPACE_DIR . '/' . $dir; + $workspaces[] = [ + 'name' => $dir, + 'created' => date('Y-m-d H:i', filectime($wsPath)), + 'modified' => date('Y-m-d H:i', filemtime($wsPath)), + 'size' => getDirectorySize($wsPath) + ]; } } } @@ -28,25 +38,52 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } if ($action === 'view') { + header('Content-Type: application/json'); $name = preg_replace('/[^a-zA-Z0-9\-\_\.]/', '', $_GET['name'] ?? ''); if (empty($name)) { echo json_encode(['error' => 'Invalid workspace name']); exit; } - $reportPath = WORKSPACE_DIR . '/' . $name . '/sniper-report.html'; + $wsPath = WORKSPACE_DIR . '/' . $name; + if (!is_dir($wsPath)) { + echo json_encode(['error' => 'Workspace not found']); + exit; + } + + // Check for sniper-report.html first + $reportPath = $wsPath . '/sniper-report.html'; if (file_exists($reportPath)) { - // Return relative web path (assuming workspace is web-accessible) - echo json_encode(['reportPath' => '/loot/workspace/' . $name . '/sniper-report.html']); + echo json_encode(['reportUrl' => '/loot/workspace/' . urlencode($name) . '/sniper-report.html']); } else { - echo json_encode(['reportPath' => null, 'message' => 'No report found']); + // Use our custom report viewer + echo json_encode(['reportUrl' => 'report.php?name=' . urlencode($name)]); } exit; } + + if ($action === 'download') { + $name = preg_replace('/[^a-zA-Z0-9\-\_\.]/', '', $_GET['name'] ?? ''); + if (empty($name)) { + die('Invalid workspace name'); + } + + $exportFile = EXPORT_DIR . '/' . $name . '.tar.gz'; + if (file_exists($exportFile)) { + header('Content-Type: application/gzip'); + header('Content-Disposition: attachment; filename="' . $name . '.tar.gz"'); + header('Content-Length: ' . filesize($exportFile)); + readfile($exportFile); + exit; + } else { + die('Export file not found. Please export the workspace first.'); + } + } } // Handle POST requests (delete, export) if ($_SERVER['REQUEST_METHOD'] === 'POST') { + header('Content-Type: application/json'); $data = json_decode(file_get_contents('php://input'), true); $action = $data['action'] ?? ''; $name = preg_replace('/[^a-zA-Z0-9\-\_\.]/', '', $data['name'] ?? ''); @@ -56,24 +93,82 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { exit; } + $wsPath = WORKSPACE_DIR . '/' . $name; + if ($action === 'delete') { - $cmd = SNIPER_PATH . ' -w ' . escapeshellarg($name) . ' -d 2>&1'; - // Auto-confirm the deletion - $output = shell_exec("echo 'y' | $cmd"); - echo json_encode(['success' => true, 'output' => $output]); + if (is_dir($wsPath)) { + // Delete directory recursively + deleteDirectory($wsPath); + echo json_encode(['success' => true, 'message' => 'Workspace deleted']); + } else { + echo json_encode(['success' => false, 'error' => 'Workspace not found']); + } exit; } if ($action === 'export') { - $cmd = SNIPER_PATH . ' -w ' . escapeshellarg($name) . ' --export 2>&1'; + if (!is_dir($wsPath)) { + echo json_encode(['success' => false, 'error' => 'Workspace not found']); + exit; + } + + $exportFile = EXPORT_DIR . '/' . $name . '.tar.gz'; + + // Create tar.gz archive + $cmd = "cd " . escapeshellarg(WORKSPACE_DIR) . " && tar -czf " . escapeshellarg($exportFile) . " " . escapeshellarg($name) . " 2>&1"; $output = shell_exec($cmd); - echo json_encode([ - 'success' => true, - 'path' => '/usr/share/sniper/loot/' . $name . '.tar', - 'output' => $output - ]); + + if (file_exists($exportFile)) { + $size = filesize($exportFile); + echo json_encode([ + 'success' => true, + 'downloadUrl' => 'workspaces.php?action=download&name=' . urlencode($name), + 'filename' => $name . '.tar.gz', + 'size' => formatBytes($size), + 'message' => 'Export created successfully' + ]); + } else { + echo json_encode([ + 'success' => false, + 'error' => 'Failed to create export: ' . $output + ]); + } exit; } } echo json_encode(['error' => 'Invalid request']); + +// Helper functions +function getDirectorySize($dir) +{ + $size = 0; + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)) as $file) { + $size += $file->getSize(); + } + return formatBytes($size); +} + +function formatBytes($bytes) +{ + if ($bytes >= 1073741824) { + return number_format($bytes / 1073741824, 2) . ' GB'; + } elseif ($bytes >= 1048576) { + return number_format($bytes / 1048576, 2) . ' MB'; + } elseif ($bytes >= 1024) { + return number_format($bytes / 1024, 2) . ' KB'; + } else { + return $bytes . ' B'; + } +} + +function deleteDirectory($dir) +{ + if (!is_dir($dir)) return false; + $files = array_diff(scandir($dir), ['.', '..']); + foreach ($files as $file) { + $path = $dir . '/' . $file; + is_dir($path) ? deleteDirectory($path) : unlink($path); + } + return rmdir($dir); +}