@iota-big3/sdk-security
Version:
Advanced security features including zero trust, quantum-safe crypto, and ML threat detection
521 lines • 19.7 kB
JavaScript
;
/**
* 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