mirror of
https://github.com/DeNNiiInc/UltyScan.git
synced 2026-04-17 18:26:00 +00:00
489 lines
14 KiB
PHP
489 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* UltyScan Web Interface - Workspace Report Viewer
|
|
* Generates a beautiful HTML report for a workspace
|
|
*/
|
|
|
|
$name = preg_replace('/[^a-zA-Z0-9\-\_\.]/', '', $_GET['name'] ?? '');
|
|
if (empty($name)) {
|
|
die('Invalid workspace name');
|
|
}
|
|
|
|
$workspaceDir = '/usr/share/sniper/loot/workspace/' . $name;
|
|
if (!is_dir($workspaceDir)) {
|
|
die('Workspace not found');
|
|
}
|
|
|
|
// Check for existing sniper report
|
|
$sniperReport = $workspaceDir . '/sniper-report.html';
|
|
if (file_exists($sniperReport)) {
|
|
// Redirect to the existing report
|
|
header('Location: /loot/workspace/' . urlencode($name) . '/sniper-report.html');
|
|
exit;
|
|
}
|
|
|
|
// Collect workspace data
|
|
$data = [
|
|
'name' => $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']);
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UltyScan Report - <?php echo htmlspecialchars($name); ?></title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--bg-primary: #0a0e17;
|
|
--bg-secondary: #111827;
|
|
--bg-card: rgba(17, 24, 39, 0.9);
|
|
--accent-primary: #3b82f6;
|
|
--accent-secondary: #8b5cf6;
|
|
--accent-success: #10b981;
|
|
--accent-warning: #f59e0b;
|
|
--accent-danger: #ef4444;
|
|
--text-primary: #f3f4f6;
|
|
--text-secondary: #9ca3af;
|
|
--border-color: rgba(75, 85, 99, 0.4);
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
min-height: 100vh;
|
|
padding: 2rem;
|
|
background-image:
|
|
radial-gradient(ellipse at 20% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%),
|
|
radial-gradient(ellipse at 80% 50%, rgba(139, 92, 246, 0.08) 0%, transparent 50%);
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
padding: 2rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2.5rem;
|
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.header .meta {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-card .number {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-success));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.stat-card .label {
|
|
color: var(--text-secondary);
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.section {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 16px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.section h2 {
|
|
font-size: 1.25rem;
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 0.75rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.section h2::before {
|
|
content: '';
|
|
width: 4px;
|
|
height: 20px;
|
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
|
border-radius: 2px;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
padding: 0.75rem 1rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
th {
|
|
background: var(--bg-secondary);
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
tr:hover td {
|
|
background: rgba(59, 130, 246, 0.05);
|
|
}
|
|
|
|
.tag {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 20px;
|
|
font-size: 0.8rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.tag-info {
|
|
background: rgba(59, 130, 246, 0.2);
|
|
color: #60a5fa;
|
|
}
|
|
|
|
.tag-warning {
|
|
background: rgba(245, 158, 11, 0.2);
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.tag-danger {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
color: #f87171;
|
|
}
|
|
|
|
.tag-success {
|
|
background: rgba(16, 185, 129, 0.2);
|
|
color: #34d399;
|
|
}
|
|
|
|
.vuln-item {
|
|
padding: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
background: var(--bg-secondary);
|
|
border-radius: 8px;
|
|
font-family: monospace;
|
|
font-size: 0.85rem;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.file-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem;
|
|
background: var(--bg-secondary);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.file-icon {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.file-name {
|
|
flex: 1;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.file-size {
|
|
color: var(--text-secondary);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.screenshot-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.screenshot-item {
|
|
background: var(--bg-secondary);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.screenshot-item img {
|
|
width: 100%;
|
|
height: 150px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.screenshot-item .caption {
|
|
padding: 0.5rem;
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
@media print {
|
|
body {
|
|
background: white;
|
|
color: black;
|
|
}
|
|
|
|
.section {
|
|
border: 1px solid #ddd;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<header class="header">
|
|
<h1>🔍 <?php echo htmlspecialchars($name); ?></h1>
|
|
<p class="meta">
|
|
Created: <?php echo $data['created']; ?> |
|
|
Modified: <?php echo $data['modified']; ?>
|
|
</p>
|
|
</header>
|
|
|
|
<div class="stats">
|
|
<div class="stat-card">
|
|
<div class="number"><?php echo count($data['hosts']); ?></div>
|
|
<div class="label">Hosts Discovered</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="number"><?php echo count($data['ports']); ?></div>
|
|
<div class="label">Open Ports</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="number"><?php echo count($data['vulnerabilities']); ?></div>
|
|
<div class="label">Findings</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="number"><?php echo count($data['files']); ?></div>
|
|
<div class="label">Files Generated</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($data['hosts'])): ?>
|
|
<div class="section">
|
|
<h2>Discovered Hosts</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Host</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($data['hosts'] as $host): ?>
|
|
<tr>
|
|
<td><?php echo htmlspecialchars($host); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($data['ports'])): ?>
|
|
<div class="section">
|
|
<h2>Open Ports</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Port</th>
|
|
<th>Protocol</th>
|
|
<th>Service</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($data['ports'] as $port): ?>
|
|
<tr>
|
|
<td><span class="tag tag-info"><?php echo htmlspecialchars($port['port']); ?></span></td>
|
|
<td><?php echo htmlspecialchars($port['protocol']); ?></td>
|
|
<td><?php echo htmlspecialchars($port['service']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($data['vulnerabilities'])): ?>
|
|
<div class="section">
|
|
<h2>Vulnerability Findings</h2>
|
|
<?php foreach ($data['vulnerabilities'] as $vuln): ?>
|
|
<div class="vuln-item"><?php echo htmlspecialchars($vuln); ?></div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($data['screenshots'])): ?>
|
|
<div class="section">
|
|
<h2>Screenshots</h2>
|
|
<div class="screenshot-grid">
|
|
<?php foreach ($data['screenshots'] as $ss): ?>
|
|
<div class="screenshot-item">
|
|
<img src="/loot/workspace/<?php echo urlencode($name); ?>/screenshots/<?php echo urlencode($ss); ?>" alt="<?php echo htmlspecialchars($ss); ?>">
|
|
<div class="caption"><?php echo htmlspecialchars($ss); ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="section">
|
|
<h2>Generated Files</h2>
|
|
<?php if (empty($data['files'])): ?>
|
|
<div class="empty-state">No files found in this workspace.</div>
|
|
<?php else: ?>
|
|
<div class="file-list">
|
|
<?php foreach ($data['files'] as $file): ?>
|
|
<div class="file-item">
|
|
<span class="file-icon">📄</span>
|
|
<span class="file-name"><?php echo htmlspecialchars($file['path']); ?></span>
|
|
<span class="file-size"><?php echo number_format($file['size'] / 1024, 1); ?> KB</span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|