UNPKG

websec-audit

Version:

A universal security scanning and audit tool for websites

1,274 lines (1,267 loc) 38.2 kB
'use strict'; var tls = require('tls'); var net = require('net'); var dns2 = require('dns'); var crypto = require('crypto'); var util = require('util'); var axios = require('axios'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var tls__namespace = /*#__PURE__*/_interopNamespace(tls); var net__namespace = /*#__PURE__*/_interopNamespace(net); var dns2__namespace = /*#__PURE__*/_interopNamespace(dns2); var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto); var axios__default = /*#__PURE__*/_interopDefault(axios); // src/backend/TLS_SSL_Scanner.ts var makeRequest = async (url, options) => { try { const config = { url, method: options?.method || "GET", headers: { "User-Agent": "Mozilla/5.0 (compatible; SecurityScanner/1.0)", ...options?.headers }, timeout: options?.timeout || 1e4, // Default 10s timeout data: options?.data, validateStatus: () => true // Don't throw on any HTTP status code }; const response = await axios__default.default(config); return { status: response.status, headers: response.headers, data: response.data, error: null }; } catch (error) { return { status: 0, headers: {}, data: null, error: error.message || "Request failed" }; } }; var normalizeUrl = (input) => { if (!input) return ""; let url = input; if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } try { const parsed = new URL(url); return parsed.origin; } catch (e) { return url; } }; var extractDomain = (url) => { try { const parsed = new URL(normalizeUrl(url)); let domain = parsed.hostname; if (domain.startsWith("www.")) { domain = domain.substring(4); } return domain; } catch (e) { return url.replace(/^(https?:\/\/)?(www\.)?/, "").split("/")[0]; } }; var createScannerInput = (target) => { if (typeof target === "string") { return { target: normalizeUrl(target), timeout: 1e4 }; } return { ...target, target: normalizeUrl(target.target) }; }; // src/backend/TLS_SSL_Scanner.ts var dnsLookup = util.promisify(dns2__namespace.lookup); var CIPHER_RATINGS = { // Strong modern ciphers "TLS_AES_256_GCM_SHA384": "strong", "TLS_AES_128_GCM_SHA256": "strong", "TLS_CHACHA20_POLY1305_SHA256": "strong", // Recommended ciphers "ECDHE-ECDSA-AES256-GCM-SHA384": "recommended", "ECDHE-RSA-AES256-GCM-SHA384": "recommended", "ECDHE-ECDSA-AES128-GCM-SHA256": "recommended", "ECDHE-RSA-AES128-GCM-SHA256": "recommended", "ECDHE-ECDSA-CHACHA20-POLY1305": "recommended", "ECDHE-RSA-CHACHA20-POLY1305": "recommended", // Adequate ciphers "DHE-RSA-AES256-GCM-SHA384": "adequate", "DHE-RSA-AES128-GCM-SHA256": "adequate", "ECDHE-ECDSA-AES256-SHA384": "adequate", "ECDHE-RSA-AES256-SHA384": "adequate", "ECDHE-ECDSA-AES128-SHA256": "adequate", "ECDHE-RSA-AES128-SHA256": "adequate", // Weak ciphers - should be avoided "ECDHE-RSA-AES256-SHA": "weak", "ECDHE-ECDSA-AES256-SHA": "weak", "DHE-RSA-AES256-SHA": "weak", "ECDHE-RSA-AES128-SHA": "weak", "ECDHE-ECDSA-AES128-SHA": "weak", "DHE-RSA-AES128-SHA": "weak", "RSA-AES256-GCM-SHA384": "weak", "RSA-AES128-GCM-SHA256": "weak", "RSA-AES256-SHA256": "weak", "RSA-AES128-SHA256": "weak", "RSA-AES256-SHA": "weak", "RSA-AES128-SHA": "weak", // Insecure ciphers - should never be used "DES-CBC3-SHA": "insecure", "ECDHE-RSA-DES-CBC3-SHA": "insecure", "EDH-RSA-DES-CBC3-SHA": "insecure", "RC4-SHA": "insecure", "RC4-MD5": "insecure", "NULL-SHA": "insecure", "NULL-MD5": "insecure", "EXP-RC4-MD5": "insecure", "EXP-DES-CBC-SHA": "insecure" }; var PROTOCOL_RATINGS = { "TLSv1.3": { rating: "secure", description: "Modern, secure protocol with perfect forward secrecy and improved handshake encryption" }, "TLSv1.2": { rating: "recommended", description: "Secure protocol when configured properly, widely supported" }, "TLSv1.1": { rating: "weak", description: "Outdated protocol with known vulnerabilities, should be disabled" }, "TLSv1": { rating: "insecure", description: "Outdated protocol with known vulnerabilities, should be disabled" }, "SSLv3": { rating: "insecure", description: "Insecure protocol affected by POODLE vulnerability, must be disabled" }, "SSLv2": { rating: "insecure", description: "Critically insecure legacy protocol, must be disabled" } }; var KNOWN_VULNERABILITIES = [ { name: "BEAST", affects: ["TLSv1"], description: "Browser Exploit Against SSL/TLS. Affects CBC ciphers in TLS 1.0 and earlier.", severity: "high" }, { name: "POODLE", affects: ["SSLv3"], description: "Padding Oracle On Downgraded Legacy Encryption. Affects all SSLv3 connections.", severity: "high" }, { name: "FREAK", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "Forcing RSA Export Keys. Server supports export-grade cipher suites.", severity: "high", testFor: (cipher) => cipher.includes("EXP") }, { name: "LOGJAM", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "Weak Diffie-Hellman key exchange. Server may use weak DH parameters.", severity: "high", testFor: (cipher) => cipher.includes("DHE") && cipher.includes("EXPORT") }, { name: "ROBOT", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "Return Of Bleichenbacher's Oracle Threat. RSA padding oracle vulnerability.", severity: "high", testFor: (cipher) => cipher.startsWith("RSA") }, { name: "LUCKY13", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "Timing attack against CBC ciphers.", severity: "medium", testFor: (cipher) => cipher.includes("CBC") }, { name: "HEARTBLEED", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "OpenSSL heartbeat information disclosure. Can't be detected from connection alone.", severity: "critical" }, { name: "Sweet32", affects: ["TLSv1", "TLSv1.1", "TLSv1.2"], description: "Birthday attacks on 64-bit block ciphers (3DES/DES)", severity: "medium", testFor: (cipher) => cipher.includes("3DES") || cipher.includes("DES-CBC") } ]; async function isPortOpen(host, port, timeout) { return new Promise((resolve) => { const socket = new net__namespace.Socket(); let isOpen = false; socket.setTimeout(timeout); socket.on("connect", () => { isOpen = true; socket.end(); }); socket.on("timeout", () => { socket.destroy(); resolve(false); }); socket.on("error", () => { resolve(false); }); socket.on("close", () => { resolve(isOpen); }); socket.connect(port, host); }); } function testForVulnerabilities(protocol, cipher) { const vulnerabilities = []; for (const vuln of KNOWN_VULNERABILITIES) { if (vuln.affects.includes(protocol)) { if (!vuln.testFor || vuln.testFor(cipher.name)) { vulnerabilities.push({ name: vuln.name, description: vuln.description, severity: vuln.severity }); } } } return vulnerabilities; } function calculateKeyStrength(cert) { let strength = 0; let algorithm = "unknown"; let rating = "weak"; if (cert.pubkey) { if (cert.pubkey.algo === "rsa") { strength = cert.pubkey.bits || 0; algorithm = "RSA"; if (strength >= 4096) { rating = "strong"; } else if (strength >= 2048) { rating = "adequate"; } else if (strength >= 1024) { rating = "weak"; } else { rating = "insecure"; } } else if (cert.pubkey.algo === "ec") { strength = cert.pubkey.bits || 0; algorithm = "ECDSA"; if (strength >= 384) { rating = "strong"; } else if (strength >= 256) { rating = "adequate"; } else { rating = "weak"; } } } return { strength, algorithm, rating }; } function checkCertificateFeatures(cert) { const features = []; const hasSCT = cert.ext && (cert.ext.includes("CT Precertificate SCTs") || cert.ext.includes("signed certificate timestamp")); features.push({ feature: "Certificate Transparency", supported: !!hasSCT, description: hasSCT ? "Certificate includes embedded SCTs, complying with Certificate Transparency" : "Certificate does not include Certificate Transparency information" }); const hasOCSPMustStaple = cert.ext && cert.ext.includes("OCSP Must-Staple"); features.push({ feature: "OCSP Must-Staple", supported: !!hasOCSPMustStaple, description: hasOCSPMustStaple ? "Certificate requires the server to provide OCSP stapling" : "Certificate does not enforce OCSP stapling" }); const hasKeyUsage = cert.ext && cert.ext.includes("X509v3 Key Usage"); features.push({ feature: "Key Usage Restrictions", supported: !!hasKeyUsage, description: hasKeyUsage ? "Certificate specifies permitted key usages" : "Certificate does not restrict key usages" }); return features; } async function tryTLSConnection(host, port, timeout, options = {}) { return new Promise((resolve) => { try { const socketOptions = { host, port, timeout, rejectUnauthorized: false, secureContext: options.secureContext, secureProtocol: options.secureProtocol, ALPNProtocols: ["h2", "http/1.1"], // Test for HTTP/2 support requestCert: true, ...options }; const socket = tls__namespace.connect(socketOptions); socket.setTimeout(timeout); socket.on("secureConnect", () => { const details = {}; try { details.alpnProtocol = socket.alpnProtocol; } catch (e) { } try { details.hostname = host; } catch (e) { } try { details.negotiatedProtocol = socket.getProtocol(); } catch (e) { } resolve({ success: true, socket, details }); }); socket.on("error", (error) => { socket.destroy(); resolve({ success: false, error }); }); socket.on("timeout", () => { socket.destroy(); resolve({ success: false, error: new Error("Connection timeout") }); }); } catch (error) { resolve({ success: false, error }); } }); } async function testCipherSupport(host, port, timeout, protocol) { const supportedCiphers = []; const allCiphers = crypto__namespace.getCiphers().filter( (c) => ( // Filter to just TLS/SSL ciphers c.includes("-") && !c.startsWith("id-") && !c.includes("NULL") ) ); const cipherGroups = { modern: allCiphers.filter( (c) => c.includes("ECDHE") && (c.includes("GCM") || c.includes("CHACHA20")) ), recommended: allCiphers.filter( (c) => c.includes("DHE") && (c.includes("GCM") || c.includes("CHACHA20")) ), legacy: allCiphers.filter( (c) => c.includes("AES") && !c.includes("GCM") ), weak: allCiphers.filter( (c) => c.includes("RC4") || c.includes("DES") || c.includes("3DES") || c.includes("MD5") ) }; const cipherList = [ ...cipherGroups.modern, ...cipherGroups.recommended, ...cipherGroups.legacy, ...cipherGroups.weak ]; const maxCiphersToTest = 30; const selectedCiphers = cipherList.slice(0, maxCiphersToTest); for (const cipher of selectedCiphers) { try { const options = { minVersion: protocol, maxVersion: protocol, ciphers: cipher }; const result = await tryTLSConnection(host, port, timeout / 2, options); if (result.success && result.socket) { supportedCiphers.push(result.socket.getCipher().name); result.socket.destroy(); } } catch (e) { } } return [...new Set(supportedCiphers)]; } function extractCertificateInfo(socket, host) { const protocol = socket.getProtocol() || ""; const cipher = socket.getCipher(); const cert = socket.getPeerCertificate(true); const chain = []; let currentCert = cert; while (currentCert && !(currentCert.issuerCertificate?.fingerprint === currentCert.fingerprint)) { if (chain.findIndex((c) => c.fingerprint === currentCert.fingerprint) === -1) { chain.push({ subject: currentCert.subject, issuer: currentCert.issuer, validFrom: currentCert.valid_from, validTo: currentCert.valid_to, fingerprint: currentCert.fingerprint }); if (currentCert.issuerCertificate && currentCert.fingerprint !== currentCert.issuerCertificate.fingerprint) { currentCert = currentCert.issuerCertificate; } else { break; } } else { break; } } const now = /* @__PURE__ */ new Date(); const validFrom = new Date(cert.valid_from); const validTo = new Date(cert.valid_to); const expiresIn = Math.round((validTo.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)); const isExpired = now > validTo; const isNotYetValid = now < validFrom; const issues = []; if (isExpired) { issues.push({ severity: "high", description: "SSL certificate has expired." }); } if (isNotYetValid) { issues.push({ severity: "high", description: "SSL certificate is not yet valid." }); } if (expiresIn <= 30 && !isExpired) { issues.push({ severity: "medium", description: `SSL certificate expires soon (${expiresIn} days).` }); } const hostnames = [host]; if (host.startsWith("www.")) { hostnames.push(host.substring(4)); } else { hostnames.push(`www.${host}`); } const altNames = cert.subjectaltname?.split(", ").map((name) => { if (name.startsWith("DNS:")) { return name.substring(4); } return name; }) || []; const validNames = [ cert.subject?.CN, ...altNames ].filter(Boolean); const hostnameMatch = hostnames.some( (hostname) => validNames.some((name) => { if (name.startsWith("*.")) { const domainPart = name.substring(2); return hostname.endsWith(domainPart) && hostname.split(".").length === domainPart.split(".").length + 1; } return name === hostname; }) ); if (!hostnameMatch) { issues.push({ severity: "high", description: `Certificate hostname mismatch. Certificate is not valid for: ${host}` }); } const protocolRating = PROTOCOL_RATINGS[protocol] || { rating: "unknown", description: "Unknown protocol version" }; if (protocolRating.rating === "insecure" || protocolRating.rating === "weak") { issues.push({ severity: "high", description: `Server uses ${protocolRating.rating} protocol: ${protocol}. ${protocolRating.description}` }); } else if (protocolRating.rating === "adequate") { issues.push({ severity: "medium", description: `Server uses ${protocolRating.rating} protocol: ${protocol}. ${protocolRating.description}` }); } const cipherRating = CIPHER_RATINGS[cipher.name] || "weak"; if (cipherRating === "insecure") { issues.push({ severity: "high", description: `Server uses insecure cipher: ${cipher.name}.` }); } else if (cipherRating === "weak") { issues.push({ severity: "high", description: `Server uses weak cipher: ${cipher.name}.` }); } else if (cipherRating === "adequate") { issues.push({ severity: "medium", description: `Server uses adequate but not ideal cipher: ${cipher.name}.` }); } const keyInfo = calculateKeyStrength(cert); if (keyInfo.rating === "insecure") { issues.push({ severity: "high", description: `Certificate uses insecure ${keyInfo.algorithm} key (${keyInfo.strength} bits).` }); } else if (keyInfo.rating === "weak") { issues.push({ severity: "medium", description: `Certificate uses weak ${keyInfo.algorithm} key (${keyInfo.strength} bits).` }); } const isSelfSigned = chain.length === 1 || cert.issuer.CN === cert.subject.CN && cert.issuer.O === cert.subject.O; if (isSelfSigned) { issues.push({ severity: "high", description: "Certificate is self-signed and not from a trusted authority." }); } const sigAlgo = cert.sigalg?.toLowerCase() || ""; if (sigAlgo.includes("sha1") || sigAlgo.includes("md5")) { issues.push({ severity: "high", description: `Certificate uses weak signature algorithm: ${cert.sigalg}` }); } const vulnerabilities = testForVulnerabilities(protocol, cipher); for (const vuln of vulnerabilities) { issues.push({ severity: vuln.severity, description: `${vuln.name}: ${vuln.description}` }); } const features = checkCertificateFeatures(cert); let securityScore = 100; for (const issue of issues) { if (issue.severity === "high") { securityScore -= 25; } else if (issue.severity === "medium") { securityScore -= 10; } else if (issue.severity === "low") { securityScore -= 5; } } for (const feature of features) { if (feature.supported) { securityScore += 5; } } securityScore = Math.max(0, Math.min(100, securityScore)); let securityRating; if (securityScore >= 95) { securityRating = "A+"; } else if (securityScore >= 85) { securityRating = "A"; } else if (securityScore >= 70) { securityRating = "B"; } else if (securityScore >= 60) { securityRating = "C"; } else if (securityScore >= 50) { securityRating = "D"; } else { securityRating = "F"; } return { protocol, cipher, certInfo: { issuer: cert.issuer.CN || cert.issuer.O || "Unknown", subject: cert.subject.CN || cert.subject.O || "Unknown", validFrom: validFrom.toISOString(), validTo: validTo.toISOString(), expiresIn, subjectAltNames: altNames, serialNumber: cert.serialNumber, signatureAlgorithm: cert.sigalg, keyStrength: keyInfo.strength, keyAlgorithm: keyInfo.algorithm }, certificateChain: chain, issues, securityRating, features, vulnerabilities }; } var scanTLS = async (input) => { const startTime = Date.now(); const normalizedInput = createScannerInput(input); const domain = extractDomain(normalizedInput.target); const timeout = normalizedInput.timeout || 1e4; const diagnosticInfo = []; try { try { const dnsResult = await dnsLookup(domain); diagnosticInfo.push(`Domain ${domain} resolves to IP: ${dnsResult.address}`); } catch (error) { diagnosticInfo.push(`DNS lookup error: ${error.message}`); return { status: "failure", scanner: "tlsConfig", error: `Failed to resolve domain: ${error.message}`, data: { version: "", ciphers: [], certificate: { issuer: "", subject: "", validFrom: "", validTo: "", expiresIn: 0 }, isValid: false, issues: [{ severity: "high", description: `Failed to resolve domain: ${error.message}` }], diagnosticInfo }, timeTaken: Date.now() - startTime }; } const portsToTry = [443, 8443]; let portOpen = false; let openPort = 0; for (const port of portsToTry) { diagnosticInfo.push(`Checking if port ${port} is open...`); const isOpen = await isPortOpen(domain, port, timeout); if (isOpen) { diagnosticInfo.push(`Port ${port} is open.`); portOpen = true; openPort = port; break; } else { diagnosticInfo.push(`Port ${port} is closed or filtered.`); } } if (!portOpen) { return { status: "failure", scanner: "tlsConfig", error: "No open TLS ports found", data: { version: "", ciphers: [], certificate: { issuer: "", subject: "", validFrom: "", validTo: "", expiresIn: 0 }, isValid: false, issues: [{ severity: "high", description: "No open TLS ports found (tried: 443, 8443)" }], diagnosticInfo }, timeTaken: Date.now() - startTime }; } const tlsVersions = [ { version: "TLSv1.3", options: { minVersion: "TLSv1.3", maxVersion: "TLSv1.3" } }, { version: "TLSv1.2", options: { minVersion: "TLSv1.2", maxVersion: "TLSv1.2" } }, { version: "Default", options: {} } // Try with defaults as fallback ]; for (const { version, options } of tlsVersions) { diagnosticInfo.push(`Attempting TLS connection on port ${openPort} with ${version} protocol...`); const connectionResult = await tryTLSConnection(domain, openPort, timeout, options); if (connectionResult.success && connectionResult.socket) { diagnosticInfo.push(`Successfully established TLS connection using ${version}.`); const { protocol, cipher, certInfo, issues, securityRating, features, vulnerabilities, certificateChain } = extractCertificateInfo(connectionResult.socket, domain); const supportedCiphers = await testCipherSupport(domain, openPort, timeout / 2, protocol); connectionResult.socket.end(); const cipherStrengths = supportedCiphers.map((cipherName) => { const rating = CIPHER_RATINGS[cipherName] || "unknown"; return { name: cipherName, strength: rating }; }); return { status: "success", scanner: "tlsConfig", data: { version: protocol, ciphers: supportedCiphers, cipherDetails: cipherStrengths, certificate: certInfo, certificateChain, isValid: !issues.some((issue) => issue.severity === "high"), issues, securityRating, supportedFeatures: features?.filter((f) => f.supported).map((f) => f.feature) || [], missingFeatures: features?.filter((f) => !f.supported).map((f) => f.feature) || [], vulnerabilities: vulnerabilities || [], diagnosticInfo }, timeTaken: Date.now() - startTime }; } else { const errorMsg = connectionResult.error?.message || "Unknown error"; diagnosticInfo.push(`Failed with ${version}: ${errorMsg}`); } } return { status: "failure", scanner: "tlsConfig", error: "All TLS connection attempts failed", data: { version: "", ciphers: [], certificate: { issuer: "", subject: "", validFrom: "", validTo: "", expiresIn: 0 }, isValid: false, issues: [{ severity: "high", description: "Failed to establish TLS connection after multiple attempts" }], diagnosticInfo }, timeTaken: Date.now() - startTime }; } catch (error) { return { status: "failure", scanner: "tlsConfig", error: error.message || "Unknown error", data: { version: "", ciphers: [], certificate: { issuer: "", subject: "", validFrom: "", validTo: "", expiresIn: 0 }, isValid: false, issues: [{ severity: "high", description: `Error scanning TLS configuration: ${error.message}` }], diagnosticInfo }, timeTaken: Date.now() - startTime }; } }; var resolveTxt2 = util.promisify(dns2__namespace.resolveTxt); util.promisify(dns2__namespace.resolveMx); var resolveNs2 = util.promisify(dns2__namespace.resolveNs); var scanDNSRecords = async (input) => { const startTime = Date.now(); const normalizedInput = createScannerInput(input); const domain = extractDomain(normalizedInput.target); const result = { spf: { exists: false, valid: false }, dmarc: { exists: false, valid: false }, dkim: { exists: false, valid: false }, dnssec: { enabled: false, valid: false } }; try { try { const txtRecords = await resolveTxt2(domain); const spfRecord = txtRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=spf1"); }); if (spfRecord) { const recordStr = spfRecord.join(""); result.spf = { exists: true, valid: true, record: recordStr, issues: [] }; result.spf.issues = []; if (!recordStr.includes("~all") && !recordStr.includes("-all") && !recordStr.includes("?all")) { result.spf.issues.push("SPF record does not end with ~all, -all, or ?all"); result.spf.valid = false; } if (recordStr.includes("+all")) { result.spf.issues.push("SPF record uses +all which allows anyone to send mail claiming to be from your domain"); result.spf.valid = false; } const parts = recordStr.split(" "); const includeCount = parts.filter((p) => p.startsWith("include:")).length; if (includeCount > 10) { result.spf.issues.push(`SPF record has ${includeCount} include mechanisms which may exceed the 10 DNS lookup limit`); } if (recordStr.length > 450) { result.spf.issues.push(`SPF record is ${recordStr.length} bytes long which exceeds the recommended 450 bytes and may be truncated`); } } } catch (error) { result.spf.exists = false; result.spf.issues = ["Failed to retrieve SPF record"]; } try { const dmarcRecords = await resolveTxt2("_dmarc." + domain); const dmarcRecord = dmarcRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=DMARC1"); }); if (dmarcRecord) { const recordStr = dmarcRecord.join(""); result.dmarc = { exists: true, valid: true, record: recordStr, issues: [] }; const policyMatch = recordStr.match(/p=([^;]+)/); if (policyMatch) { result.dmarc.policy = policyMatch[1]; } if (!recordStr.includes("p=")) { result.dmarc.issues = ["DMARC record does not include a policy (p=)"]; result.dmarc.valid = false; } else if (result.dmarc.policy === "none") { result.dmarc.issues = ['DMARC policy is set to "none" which only monitors but takes no action']; } } } catch (error) { result.dmarc.exists = false; result.dmarc.issues = ["Failed to retrieve DMARC record"]; } const commonSelectors = [ "default", "google", "selector1", "selector2", "k1", "dkim", "mail", "email", "outlook", "amazonses", "mandrill", "sendgrid", "20161025", "mailjet", "zoho" ]; const dkimResults = []; const customSelectors = normalizedInput.options?.dkimSelectors; const selectorsToCheck = customSelectors ? [...commonSelectors, ...customSelectors] : commonSelectors; for (const selector of selectorsToCheck) { try { const dkimRecords = await resolveTxt2(`${selector}._domainkey.${domain}`); if (dkimRecords && dkimRecords.length > 0) { dkimResults.push(selector); } } catch (error) { } } if (dkimResults.length > 0) { result.dkim = { exists: true, valid: true, selectors: dkimResults }; } else { result.dkim.issues = ["No DKIM records found for common selectors"]; } try { const nsRecords = await resolveNs2(domain); if (nsRecords && nsRecords.length > 0) { result.dnssec = { enabled: true, valid: true, issues: ["Basic DNSSEC detection only. Full validation requires specialized tools."] }; } } catch (error) { result.dnssec.issues = ["Failed to check nameservers for DNSSEC"]; } return { status: "success", scanner: "dnsRecords", data: result, timeTaken: Date.now() - startTime }; } catch (error) { return { status: "failure", scanner: "dnsRecords", error: error.message || "Unknown error", data: result, timeTaken: Date.now() - startTime }; } }; var DEFAULT_PORTS = [ 21, // FTP 22, // SSH 23, // Telnet 25, // SMTP 53, // DNS 80, // HTTP 110, // POP3 143, // IMAP 443, // HTTPS 465, // SMTPS 587, // SMTP Submission 993, // IMAPS 995, // POP3S 3306, // MySQL 5432, // PostgreSQL 8080, // HTTP Alternate 8443 // HTTPS Alternate ]; var SERVICE_NAMES = { 21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS", 465: "SMTPS", 587: "SMTP Submission", 993: "IMAPS", 995: "POP3S", 3306: "MySQL", 5432: "PostgreSQL", 8080: "HTTP Alternate", 8443: "HTTPS Alternate" }; var scanPorts = async (input) => { const startTime = Date.now(); const normalizedInput = createScannerInput(input); const domain = extractDomain(normalizedInput.target); const timeout = normalizedInput.timeout || 3e3; const portsToScan = normalizedInput.options?.ports || DEFAULT_PORTS; const result = { openPorts: [], total: 0 }; try { const portPromises = portsToScan.map((port) => { return new Promise((resolve) => { const socket = new net__namespace.Socket(); let resolved = false; socket.setTimeout(timeout); socket.on("connect", () => { if (resolved) return; resolved = true; let banner = ""; const bannerTimeout = setTimeout(() => { socket.destroy(); result.openPorts.push({ port, service: SERVICE_NAMES[port] || void 0, banner: banner || void 0 }); resolve(); }, 1e3); socket.once("data", (data) => { banner = data.toString().trim(); clearTimeout(bannerTimeout); socket.destroy(); result.openPorts.push({ port, service: SERVICE_NAMES[port] || void 0, banner: banner || void 0 }); resolve(); }); if (port === 80) { socket.write("HEAD / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n"); } else if (port === 443) { socket.destroy(); result.openPorts.push({ port, service: "HTTPS" }); resolve(); } else if (port === 25 || port === 587) { } else if (port === 22) { } else { socket.destroy(); result.openPorts.push({ port, service: SERVICE_NAMES[port] || void 0 }); resolve(); } }); socket.on("error", () => { if (resolved) return; resolved = true; socket.destroy(); resolve(); }); socket.on("timeout", () => { if (resolved) return; resolved = true; socket.destroy(); resolve(); }); socket.connect(port, domain); }); }); await Promise.all(portPromises); result.total = result.openPorts.length; return { status: "success", scanner: "portScan", data: result, timeTaken: Date.now() - startTime }; } catch (error) { return { status: "failure", scanner: "portScan", error: error.message || "Unknown error", data: result, timeTaken: Date.now() - startTime }; } }; var resolveTxt4 = util.promisify(dns2__namespace.resolveTxt); var checkEmailSecurity = async (input) => { const startTime = Date.now(); const normalizedInput = createScannerInput(input); const domain = extractDomain(normalizedInput.target); const result = { domain, spf: { exists: false, valid: false }, dmarc: { exists: false, valid: false }, mta_sts: { exists: false, valid: false }, bimi: { exists: false, valid: false }, overall: { securityScore: 0, recommendations: [] } }; try { try { const txtRecords = await resolveTxt4(domain); const spfRecord = txtRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=spf1"); }); if (spfRecord) { const recordStr = spfRecord.join(""); result.spf = { exists: true, valid: true, record: recordStr, issues: [] }; if (!recordStr.includes("~all") && !recordStr.includes("-all")) { result.spf.issues?.push("SPF record does not end with ~all or -all"); result.spf.valid = false; } } else { result.overall.recommendations.push("Implement an SPF record to prevent email spoofing"); } } catch (error) { result.spf.exists = false; result.spf.issues = ["Failed to retrieve SPF record"]; result.overall.recommendations.push("Implement an SPF record to prevent email spoofing"); } try { const dmarcRecords = await resolveTxt4("_dmarc." + domain); const dmarcRecord = dmarcRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=DMARC1"); }); if (dmarcRecord) { const recordStr = dmarcRecord.join(""); result.dmarc = { exists: true, valid: true, record: recordStr, issues: [] }; const policyMatch = recordStr.match(/p=([^;]+)/); if (policyMatch) { result.dmarc.policy = policyMatch[1]; if (result.dmarc.policy === "none") { result.dmarc.issues?.push('DMARC policy is set to "none" which only monitors but takes no action'); result.overall.recommendations.push('Consider strengthening DMARC policy from "none" to "quarantine" or "reject"'); } else if (result.dmarc.policy === "quarantine") { result.overall.recommendations.push('Consider strengthening DMARC policy from "quarantine" to "reject" for maximum protection'); } } else { result.dmarc.valid = false; result.dmarc.issues?.push("DMARC record does not include a policy (p=)"); } } else { result.overall.recommendations.push("Implement a DMARC record to enhance email authentication"); } } catch (error) { result.dmarc.exists = false; result.dmarc.issues = ["Failed to retrieve DMARC record"]; result.overall.recommendations.push("Implement a DMARC record to enhance email authentication"); } try { const mtaStsRecords = await resolveTxt4("_mta-sts." + domain); const mtaStsRecord = mtaStsRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=STSv1"); }); if (mtaStsRecord) { const recordStr = mtaStsRecord.join(""); result.mta_sts = { exists: true, valid: true, record: recordStr, issues: [] }; } else { result.overall.recommendations.push("Implement MTA-STS for secure email transit and protection against downgrade attacks"); } } catch (error) { result.mta_sts.exists = false; result.mta_sts.issues = ["Failed to retrieve MTA-STS record"]; } try { const bimiRecords = await resolveTxt4("default._bimi." + domain); const bimiRecord = bimiRecords.find((record) => { const recordStr = record.join(""); return recordStr.startsWith("v=BIMI1"); }); if (bimiRecord) { const recordStr = bimiRecord.join(""); result.bimi = { exists: true, valid: true, record: recordStr, issues: [] }; } } catch (error) { result.bimi.exists = false; result.bimi.issues = ["Failed to retrieve BIMI record"]; } let score = 0; if (result.spf.exists) score += 15; if (result.spf.valid) score += 10; if (result.dmarc.exists) score += 20; if (result.dmarc.valid) score += 10; if (result.dmarc.policy === "reject") score += 10; else if (result.dmarc.policy === "quarantine") score += 5; if (result.mta_sts.exists) score += 15; if (result.mta_sts.valid) score += 10; if (result.bimi.exists) score += 10; result.overall.securityScore = score; return { status: "success", scanner: "emailSecurity", data: result, timeTaken: Date.now() - startTime }; } catch (error) { return { status: "failure", scanner: "emailSecurity", error: error.message || "Unknown error", data: result, timeTaken: Date.now() - startTime }; } }; exports.checkEmailSecurity = checkEmailSecurity; exports.makeRequest = makeRequest; exports.scanDNSRecords = scanDNSRecords; exports.scanPorts = scanPorts; exports.scanTLS = scanTLS; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.js.map