This commit is contained in:
overcuriousity
2025-09-09 22:19:46 +02:00
parent cee620f5f6
commit 29e36e34be
12 changed files with 2565 additions and 666 deletions

View File

@@ -1,4 +1,4 @@
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Debug Output
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Forensic Data Support
class ReconTool {
constructor() {
@@ -6,7 +6,8 @@ class ReconTool {
this.pollInterval = null;
this.liveDataInterval = null;
this.currentReport = null;
this.debugMode = true; // Enable debug logging
this.debugMode = true;
this.graphVisualization = null; // Add this line
this.init();
}
@@ -23,6 +24,13 @@ class ReconTool {
init() {
this.bindEvents();
this.setupRealtimeElements();
// Handle window resize for graph
window.addEventListener('resize', () => {
if (this.graphVisualization) {
this.graphVisualization.handleResize();
}
});
}
setupRealtimeElements() {
@@ -43,6 +51,14 @@ class ReconTool {
<span class="stat-label">IP Addresses:</span>
<span id="liveIPs" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Discovery Edges:</span>
<span id="liveEdges" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Operations:</span>
<span id="liveOperations" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">DNS Records:</span>
<span id="liveDNS" class="stat-value">0</span>
@@ -117,6 +133,27 @@ class ReconTool {
this.startScan();
}
});
document.getElementById('showGraphView').addEventListener('click', () => {
this.showGraphView();
});
}
showGraphView() {
if (this.graphVisualization && this.currentScanId) {
document.getElementById('graphSection').style.display = 'block';
// Update button states
document.getElementById('showGraphView').classList.add('active');
document.getElementById('showJson').classList.remove('active');
document.getElementById('showText').classList.remove('active');
// Scroll to graph section
document.getElementById('graphSection').scrollIntoView({
behavior: 'smooth'
});
} else {
alert('Graph data not available yet. Please wait for scan completion.');
}
}
async startScan() {
@@ -137,7 +174,7 @@ class ReconTool {
try {
// Show progress section
this.showProgressSection();
this.updateProgress(0, 'Starting scan...');
this.updateProgress(0, 'Starting forensic scan...');
this.debug('Starting scan with data:', scanData);
@@ -223,7 +260,7 @@ class ReconTool {
// Update progress
this.updateProgress(status.progress, status.message);
// Update live stats
// Update live stats (handle new forensic format)
if (status.live_stats) {
this.debug('Received live stats:', status.live_stats);
this.updateLiveStats(status.live_stats);
@@ -270,7 +307,7 @@ class ReconTool {
this.debug('Received live data:', data);
// Update live discoveries
// Update live discoveries (handle new forensic format)
this.updateLiveDiscoveries(data);
} catch (error) {
@@ -282,17 +319,19 @@ class ReconTool {
updateLiveStats(stats) {
this.debug('Updating live stats:', stats);
// Update the live statistics counters
const statElements = {
'liveHostnames': stats.hostnames || 0,
'liveIPs': stats.ip_addresses || 0,
// Handle both old and new stat formats for compatibility
const statMappings = {
'liveHostnames': stats.hostnames || stats.hostnames || 0,
'liveIPs': stats.ip_addresses || stats.ips || 0,
'liveEdges': stats.discovery_edges || 0, // New forensic field
'liveOperations': stats.operations_performed || 0, // New forensic field
'liveDNS': stats.dns_records || 0,
'liveCerts': stats.certificates || 0,
'liveCerts': stats.certificates_total || stats.certificates || 0,
'liveShodan': stats.shodan_results || 0,
'liveVT': stats.virustotal_results || 0
};
Object.entries(statElements).forEach(([elementId, value]) => {
Object.entries(statMappings).forEach(([elementId, value]) => {
const element = document.getElementById(elementId);
if (element) {
const currentValue = element.textContent;
@@ -315,43 +354,92 @@ class ReconTool {
updateLiveDiscoveries(data) {
this.debug('Updating live discoveries with data:', data);
// Handle new forensic data format
let hostnames = [];
let ipAddresses = [];
let activities = [];
// Extract data from forensic format or fallback to old format
if (data.hostnames && Array.isArray(data.hostnames)) {
hostnames = data.hostnames;
} else if (data.stats && data.stats.hostnames) {
// If we only have stats, create a placeholder list
hostnames = [`${data.stats.hostnames} discovered`];
}
if (data.ip_addresses && Array.isArray(data.ip_addresses)) {
ipAddresses = data.ip_addresses;
} else if (data.stats && data.stats.ip_addresses) {
// If we only have stats, create a placeholder list
ipAddresses = [`${data.stats.ip_addresses} discovered`];
}
// Handle activity log from forensic format
if (data.latest_discoveries && Array.isArray(data.latest_discoveries)) {
activities = data.latest_discoveries;
}
// Update hostnames list
const hostnameList = document.querySelector('#recentHostnames .hostname-list');
if (hostnameList && data.hostnames && data.hostnames.length > 0) {
if (hostnameList && hostnames.length > 0) {
// Show last 10 hostnames
const recentHostnames = data.hostnames;
const recentHostnames = hostnames.slice(-10);
hostnameList.innerHTML = recentHostnames.map(hostname =>
`<span class="discovery-item">${hostname}</span>`
).join('');
this.debug(`Updated hostname list with ${recentHostnames.length} items`);
} else if (hostnameList) {
this.debug(`No hostnames to display (${data.hostnames ? data.hostnames.length : 0} total)`);
this.debug(`No hostnames to display (${hostnames.length} total)`);
}
// Update IP addresses list
const ipList = document.querySelector('#recentIPs .ip-list');
if (ipList && data.ip_addresses && data.ip_addresses.length > 0) {
if (ipList && ipAddresses.length > 0) {
// Show last 10 IPs
const recentIPs = data.ip_addresses;
const recentIPs = ipAddresses.slice(-10);
ipList.innerHTML = recentIPs.map(ip =>
`<span class="discovery-item">${ip}</span>`
).join('');
this.debug(`Updated IP list with ${recentIPs.length} items`);
} else if (ipList) {
this.debug(`No IPs to display (${data.ip_addresses ? data.ip_addresses.length : 0} total)`);
this.debug(`No IPs to display (${ipAddresses.length} total)`);
}
// Update activity log
const activityList = document.querySelector('#activityLog .activity-list');
if (activityList && data.latest_discoveries && data.latest_discoveries.length > 0) {
const activities = data.latest_discoveries.slice(-5); // Last 5 activities
activityList.innerHTML = activities.map(activity => {
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
}).join('');
this.debug(`Updated activity log with ${activities.length} items`);
} else if (activityList) {
this.debug(`No activities to display (${data.latest_discoveries ? data.latest_discoveries.length : 0} total)`);
if (activityList) {
if (activities.length > 0) {
const recentActivities = activities.slice(-5); // Last 5 activities
activityList.innerHTML = recentActivities.map(activity => {
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
}).join('');
this.debug(`Updated activity log with ${recentActivities.length} items`);
} else {
// Fallback: show generic activity based on stats
const stats = data.stats || {};
const genericActivities = [];
if (stats.operations_performed > 0) {
genericActivities.push(`${stats.operations_performed} operations performed`);
}
if (stats.hostnames > 0) {
genericActivities.push(`${stats.hostnames} hostnames discovered`);
}
if (stats.dns_records > 0) {
genericActivities.push(`${stats.dns_records} DNS records collected`);
}
if (genericActivities.length > 0) {
const now = new Date().toLocaleTimeString();
activityList.innerHTML = genericActivities.map(activity =>
`<div class="activity-item">[${now}] ${activity}</div>`
).join('');
this.debug(`Updated activity log with ${genericActivities.length} generic items`);
} else {
this.debug('No activities to display');
}
}
}
}
@@ -374,9 +462,15 @@ class ReconTool {
this.debug('Report loaded successfully');
this.showResultsSection();
this.showReport('text'); // Default to text view
// Load and show graph
if (!this.graphVisualization) {
this.graphVisualization = new GraphVisualization();
}
await this.graphVisualization.loadAndShowGraph(this.currentScanId);
} catch (error) {
console.error(' Error loading report:', error);
console.error('⚠️ Error loading report:', error);
this.showError(`Error loading report: ${error.message}`);
}
}
@@ -410,7 +504,7 @@ class ReconTool {
if (liveSection) {
const title = liveSection.querySelector('h3');
if (title) {
title.textContent = '📊 Final Discovery Summary';
title.textContent = '📊 Final Forensic Summary';
}
liveSection.style.display = 'block';
}
@@ -424,7 +518,7 @@ class ReconTool {
if (progressMessage) progressMessage.style.display = 'none';
if (scanControls) scanControls.style.display = 'none';
this.debug('Showing results section with live discoveries');
this.debug('Showing results section with forensic discoveries');
}
resetToForm() {
@@ -527,11 +621,11 @@ class ReconTool {
content = typeof this.currentReport.json_report === 'string'
? this.currentReport.json_report
: JSON.stringify(this.currentReport.json_report, null, 2);
filename = `recon-report-${this.currentScanId}.json`;
filename = `forensic-recon-report-${this.currentScanId}.json`;
mimeType = 'application/json';
} else {
content = this.currentReport.text_report;
filename = `recon-report-${this.currentScanId}.txt`;
filename = `forensic-recon-report-${this.currentScanId}.txt`;
mimeType = 'text/plain';
}
@@ -548,8 +642,375 @@ class ReconTool {
}
}
class GraphVisualization {
constructor() {
this.svg = null;
this.simulation = null;
this.graphData = null;
this.showLabels = false;
this.selectedNode = null;
this.zoom = null;
this.container = null;
}
async loadAndShowGraph(scanId) {
try {
console.log('🕸️ Loading graph data...');
const response = await fetch(`/api/scan/${scanId}/graph`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const graphData = await response.json();
if (graphData.error) {
throw new Error(graphData.error);
}
this.graphData = graphData;
this.showGraphSection();
this.initializeGraph();
this.updateGraphStats(graphData.stats);
console.log('✅ Graph loaded successfully', graphData.stats);
} catch (error) {
console.error('⚠️ Error loading graph:', error);
alert(`Failed to load graph: ${error.message}`);
}
}
showGraphSection() {
document.getElementById('graphSection').style.display = 'block';
this.bindGraphEvents();
}
bindGraphEvents() {
document.getElementById('showGraph').addEventListener('click', () => {
this.showGraph();
});
document.getElementById('hideGraph').addEventListener('click', () => {
this.hideGraph();
});
document.getElementById('resetZoom').addEventListener('click', () => {
this.resetZoom();
});
document.getElementById('toggleLabels').addEventListener('click', () => {
this.toggleLabels();
});
}
initializeGraph() {
if (!this.graphData) return;
// Clear existing graph
d3.select('#discoveryGraph').selectAll('*').remove();
// Set up SVG
const container = d3.select('#discoveryGraph');
const containerNode = container.node();
const width = containerNode.clientWidth;
const height = containerNode.clientHeight;
this.svg = container
.attr('width', width)
.attr('height', height);
// Set up zoom behavior
this.zoom = d3.zoom()
.scaleExtent([0.1, 3])
.on('zoom', (event) => {
this.container.attr('transform', event.transform);
});
this.svg.call(this.zoom);
// Create container for graph elements
this.container = this.svg.append('g');
// Set up force simulation
this.simulation = d3.forceSimulation(this.graphData.nodes)
.force('link', d3.forceLink(this.graphData.edges)
.id(d => d.id)
.distance(80)
.strength(0.5))
.force('charge', d3.forceManyBody()
.strength(-300)
.distanceMax(400))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide()
.radius(d => d.size + 5));
this.drawGraph();
this.startSimulation();
}
drawGraph() {
// Draw links
const links = this.container.append('g')
.selectAll('line')
.data(this.graphData.edges)
.enter().append('line')
.attr('class', 'graph-link')
.attr('stroke', d => d.color)
.on('mouseover', (event, d) => {
this.showTooltip(event, `Discovery: ${d.method}<br>From: ${d.source}<br>To: ${d.target}`);
})
.on('mouseout', () => {
this.hideTooltip();
});
// Draw nodes
const nodes = this.container.append('g')
.selectAll('circle')
.data(this.graphData.nodes)
.enter().append('circle')
.attr('class', 'graph-node')
.attr('r', d => d.size)
.attr('fill', d => d.color)
.style('opacity', 0.8)
.on('mouseover', (event, d) => {
this.showNodeTooltip(event, d);
this.highlightConnections(d);
})
.on('mouseout', (event, d) => {
this.hideTooltip();
this.unhighlightConnections();
})
.on('click', (event, d) => {
this.selectNode(d);
})
.call(d3.drag()
.on('start', (event, d) => {
if (!event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on('drag', (event, d) => {
d.fx = event.x;
d.fy = event.y;
})
.on('end', (event, d) => {
if (!event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}));
// Draw labels (initially hidden)
const labels = this.container.append('g')
.selectAll('text')
.data(this.graphData.nodes)
.enter().append('text')
.attr('class', 'graph-label')
.attr('dy', '.35em')
.style('opacity', this.showLabels ? 1 : 0)
.text(d => d.label);
// Store references
this.links = links;
this.nodes = nodes;
this.labels = labels;
}
startSimulation() {
this.simulation.on('tick', () => {
this.links
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
this.nodes
.attr('cx', d => d.x)
.attr('cy', d => d.y);
this.labels
.attr('x', d => d.x)
.attr('y', d => d.y + d.size + 12);
});
}
showNodeTooltip(event, node) {
const tooltip = `
<strong>${node.label}</strong><br>
Depth: ${node.depth}<br>
DNS Records: ${node.dns_records}<br>
Certificates: ${node.certificates}<br>
IPs: ${node.ip_addresses.length}<br>
Discovery: ${node.discovery_methods.join(', ')}<br>
First Seen: ${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}
`;
this.showTooltip(event, tooltip);
}
showTooltip(event, content) {
const tooltip = document.getElementById('graphTooltip');
tooltip.innerHTML = content;
tooltip.className = 'graph-tooltip visible';
const rect = tooltip.getBoundingClientRect();
const containerRect = document.getElementById('discoveryGraph').getBoundingClientRect();
tooltip.style.left = `${event.clientX - containerRect.left + 10}px`;
tooltip.style.top = `${event.clientY - containerRect.top - 10}px`;
// Adjust position if tooltip goes off screen
if (event.clientX + rect.width > window.innerWidth) {
tooltip.style.left = `${event.clientX - containerRect.left - rect.width - 10}px`;
}
if (event.clientY - rect.height < 0) {
tooltip.style.top = `${event.clientY - containerRect.top + 20}px`;
}
}
hideTooltip() {
const tooltip = document.getElementById('graphTooltip');
tooltip.className = 'graph-tooltip';
}
highlightConnections(node) {
// Highlight connected links
this.links
.style('opacity', d => (d.source.id === node.id || d.target.id === node.id) ? 1 : 0.2)
.classed('highlighted', d => d.source.id === node.id || d.target.id === node.id);
// Highlight connected nodes
this.nodes
.style('opacity', d => {
if (d.id === node.id) return 1;
const connected = this.graphData.edges.some(edge =>
(edge.source.id === node.id && edge.target.id === d.id) ||
(edge.target.id === node.id && edge.source.id === d.id)
);
return connected ? 0.8 : 0.3;
});
}
unhighlightConnections() {
this.links
.style('opacity', 0.6)
.classed('highlighted', false);
this.nodes
.style('opacity', 0.8);
}
selectNode(node) {
// Update selected node styling
this.nodes
.classed('selected', d => d.id === node.id);
// Show node details
this.showNodeDetails(node);
this.selectedNode = node;
}
showNodeDetails(node) {
const detailsContainer = document.getElementById('nodeDetails');
const selectedInfo = document.getElementById('selectedNodeInfo');
const details = `
<div class="detail-item">
<span class="detail-label">Hostname:</span>
<span class="detail-value">${node.label}</span>
</div>
<div class="detail-item">
<span class="detail-label">Discovery Depth:</span>
<span class="detail-value">${node.depth}</span>
</div>
<div class="detail-item">
<span class="detail-label">DNS Records:</span>
<span class="detail-value">${node.dns_records}</span>
</div>
<div class="detail-item">
<span class="detail-label">Certificates:</span>
<span class="detail-value">${node.certificates}</span>
</div>
<div class="detail-item">
<span class="detail-label">IP Addresses:</span>
<span class="detail-value">${node.ip_addresses.join(', ') || 'None'}</span>
</div>
<div class="detail-item">
<span class="detail-label">Discovery Methods:</span>
<span class="detail-value">${node.discovery_methods.join(', ')}</span>
</div>
<div class="detail-item">
<span class="detail-label">First Seen:</span>
<span class="detail-value">${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}</span>
</div>
`;
detailsContainer.innerHTML = details;
selectedInfo.style.display = 'block';
}
toggleLabels() {
this.showLabels = !this.showLabels;
if (this.labels) {
this.labels.transition()
.duration(300)
.style('opacity', this.showLabels ? 1 : 0);
}
const button = document.getElementById('toggleLabels');
button.textContent = this.showLabels ? 'Hide Labels' : 'Show Labels';
}
resetZoom() {
if (this.svg && this.zoom) {
this.svg.transition()
.duration(750)
.call(this.zoom.transform, d3.zoomIdentity);
}
}
showGraph() {
if (this.container) {
this.container.style('display', 'block');
}
document.getElementById('showGraph').classList.add('active');
document.getElementById('hideGraph').classList.remove('active');
}
hideGraph() {
if (this.container) {
this.container.style('display', 'none');
}
document.getElementById('hideGraph').classList.add('active');
document.getElementById('showGraph').classList.remove('active');
}
updateGraphStats(stats) {
document.getElementById('graphNodes').textContent = stats.node_count || 0;
document.getElementById('graphEdges').textContent = stats.edge_count || 0;
document.getElementById('graphDepth').textContent = stats.max_depth || 0;
}
// Handle window resize
handleResize() {
if (this.svg && this.graphData) {
const containerNode = document.getElementById('discoveryGraph');
const width = containerNode.clientWidth;
const height = containerNode.clientHeight;
this.svg
.attr('width', width)
.attr('height', height);
this.simulation
.force('center', d3.forceCenter(width / 2, height / 2))
.restart();
}
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
console.log('🌐 DNS Reconnaissance Tool initialized with debug mode');
console.log('🌐 Forensic DNS Reconnaissance Tool initialized with debug mode');
new ReconTool();
});

View File

@@ -436,4 +436,253 @@ header p {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
}
/* Add this CSS to the existing style.css file */
/* Graph Section */
.graph-section {
background: #2a2a2a;
border-radius: 4px;
border: 1px solid #444;
box-shadow: inset 0 0 15px rgba(0,0,0,0.5);
padding: 30px;
margin-bottom: 25px;
}
.graph-controls {
margin-bottom: 20px;
text-align: center;
}
.graph-controls button {
margin: 0 5px;
}
.graph-legend {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
padding: 15px;
background: rgba(0,0,0,0.3);
border-radius: 4px;
border: 1px solid #444;
flex-wrap: wrap;
}
.graph-legend h4, .graph-legend h5 {
color: #e0e0e0;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.legend-items, .legend-methods {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
color: #c7c7c7;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #666;
}
.method-item {
font-size: 0.9rem;
color: #a0a0a0;
margin-bottom: 4px;
}
.graph-container {
position: relative;
width: 100%;
height: 600px;
background: #0a0a0a;
border-radius: 4px;
border: 1px solid #333;
margin-bottom: 20px;
overflow: hidden;
}
#discoveryGraph {
width: 100%;
height: 100%;
cursor: grab;
}
#discoveryGraph:active {
cursor: grabbing;
}
.graph-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: #00ff41;
padding: 10px;
border-radius: 4px;
border: 1px solid #00ff41;
font-family: 'Courier New', monospace;
font-size: 0.8rem;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 1000;
max-width: 300px;
line-height: 1.4;
}
.graph-tooltip.visible {
opacity: 1;
}
.graph-info {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 20px;
}
.graph-stats {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.selected-node-info {
background: rgba(0, 40, 0, 0.8);
border: 1px solid #00ff41;
border-radius: 4px;
padding: 15px;
min-width: 250px;
}
.selected-node-info h4 {
color: #00ff41;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
#nodeDetails {
font-family: 'Courier New', monospace;
font-size: 0.8rem;
line-height: 1.4;
color: #c7c7c7;
}
#nodeDetails .detail-item {
margin-bottom: 8px;
border-bottom: 1px solid #333;
padding-bottom: 4px;
}
#nodeDetails .detail-item:last-child {
border-bottom: none;
}
#nodeDetails .detail-label {
color: #00ff41;
font-weight: bold;
}
#nodeDetails .detail-value {
color: #c7c7c7;
margin-left: 10px;
}
/* Graph Nodes and Links Styling (applied via D3) */
.graph-node {
stroke: #333;
stroke-width: 2px;
cursor: pointer;
transition: all 0.3s ease;
}
.graph-node:hover {
stroke: #fff;
stroke-width: 3px;
}
.graph-node.selected {
stroke: #ff9900;
stroke-width: 4px;
}
.graph-link {
stroke-opacity: 0.6;
stroke-width: 2px;
transition: all 0.3s ease;
}
.graph-link:hover {
stroke-opacity: 1;
stroke-width: 3px;
}
.graph-link.highlighted {
stroke-opacity: 1;
stroke-width: 3px;
}
.graph-label {
font-family: 'Courier New', monospace;
font-size: 10px;
fill: #c7c7c7;
text-anchor: middle;
pointer-events: none;
opacity: 0.8;
}
.graph-label.visible {
opacity: 1;
}
/* Responsive adjustments for graph */
@media (max-width: 768px) {
.graph-container {
height: 400px;
}
.graph-legend {
flex-direction: column;
gap: 15px;
}
.legend-items, .legend-methods {
flex-direction: row;
flex-wrap: wrap;
gap: 15px;
}
.graph-info {
flex-direction: column;
align-items: stretch;
}
.graph-stats {
justify-content: center;
}
.selected-node-info {
min-width: auto;
width: 100%;
}
.graph-tooltip {
font-size: 0.7rem;
max-width: 250px;
}
}