mirror of
https://github.com/MrUnknownDE/utools.git
synced 2026-05-01 20:13:46 +02:00
add Port-Scanner
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// backend/routes/portScan.js
|
||||
const express = require('express');
|
||||
const Sentry = require("@sentry/node");
|
||||
const pino = require('pino');
|
||||
const { isValidIp, isPrivateIp, checkPort } = require('../utils');
|
||||
|
||||
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
|
||||
const router = express.Router();
|
||||
|
||||
const COMMON_PORTS_TO_SCAN = [
|
||||
21, 22, 25, 53, 80, 110, 143, 443, 3306, 3389, 5432, 8080, 8443
|
||||
];
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const targetIpRaw = req.query.targetIp;
|
||||
const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw;
|
||||
const requestIp = req.ip || req.socket.remoteAddress;
|
||||
|
||||
logger.info({ requestIp, targetIp }, 'Port scan stream request received');
|
||||
|
||||
if (!isValidIp(targetIp)) {
|
||||
logger.warn({ requestIp, targetIp }, 'Invalid target IP for port scan');
|
||||
return res.status(400).json({ success: false, error: 'Invalid target IP address provided.' });
|
||||
}
|
||||
if (isPrivateIp(targetIp)) {
|
||||
logger.warn({ requestIp, targetIp }, 'Attempt to scan private IP blocked');
|
||||
return res.status(403).json({ success: false, error: 'Operations on private or local IP addresses are not allowed.' });
|
||||
}
|
||||
|
||||
Sentry.configureScope(scope => {
|
||||
scope.setContext("port_scan_details", { targetIp, requestIp });
|
||||
});
|
||||
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
res.setHeader('X-Accel-Buffering', 'no');
|
||||
res.flushHeaders();
|
||||
|
||||
const sendEvent = (event, data) => {
|
||||
if (!res.writableEnded) {
|
||||
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
let isConnectionClosed = false;
|
||||
req.on('close', () => {
|
||||
logger.info({ requestIp, targetIp }, 'Client disconnected from port scan stream.');
|
||||
isConnectionClosed = true;
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
sendEvent('info', { message: `Starting scan of ${COMMON_PORTS_TO_SCAN.length} common ports on ${targetIp}...` });
|
||||
|
||||
for (const port of COMMON_PORTS_TO_SCAN) {
|
||||
if (isConnectionClosed) break;
|
||||
const result = await checkPort(port, targetIp, 2000);
|
||||
sendEvent('port_status', result);
|
||||
}
|
||||
|
||||
if (!isConnectionClosed) {
|
||||
logger.info({ requestIp, targetIp }, 'Port scan stream completed successfully.');
|
||||
sendEvent('end', { message: 'Scan complete.' });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ requestIp, targetIp, error: error.message }, 'Error during port scan stream');
|
||||
Sentry.captureException(error);
|
||||
if (!isConnectionClosed) {
|
||||
sendEvent('error', { error: 'An unexpected error occurred during the scan.' });
|
||||
}
|
||||
} finally {
|
||||
if (!res.writableEnded) {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,3 +1,4 @@
|
||||
// backend/server.js
|
||||
// server.js
|
||||
// Load .env variables FIRST!
|
||||
require('dotenv').config();
|
||||
@@ -33,6 +34,7 @@ const lookupRoutes = require('./routes/lookup');
|
||||
const dnsLookupRoutes = require('./routes/dnsLookup');
|
||||
const whoisLookupRoutes = require('./routes/whoisLookup');
|
||||
const versionRoutes = require('./routes/version');
|
||||
const portScanRoutes = require('./routes/portScan');
|
||||
|
||||
// --- Logger Initialisierung ---
|
||||
const logger = pino({
|
||||
@@ -94,6 +96,7 @@ app.use('/api/traceroute', generalLimiter);
|
||||
app.use('/api/lookup', generalLimiter);
|
||||
app.use('/api/dns-lookup', generalLimiter);
|
||||
app.use('/api/whois-lookup', generalLimiter);
|
||||
app.use('/api/port-scan', generalLimiter);
|
||||
|
||||
|
||||
// --- API Routes ---
|
||||
@@ -105,6 +108,7 @@ app.use('/api/lookup', lookupRoutes);
|
||||
app.use('/api/dns-lookup', dnsLookupRoutes);
|
||||
app.use('/api/whois-lookup', whoisLookupRoutes);
|
||||
app.use('/api/version', versionRoutes);
|
||||
app.use('/api/port-scan', portScanRoutes);
|
||||
|
||||
|
||||
// --- Sentry Error Handler ---
|
||||
|
||||
+43
-2
@@ -1,4 +1,3 @@
|
||||
// backend/utils.js
|
||||
const net = require('net'); // Node.js built-in module for IP validation
|
||||
const { spawn } = require('child_process');
|
||||
const pino = require('pino'); // Import pino for logging within utils if needed
|
||||
@@ -256,6 +255,48 @@ function parseTracerouteLine(line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific TCP port is open on a given host.
|
||||
* @param {number} port - The port to check.
|
||||
* @param {string} host - The target host IP address.
|
||||
* @param {number} timeout - Connection timeout in milliseconds.
|
||||
* @returns {Promise<{port: number, status: 'open'|'closed'|'timeout', service: string}>} A promise that resolves with the port status.
|
||||
*/
|
||||
function checkPort(port, host, timeout = 2000) {
|
||||
// A small map of common ports to their services
|
||||
const commonPorts = {
|
||||
21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP', 53: 'DNS', 80: 'HTTP',
|
||||
110: 'POP3', 143: 'IMAP', 443: 'HTTPS', 445: 'SMB', 993: 'IMAPS',
|
||||
995: 'POP3S', 1433: 'MSSQL', 1521: 'Oracle', 3306: 'MySQL', 3389: 'RDP',
|
||||
5432: 'PostgreSQL', 5900: 'VNC', 8080: 'HTTP-Alt', 8443: 'HTTPS-Alt'
|
||||
};
|
||||
const service = commonPorts[port] || 'Unknown';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket();
|
||||
socket.setTimeout(timeout);
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.destroy();
|
||||
resolve({ port, status: 'open', service });
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
resolve({ port, status: 'timeout', service });
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
socket.destroy();
|
||||
// 'ECONNREFUSED' is the key for a closed port. Other errors might be network issues.
|
||||
const status = err.code === 'ECONNREFUSED' ? 'closed' : 'error';
|
||||
resolve({ port, status, service, error: err.code });
|
||||
});
|
||||
|
||||
socket.connect(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
isValidIp,
|
||||
@@ -265,5 +306,5 @@ module.exports = {
|
||||
executeCommand,
|
||||
parsePingOutput,
|
||||
parseTracerouteLine,
|
||||
// Note: logger is not exported, assuming it's managed globally or passed where needed
|
||||
checkPort,
|
||||
};
|
||||
+12
-1
@@ -217,6 +217,7 @@
|
||||
<div class="mt-4 space-x-2">
|
||||
<button id="lookup-ping-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded text-sm transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed" disabled>Ping this IP</button>
|
||||
<button id="lookup-trace-button" class="bg-teal-600 hover:bg-teal-700 text-white font-bold py-1 px-3 rounded text-sm transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed" disabled>Trace this IP</button>
|
||||
<button id="lookup-scan-button" class="bg-red-600 hover:bg-red-700 text-white font-bold py-1 px-3 rounded text-sm transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed" disabled>Scan Ports</button>
|
||||
</div>
|
||||
<!-- Bereich für Ping-Ergebnisse (Lookup) -->
|
||||
<div id="lookup-ping-results" class="mt-2 text-sm hidden">
|
||||
@@ -239,6 +240,16 @@
|
||||
<div id="traceroute-output"><pre></pre></div>
|
||||
</div>
|
||||
|
||||
<!-- Bereich für Port Scan -->
|
||||
<div id="port-scan-section" class="mt-8 p-4 bg-gray-700 rounded hidden">
|
||||
<h2 class="text-xl font-semibold text-purple-300 border-b border-purple-500 pb-1 mb-4">Port Scan Results</h2>
|
||||
<div id="port-scan-status" class="flex items-center mb-2">
|
||||
<div id="port-scan-loader" class="loader mr-2 hidden"></div>
|
||||
<span id="port-scan-message" class="text-gray-400"></span>
|
||||
</div>
|
||||
<div id="port-scan-output" class="text-sm font-mono"></div>
|
||||
</div>
|
||||
|
||||
<!-- Globaler Fehlerbereich -->
|
||||
<div id="global-error" class="mt-6 p-4 bg-red-800 text-red-100 rounded hidden"></div>
|
||||
|
||||
@@ -258,4 +269,4 @@
|
||||
<!-- Eigene JS-Logik -->
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -67,6 +67,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const tracerouteLoader = document.getElementById('traceroute-loader');
|
||||
const tracerouteMessage = document.getElementById('traceroute-message');
|
||||
|
||||
// --- DOM Elements (Port Scan) ---
|
||||
const portScanSection = document.getElementById('port-scan-section');
|
||||
const portScanOutputEl = document.getElementById('port-scan-output');
|
||||
const portScanLoader = document.getElementById('port-scan-loader');
|
||||
const portScanMessage = document.getElementById('port-scan-message');
|
||||
const lookupScanButton = document.getElementById('lookup-scan-button');
|
||||
|
||||
// --- DOM Elements (Footer) ---
|
||||
const commitShaEl = document.getElementById('commit-sha');
|
||||
|
||||
@@ -79,6 +86,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let currentIp = null; // Store the user's fetched IP
|
||||
let currentLookupIp = null; // Store the last successfully looked-up IP
|
||||
let eventSource = null; // Store the EventSource instance for traceroute
|
||||
let portScanEventSource = null; // Store the EventSource for port scan
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
@@ -361,6 +369,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (lookupPingLoader) lookupPingLoader.classList.add('hidden');
|
||||
if (lookupPingOutputEl) lookupPingOutputEl.textContent = '';
|
||||
if (lookupPingErrorEl) lookupPingErrorEl.textContent = '';
|
||||
if (portScanSection) portScanSection.classList.add('hidden'); // Hide port scan results
|
||||
if (portScanOutputEl) portScanOutputEl.innerHTML = '';
|
||||
hideLookupStatus(); // Hide status on reset
|
||||
|
||||
const fieldsToClear = [
|
||||
@@ -373,6 +383,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (lookupPingButton) lookupPingButton.disabled = true;
|
||||
if (lookupTraceButton) lookupTraceButton.disabled = true;
|
||||
if (lookupScanButton) lookupScanButton.disabled = true;
|
||||
currentLookupIp = null;
|
||||
|
||||
// Remove lookup map instance if it exists
|
||||
@@ -380,6 +391,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
window['lookup-map_instance'].remove();
|
||||
window['lookup-map_instance'] = null;
|
||||
}
|
||||
|
||||
if (portScanEventSource) {
|
||||
portScanEventSource.close();
|
||||
portScanEventSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Ruft Informationen für eine spezifische IP ab */
|
||||
@@ -422,6 +438,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (lookupPingButton) lookupPingButton.disabled = false;
|
||||
if (lookupTraceButton) lookupTraceButton.disabled = false;
|
||||
if (lookupScanButton) lookupScanButton.disabled = false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch lookup info for ${ipToLookup}:`, error);
|
||||
@@ -653,6 +670,91 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
tracerouteOutputEl.scrollTop = tracerouteOutputEl.scrollHeight;
|
||||
}
|
||||
|
||||
// --- Port Scan Functions ---
|
||||
function startPortScan(ip) {
|
||||
if (!ip) {
|
||||
showGlobalError('Cannot start port scan: IP address is missing.');
|
||||
return;
|
||||
}
|
||||
if (!portScanSection || !portScanOutputEl || !portScanLoader || !portScanMessage) return;
|
||||
|
||||
if (portScanEventSource) {
|
||||
portScanEventSource.close();
|
||||
}
|
||||
|
||||
portScanSection.classList.remove('hidden');
|
||||
portScanOutputEl.innerHTML = '';
|
||||
portScanLoader.classList.remove('hidden');
|
||||
portScanMessage.textContent = `Starting port scan for ${ip}...`;
|
||||
hideGlobalError();
|
||||
hideLookupError();
|
||||
|
||||
const url = `${API_BASE_URL}/port-scan?targetIp=${encodeURIComponent(ip)}`;
|
||||
portScanEventSource = new EventSource(url);
|
||||
|
||||
portScanEventSource.onopen = () => {
|
||||
console.log('SSE connection opened for port scan.');
|
||||
};
|
||||
|
||||
portScanEventSource.onerror = (event) => {
|
||||
console.error('Port Scan EventSource failed:', event);
|
||||
portScanMessage.textContent = 'Connection error during port scan.';
|
||||
portScanLoader.classList.add('hidden');
|
||||
portScanEventSource.close();
|
||||
};
|
||||
|
||||
portScanEventSource.addEventListener('info', (event) => {
|
||||
const infoData = JSON.parse(event.data);
|
||||
portScanMessage.textContent = infoData.message;
|
||||
});
|
||||
|
||||
portScanEventSource.addEventListener('port_status', (event) => {
|
||||
const portData = JSON.parse(event.data);
|
||||
displayPortScanResult(portData);
|
||||
});
|
||||
|
||||
portScanEventSource.addEventListener('error', (event) => {
|
||||
const errorData = JSON.parse(event.data);
|
||||
displayPortScanResult({ error: errorData.error });
|
||||
});
|
||||
|
||||
portScanEventSource.addEventListener('end', (event) => {
|
||||
const endData = JSON.parse(event.data);
|
||||
portScanMessage.textContent = endData.message;
|
||||
portScanLoader.classList.add('hidden');
|
||||
portScanEventSource.close();
|
||||
});
|
||||
}
|
||||
|
||||
function displayPortScanResult(data) {
|
||||
if (!portScanOutputEl) return;
|
||||
const lineDiv = document.createElement('div');
|
||||
lineDiv.classList.add('mb-1');
|
||||
|
||||
let statusColor = 'text-gray-400';
|
||||
let statusText = data.status.toUpperCase();
|
||||
if (data.status === 'open') {
|
||||
statusColor = 'text-green-400';
|
||||
statusText = 'OPEN';
|
||||
} else if (data.status === 'closed') {
|
||||
statusColor = 'text-red-400';
|
||||
statusText = 'CLOSED';
|
||||
} else if (data.status === 'timeout') {
|
||||
statusColor = 'text-yellow-400';
|
||||
statusText = 'TIMEOUT (Filtered?)';
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
lineDiv.innerHTML = `<span class="text-red-400">Error: ${data.error}</span>`;
|
||||
} else {
|
||||
lineDiv.innerHTML = `Port <span class="font-bold w-12 inline-block">${data.port}</span> <span class="w-24 inline-block">(${data.service})</span>: <span class="font-bold ${statusColor}">${statusText}</span>`;
|
||||
}
|
||||
|
||||
portScanOutputEl.appendChild(lineDiv);
|
||||
portScanOutputEl.scrollTop = portScanOutputEl.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
// --- Event Handlers ---
|
||||
function handleIpClick(event) {
|
||||
event.preventDefault(); // Verhindert das Standardverhalten des Links (#)
|
||||
@@ -718,6 +820,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function handleLookupScanClick() {
|
||||
if (currentLookupIp) {
|
||||
console.log(`Starting port scan for looked-up IP: ${currentLookupIp}`);
|
||||
startPortScan(currentLookupIp);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Initial Load & Event Listeners ---
|
||||
fetchIpInfo(); // Lade Infos zur eigenen IP
|
||||
fetchVersionInfo(); // Lade Versionsinfo für Footer
|
||||
@@ -729,6 +838,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
if (lookupPingButton) lookupPingButton.addEventListener('click', handleLookupPingClick);
|
||||
if (lookupTraceButton) lookupTraceButton.addEventListener('click', handleLookupTraceClick);
|
||||
if (lookupScanButton) lookupScanButton.addEventListener('click', handleLookupScanClick);
|
||||
|
||||
// Der Event Listener für den IP-Link wird jetzt in fetchIpInfo() hinzugefügt,
|
||||
// nachdem die IP erfolgreich abgerufen wurde.
|
||||
|
||||
Reference in New Issue
Block a user