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 -
+
+
+
+
+
+
+
+
+
+
+
+
+
Discovered Hosts
+
+
+
+ | Host |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
Open Ports
+
+
+
+ | Port |
+ Protocol |
+ Service |
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
Vulnerability Findings
+
+
+
+
+
+
+
+
+
Screenshots
+
+
+
+
; ?>/screenshots/<?php echo urlencode($ss); ?>)
+
+
+
+
+
+
+
+
+
Generated Files
+
+
No files found in this workspace.
+
+
+
+
+
+
+
+
\ 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);
+}