UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

521 lines 19.7 kB
"use strict"; /** * Network Scanner * Port scanning, service detection, and network mapping */ Object.defineProperty(exports, "__esModule", { value: true }); exports.NetworkScanner = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const dns = tslib_1.__importStar(require("dns/promises")); const events_1 = require("events"); const net = tslib_1.__importStar(require("net")); const types_1 = require("../types"); var ScanTechnique; (function (ScanTechnique) { ScanTechnique["TCP_CONNECT"] = "TCP_CONNECT"; ScanTechnique["SYN_SCAN"] = "SYN_SCAN"; ScanTechnique["UDP_SCAN"] = "UDP_SCAN"; ScanTechnique["XMAS_SCAN"] = "XMAS_SCAN"; ScanTechnique["NULL_SCAN"] = "NULL_SCAN"; ScanTechnique["FIN_SCAN"] = "FIN_SCAN"; })(ScanTechnique || (ScanTechnique = {})); class NetworkScanner extends events_1.EventEmitter { constructor() { super(); this.commonPorts = [ 21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1723, 3306, 3389, 5900, 8080, 8443 ]; this.serviceSignatures = new Map([ [21, 'FTP'], [22, 'SSH'], [23, 'Telnet'], [25, 'SMTP'], [53, 'DNS'], [80, 'HTTP'], [110, 'POP3'], [111, 'RPC'], [135, 'RPC'], [139, 'NetBIOS'], [143, 'IMAP'], [443, 'HTTPS'], [445, 'SMB'], [993, 'IMAPS'], [995, 'POP3S'], [1433, 'MSSQL'], [1723, 'PPTP'], [3306, 'MySQL'], [3389, 'RDP'], [5432, 'PostgreSQL'], [5900, 'VNC'], [8080, 'HTTP-Alt'], [8443, 'HTTPS-Alt'] ]); } /** * Scan a single target */ async scanTarget(target, options = {}) { const startTime = new Date(); this.emit('scan:started', { target: target.address }); try { // Resolve hostname if needed const ip = await this.resolveTarget(target.address); // Determine ports to scan const ports = this.parsePorts(options.ports || this.commonPorts); // Scan ports const openPorts = await this.scanPorts(ip, ports, options); // Detect services const services = options.serviceDetection ? await this.detectServices(ip, openPorts) : openPorts.map(port => this.guessService(port)); // Check for vulnerabilities const vulnerabilities = await this.checkVulnerabilities(target, services); // OS detection const osInfo = options.osDetection ? await this.detectOS(ip, openPorts) : undefined; const result = { target, startTime, endTime: new Date(), vulnerabilities, openPorts, services, metadata: { ip, hostname: target.address !== ip ? target.address : undefined, os: osInfo } }; this.emit('scan:completed', { target: target.address, result }); return result; } catch (error) { this.emit('scan:failed', { target: target.address, error }); throw error; } } /** * Scan a network range */ async scanNetwork(range, options = {}) { const startTime = new Date(); const hosts = this.expandRange(range); const discoveredHosts = []; const allVulnerabilities = []; const networkMap = { nodes: [], edges: [] }; this.emit('network-scan:started', { range, hostCount: hosts.length }); // Discover live hosts for (const host of hosts) { const isAlive = await this.isHostAlive(host); if (isAlive) { discoveredHosts.push(host); // Add to network map const node = { id: host, ip: host, type: 'HOST' }; networkMap.nodes.push(node); } } this.emit('network-scan:discovery', { discoveredHosts: discoveredHosts.length, total: hosts.length }); // Scan discovered hosts for (const host of discoveredHosts) { const target = { id: crypto.randomUUID(), type: 'HOST', address: host }; const result = await this.scanTarget(target, options); allVulnerabilities.push(...result.vulnerabilities); // Update network map with OS info const node = networkMap.nodes.find(n => n.ip === host); if (node && result.metadata?.os) { node.os = result.metadata.os; } } // Map network topology await this.mapTopology(networkMap); const result = { target: { id: crypto.randomUUID(), type: 'NETWORK', address: range }, startTime, endTime: new Date(), vulnerabilities: allVulnerabilities, discoveredHosts, networkMap }; this.emit('network-scan:completed', { range, result }); return result; } /** * Private methods */ async resolveTarget(address) { // Check if already an IP if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(address)) { return address; } // Resolve hostname try { const result = await dns.resolve4(address); return result[0]; } catch { throw new Error(`Failed to resolve hostname: ${address}`); } } parsePorts(ports) { if (Array.isArray(ports)) { return ports; } // Parse port range string (e.g., "1-1000,8080,8443") const portList = []; const parts = ports.split(','); for (const part of parts) { if (part.includes('-')) { const [start, end] = part.split('-').map(Number); for (let port = start; port <= end; port++) { portList.push(port); } } else { portList.push(Number(part)); } } return portList; } async scanPorts(ip, ports, options) { const openPorts = []; const timeout = options.timeout || 1000; const threads = options.threads || 10; // Batch ports for parallel scanning const batches = []; for (let i = 0; i < ports.length; i += threads) { batches.push(ports.slice(i, i + threads)); } for (const batch of batches) { const promises = batch.map(port => this.checkPort(ip, port, timeout)); const results = await Promise.all(promises); results.forEach((isOpen, index) => { if (isOpen) { openPorts.push(batch[index]); this.emit('port:open', { ip, port: batch[index] }); } }); } return openPorts; } async checkPort(ip, port, timeout) { return new Promise((resolve) => { const socket = new net.Socket(); let connected = false; socket.setTimeout(timeout); socket.on('connect', () => { connected = true; socket.destroy(); resolve(true); }); socket.on('timeout', () => { socket.destroy(); resolve(false); }); socket.on('error', () => { socket.destroy(); resolve(false); }); socket.connect(port, ip); }); } guessService(port) { return { port, protocol: 'TCP', name: this.serviceSignatures.get(port) || 'Unknown' }; } async detectServices(ip, ports) { const services = []; for (const port of ports) { const banner = await this.grabBanner(ip, port); const service = this.guessService(port); if (banner) { service.banner = banner; // Try to identify service from banner const identified = this.identifyService(banner); if (identified) { service.name = identified.name; service.version = identified.version; } } services.push(service); } return services; } async grabBanner(ip, port) { return new Promise((resolve) => { const socket = new net.Socket(); let banner = ''; socket.setTimeout(2000); socket.on('data', (data) => { banner += data.toString(); socket.destroy(); resolve(banner.trim()); }); socket.on('timeout', () => { socket.destroy(); resolve(undefined); }); socket.on('error', () => { socket.destroy(); resolve(undefined); }); socket.connect(port, ip, () => { // Send probe for some services if (port === 80 || port === 8080) { socket.write('HEAD / HTTP/1.0\r\n\r\n'); } else if (port === 21) { // FTP sends banner automatically } else if (port === 22) { // SSH sends banner automatically } }); }); } identifyService(banner) { // Common service patterns const patterns = [ { regex: /SSH-[\d.]+-([\w\d.-]+)/, name: 'SSH', versionGroup: 1 }, { regex: /Server: Apache\/([\d.]+)/, name: 'Apache', versionGroup: 1 }, { regex: /Server: nginx\/([\d.]+)/, name: 'nginx', versionGroup: 1 }, { regex: /220.*FTP/, name: 'FTP' }, { regex: /MySQL.*\s([\d.]+)/, name: 'MySQL', versionGroup: 1 }, { regex: /PostgreSQL\s([\d.]+)/, name: 'PostgreSQL', versionGroup: 1 } ]; for (const pattern of patterns) { const match = banner.match(pattern.regex); if (match) { return { name: pattern.name, version: pattern.versionGroup ? match[pattern.versionGroup] : undefined }; } } return undefined; } async checkVulnerabilities(target, services) { const vulnerabilities = []; for (const service of services) { // Check for insecure services if (service.name === 'Telnet') { vulnerabilities.push(this.createVulnerability('Telnet Service Enabled', 'Telnet transmits data in cleartext, including passwords', types_1.VulnerabilitySeverity.HIGH, types_1.VulnerabilityCategory.SENSITIVE_DATA_EXPOSURE, target, service)); } if (service.name === 'FTP' && service.port === 21) { vulnerabilities.push(this.createVulnerability('FTP Service Enabled', 'FTP transmits credentials in cleartext', types_1.VulnerabilitySeverity.MEDIUM, types_1.VulnerabilityCategory.SENSITIVE_DATA_EXPOSURE, target, service)); } // Check for outdated versions if (service.version) { const vuln = this.checkVersionVulnerabilities(service); if (vuln) { vulnerabilities.push({ ...vuln, affectedTarget: target }); } } // Check for default ports of sensitive services if (service.name === 'RDP' && service.port === 3389) { vulnerabilities.push(this.createVulnerability('RDP on Default Port', 'Remote Desktop Protocol exposed on default port increases attack surface', types_1.VulnerabilitySeverity.MEDIUM, types_1.VulnerabilityCategory.SECURITY_MISCONFIGURATION, target, service)); } } return vulnerabilities; } createVulnerability(title, description, severity, category, target, service) { return { id: crypto.randomUUID(), title, description, severity, category, discoveredAt: new Date(), discoveredBy: 'NetworkScanner', testType: 'NETWORK', affectedTarget: target, affectedComponent: `${service.name}:${service.port}`, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [ { type: types_1.EvidenceType.LOG, data: `Service ${service.name} detected on port ${service.port}`, timestamp: new Date() } ], exploitStatus: types_1.ExploitStatus.NOT_ATTEMPTED, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.LOW, availability: types_1.ImpactLevel.NONE }, remediation: { summary: `Disable or secure ${service.name} service`, steps: [ `Disable ${service.name} if not required`, 'Use encrypted alternatives (SSH instead of Telnet, SFTP instead of FTP)', 'Implement network segmentation', 'Apply access control lists' ], effort: 'LOW', priority: severity === types_1.VulnerabilitySeverity.HIGH ? 'IMMEDIATE' : 'HIGH', retestRequired: true }, riskScore: severity === types_1.VulnerabilitySeverity.HIGH ? 8 : 6, likelihood: 'MEDIUM' }; } checkVersionVulnerabilities(service) { // Mock version vulnerability database const vulnerableVersions = { 'Apache': [ { version: '2.4.49', cve: 'CVE-2021-41773', severity: types_1.VulnerabilitySeverity.CRITICAL } ], 'nginx': [ { version: '1.18.0', cve: 'CVE-2021-23017', severity: types_1.VulnerabilitySeverity.HIGH } ], 'SSH': [ { version: 'OpenSSH_7.4', cve: 'CVE-2018-15473', severity: types_1.VulnerabilitySeverity.MEDIUM } ] }; const vulns = vulnerableVersions[service.name]; if (!vulns || !service.version) return undefined; const vuln = vulns.find(v => service.version?.includes(v.version)); if (!vuln) return undefined; return { id: crypto.randomUUID(), title: `${service.name} ${vuln.cve} Vulnerability`, description: `${service.name} version ${service.version} is vulnerable to ${vuln.cve}`, severity: vuln.severity, category: types_1.VulnerabilityCategory.USING_VULNERABLE_COMPONENTS, cve: [vuln.cve], discoveredAt: new Date(), discoveredBy: 'NetworkScanner', testType: 'NETWORK', affectedTarget: {}, // Will be set by caller affectedComponent: `${service.name} ${service.version}`, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [ { type: types_1.EvidenceType.LOG, data: `Version ${service.version} detected`, timestamp: new Date() } ], exploitStatus: types_1.ExploitStatus.NOT_ATTEMPTED, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.HIGH, availability: types_1.ImpactLevel.HIGH }, remediation: { summary: `Update ${service.name} to latest version`, steps: [ `Update ${service.name} to a patched version`, 'Apply vendor security patches', 'Monitor vendor security advisories' ], effort: 'MEDIUM', priority: 'IMMEDIATE', references: [`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${vuln.cve}`], retestRequired: true }, riskScore: 9, likelihood: 'HIGH' }; } async detectOS(ip, openPorts) { // Simple OS fingerprinting based on open ports and services const hasSSH = openPorts.includes(22); const hasRDP = openPorts.includes(3389); const hasSMB = openPorts.includes(445); const hasNetBIOS = openPorts.includes(139); if (hasRDP || (hasSMB && hasNetBIOS)) { return 'Windows'; } else if (hasSSH && !hasRDP) { return 'Linux/Unix'; } return 'Unknown'; } expandRange(range) { const hosts = []; // Simple CIDR expansion (e.g., 192.168.1.0/24) if (range.includes('/')) { const [baseIP, cidr] = range.split('/'); const bits = 32 - parseInt(cidr); const hostCount = Math.pow(2, bits) - 2; // Exclude network and broadcast const parts = baseIP.split('.').map(Number); const baseNum = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]; for (let i = 1; i <= hostCount && i <= 254; i++) { const num = baseNum + i; const ip = [ (num >> 24) & 255, (num >> 16) & 255, (num >> 8) & 255, num & 255 ].join('.'); hosts.push(ip); } } else { // Single host hosts.push(range); } return hosts; } async isHostAlive(host) { // Try common ports to check if host is alive const checkPorts = [80, 443, 22, 445, 3389]; for (const port of checkPorts) { const isOpen = await this.checkPort(host, port, 500); if (isOpen) return true; } return false; } async mapTopology(topology) { // Simple topology mapping - connect hosts in same subnet for (let i = 0; i < topology.nodes.length; i++) { for (let j = i + 1; j < topology.nodes.length; j++) { const node1 = topology.nodes[i]; const node2 = topology.nodes[j]; // Check if in same subnet (simple /24 check) const ip1Parts = node1.ip.split('.').slice(0, 3).join('.'); const ip2Parts = node2.ip.split('.').slice(0, 3).join('.'); if (ip1Parts === ip2Parts) { topology.edges.push({ source: node1.id, target: node2.id, type: 'DIRECT' }); } } } } } exports.NetworkScanner = NetworkScanner; //# sourceMappingURL=network-scanner.js.map