Files
smartmta/dist_ts/security/classes.contentscanner.js
2026-02-10 15:54:09 +00:00

637 lines
49 KiB
JavaScript

import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { logger } from '../logger.js';
import { Email } from '../mail/core/classes.email.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
import { LRUCache } from 'lru-cache';
/**
* Threat categories
*/
export var ThreatCategory;
(function (ThreatCategory) {
ThreatCategory["SPAM"] = "spam";
ThreatCategory["PHISHING"] = "phishing";
ThreatCategory["MALWARE"] = "malware";
ThreatCategory["EXECUTABLE"] = "executable";
ThreatCategory["SUSPICIOUS_LINK"] = "suspicious_link";
ThreatCategory["MALICIOUS_MACRO"] = "malicious_macro";
ThreatCategory["XSS"] = "xss";
ThreatCategory["SENSITIVE_DATA"] = "sensitive_data";
ThreatCategory["BLACKLISTED_CONTENT"] = "blacklisted_content";
ThreatCategory["CUSTOM_RULE"] = "custom_rule";
})(ThreatCategory || (ThreatCategory = {}));
/**
* Content Scanner for detecting malicious email content
*/
export class ContentScanner {
static instance;
scanCache;
options;
// Predefined patterns for common threats
static MALICIOUS_PATTERNS = {
// Phishing patterns
phishing: [
/(?:verify|confirm|update|login).*(?:account|password|details)/i,
/urgent.*(?:action|attention|required)/i,
/(?:paypal|apple|microsoft|amazon|google|bank).*(?:verify|confirm|suspend)/i,
/your.*(?:account).*(?:suspended|compromised|locked)/i,
/\b(?:password reset|security alert|security notice)\b/i
],
// Spam indicators
spam: [
/\b(?:viagra|cialis|enlargement|diet pill|lose weight fast|cheap meds)\b/i,
/\b(?:million dollars|lottery winner|prize claim|inheritance|rich widow)\b/i,
/\b(?:earn from home|make money fast|earn \$\d{3,}\/day)\b/i,
/\b(?:limited time offer|act now|exclusive deal|only \d+ left)\b/i,
/\b(?:forex|stock tip|investment opportunity|cryptocurrency|bitcoin)\b/i
],
// Malware indicators in text
malware: [
/(?:attached file|see attachment).*(?:invoice|receipt|statement|document)/i,
/open.*(?:the attached|this attachment)/i,
/(?:enable|allow).*(?:macros|content|editing)/i,
/download.*(?:attachment|file|document)/i,
/\b(?:ransomware protection|virus alert|malware detected)\b/i
],
// Suspicious links
suspiciousLinks: [
/https?:\/\/bit\.ly\//i,
/https?:\/\/goo\.gl\//i,
/https?:\/\/t\.co\//i,
/https?:\/\/tinyurl\.com\//i,
/https?:\/\/(?:\d{1,3}\.){3}\d{1,3}/i, // IP address URLs
/https?:\/\/.*\.(?:xyz|top|club|gq|cf)\//i, // Suspicious TLDs
/(?:login|account|signin|auth).*\.(?!gov|edu|com|org|net)\w+\.\w+/i, // Login pages on unusual domains
],
// XSS and script injection
scriptInjection: [
/<script.*>.*<\/script>/is,
/javascript:/i,
/on(?:click|load|mouse|error|focus|blur)=".*"/i,
/document\.(?:cookie|write|location)/i,
/eval\s*\(/i
],
// Sensitive data patterns
sensitiveData: [
/\b(?:\d{3}-\d{2}-\d{4}|\d{9})\b/, // SSN
/\b\d{13,16}\b/, // Credit card numbers
/\b(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})\b/ // Possible Base64
]
};
// Common executable extensions
static EXECUTABLE_EXTENSIONS = [
'.exe', '.dll', '.bat', '.cmd', '.msi', '.ts', '.vbs', '.ps1',
'.sh', '.jar', '.py', '.com', '.scr', '.pif', '.hta', '.cpl',
'.reg', '.vba', '.lnk', '.wsf', '.msi', '.msp', '.mst'
];
// Document formats that may contain macros
static MACRO_DOCUMENT_EXTENSIONS = [
'.doc', '.docm', '.xls', '.xlsm', '.ppt', '.pptm', '.dotm', '.xlsb', '.ppam', '.potm'
];
/**
* Default options for the content scanner
*/
static DEFAULT_OPTIONS = {
maxCacheSize: 10000,
cacheTTL: 24 * 60 * 60 * 1000, // 24 hours
scanSubject: true,
scanBody: true,
scanAttachments: true,
maxAttachmentSizeToScan: 10 * 1024 * 1024, // 10MB
scanAttachmentNames: true,
blockExecutables: true,
blockMacros: true,
customRules: [],
minThreatScore: 30, // Minimum score to consider content as a threat
highThreatScore: 70 // Score above which content is considered high threat
};
/**
* Constructor for the ContentScanner
* @param options Configuration options
*/
constructor(options = {}) {
// Merge with default options
this.options = {
...ContentScanner.DEFAULT_OPTIONS,
...options
};
// Initialize cache
this.scanCache = new LRUCache({
max: this.options.maxCacheSize,
ttl: this.options.cacheTTL,
});
logger.log('info', 'ContentScanner initialized');
}
/**
* Get the singleton instance of the scanner
* @param options Configuration options
* @returns Singleton scanner instance
*/
static getInstance(options = {}) {
if (!ContentScanner.instance) {
ContentScanner.instance = new ContentScanner(options);
}
return ContentScanner.instance;
}
/**
* Scan an email for malicious content
* @param email The email to scan
* @returns Scan result
*/
async scanEmail(email) {
try {
// Generate a cache key from the email
const cacheKey = this.generateCacheKey(email);
// Check cache first
const cachedResult = this.scanCache.get(cacheKey);
if (cachedResult) {
logger.log('info', `Using cached scan result for email ${email.getMessageId()}`);
return cachedResult;
}
// Initialize scan result
const result = {
isClean: true,
threatScore: 0,
scannedElements: [],
timestamp: Date.now()
};
// List of scan promises
const scanPromises = [];
// Scan subject
if (this.options.scanSubject && email.subject) {
scanPromises.push(this.scanSubject(email.subject, result));
}
// Scan body content
if (this.options.scanBody) {
if (email.text) {
scanPromises.push(this.scanTextContent(email.text, result));
}
if (email.html) {
scanPromises.push(this.scanHtmlContent(email.html, result));
}
}
// Scan attachments
if (this.options.scanAttachments && email.attachments && email.attachments.length > 0) {
for (const attachment of email.attachments) {
scanPromises.push(this.scanAttachment(attachment, result));
}
}
// Run all scans in parallel
await Promise.all(scanPromises);
// Determine if the email is clean based on threat score
result.isClean = result.threatScore < this.options.minThreatScore;
// Save to cache
this.scanCache.set(cacheKey, result);
// Log high threat findings
if (result.threatScore >= this.options.highThreatScore) {
this.logHighThreatFound(email, result);
}
else if (!result.isClean) {
this.logThreatFound(email, result);
}
return result;
}
catch (error) {
logger.log('error', `Error scanning email: ${error.message}`, {
messageId: email.getMessageId(),
error: error.stack
});
// Return a safe default with error indication
return {
isClean: true, // Let it pass if scanner fails (configure as desired)
threatScore: 0,
scannedElements: ['error'],
timestamp: Date.now(),
threatType: 'scan_error',
threatDetails: `Scan error: ${error.message}`
};
}
}
/**
* Generate a cache key from an email
* @param email The email to generate a key for
* @returns Cache key
*/
generateCacheKey(email) {
// Use message ID if available
if (email.getMessageId()) {
return `email:${email.getMessageId()}`;
}
// Fallback to a hash of key content
const contentToHash = [
email.from,
email.subject || '',
email.text?.substring(0, 1000) || '',
email.html?.substring(0, 1000) || '',
email.attachments?.length || 0
].join(':');
return `email:${plugins.crypto.createHash('sha256').update(contentToHash).digest('hex')}`;
}
/**
* Scan email subject for threats
* @param subject The subject to scan
* @param result The scan result to update
*/
async scanSubject(subject, result) {
result.scannedElements.push('subject');
// Check against phishing patterns
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.phishing) {
if (pattern.test(subject)) {
result.threatScore += 25;
result.threatType = ThreatCategory.PHISHING;
result.threatDetails = `Subject contains potential phishing indicators: ${subject}`;
return;
}
}
// Check against spam patterns
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.spam) {
if (pattern.test(subject)) {
result.threatScore += 15;
result.threatType = ThreatCategory.SPAM;
result.threatDetails = `Subject contains potential spam indicators: ${subject}`;
return;
}
}
// Check custom rules
for (const rule of this.options.customRules) {
const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, 'i');
if (pattern.test(subject)) {
result.threatScore += rule.score;
result.threatType = rule.type;
result.threatDetails = rule.description;
return;
}
}
}
/**
* Scan plain text content for threats
* @param text The text content to scan
* @param result The scan result to update
*/
async scanTextContent(text, result) {
result.scannedElements.push('text');
// Check suspicious links
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.suspiciousLinks) {
if (pattern.test(text)) {
result.threatScore += 20;
if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SUSPICIOUS_LINK ? 0 : 20)) {
result.threatType = ThreatCategory.SUSPICIOUS_LINK;
result.threatDetails = `Text contains suspicious links`;
}
}
}
// Check phishing
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.phishing) {
if (pattern.test(text)) {
result.threatScore += 25;
if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.PHISHING ? 0 : 25)) {
result.threatType = ThreatCategory.PHISHING;
result.threatDetails = `Text contains potential phishing indicators`;
}
}
}
// Check spam
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.spam) {
if (pattern.test(text)) {
result.threatScore += 15;
if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SPAM ? 0 : 15)) {
result.threatType = ThreatCategory.SPAM;
result.threatDetails = `Text contains potential spam indicators`;
}
}
}
// Check malware indicators
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.malware) {
if (pattern.test(text)) {
result.threatScore += 30;
if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.MALWARE ? 0 : 30)) {
result.threatType = ThreatCategory.MALWARE;
result.threatDetails = `Text contains potential malware indicators`;
}
}
}
// Check sensitive data
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.sensitiveData) {
if (pattern.test(text)) {
result.threatScore += 25;
if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SENSITIVE_DATA ? 0 : 25)) {
result.threatType = ThreatCategory.SENSITIVE_DATA;
result.threatDetails = `Text contains potentially sensitive data patterns`;
}
}
}
// Check custom rules
for (const rule of this.options.customRules) {
const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, 'i');
if (pattern.test(text)) {
result.threatScore += rule.score;
if (!result.threatType || result.threatScore > 20) {
result.threatType = rule.type;
result.threatDetails = rule.description;
}
}
}
}
/**
* Scan HTML content for threats
* @param html The HTML content to scan
* @param result The scan result to update
*/
async scanHtmlContent(html, result) {
result.scannedElements.push('html');
// Check for script injection
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.scriptInjection) {
if (pattern.test(html)) {
result.threatScore += 40;
if (!result.threatType || result.threatType !== ThreatCategory.XSS) {
result.threatType = ThreatCategory.XSS;
result.threatDetails = `HTML contains potentially malicious script content`;
}
}
}
// Extract text content from HTML for further scanning
const textContent = this.extractTextFromHtml(html);
if (textContent) {
// We'll leverage the text scanning but not double-count threat score
const tempResult = {
isClean: true,
threatScore: 0,
scannedElements: [],
timestamp: Date.now()
};
await this.scanTextContent(textContent, tempResult);
// Only add additional threat types if they're more severe
if (tempResult.threatType && tempResult.threatScore > 0) {
// Add half of the text content score to avoid double counting
result.threatScore += Math.floor(tempResult.threatScore / 2);
// Adopt the threat type if more severe or no existing type
if (!result.threatType || tempResult.threatScore > result.threatScore) {
result.threatType = tempResult.threatType;
result.threatDetails = tempResult.threatDetails;
}
}
}
// Extract and check links from HTML
const links = this.extractLinksFromHtml(html);
if (links.length > 0) {
// Check for suspicious links
let suspiciousLinks = 0;
for (const link of links) {
for (const pattern of ContentScanner.MALICIOUS_PATTERNS.suspiciousLinks) {
if (pattern.test(link)) {
suspiciousLinks++;
break;
}
}
}
if (suspiciousLinks > 0) {
// Add score based on percentage of suspicious links
const suspiciousPercentage = (suspiciousLinks / links.length) * 100;
const additionalScore = Math.min(40, Math.floor(suspiciousPercentage / 2.5));
result.threatScore += additionalScore;
if (!result.threatType || additionalScore > 20) {
result.threatType = ThreatCategory.SUSPICIOUS_LINK;
result.threatDetails = `HTML contains ${suspiciousLinks} suspicious links out of ${links.length} total links`;
}
}
}
}
/**
* Scan an attachment for threats
* @param attachment The attachment to scan
* @param result The scan result to update
*/
async scanAttachment(attachment, result) {
const filename = attachment.filename.toLowerCase();
result.scannedElements.push(`attachment:${filename}`);
// Skip large attachments if configured
if (attachment.content && attachment.content.length > this.options.maxAttachmentSizeToScan) {
logger.log('info', `Skipping scan of large attachment: ${filename} (${attachment.content.length} bytes)`);
return;
}
// Check filename for executable extensions
if (this.options.blockExecutables) {
for (const ext of ContentScanner.EXECUTABLE_EXTENSIONS) {
if (filename.endsWith(ext)) {
result.threatScore += 70; // High score for executable attachments
result.threatType = ThreatCategory.EXECUTABLE;
result.threatDetails = `Attachment has a potentially dangerous extension: ${filename}`;
return; // No need to scan contents if filename already flagged
}
}
}
// Check for Office documents with macros
if (this.options.blockMacros) {
for (const ext of ContentScanner.MACRO_DOCUMENT_EXTENSIONS) {
if (filename.endsWith(ext)) {
// For Office documents, check if they contain macros
// This is a simplified check - a real implementation would use specialized libraries
// to detect macros in Office documents
if (attachment.content && this.likelyContainsMacros(attachment)) {
result.threatScore += 60;
result.threatType = ThreatCategory.MALICIOUS_MACRO;
result.threatDetails = `Attachment appears to contain macros: ${filename}`;
return;
}
}
}
}
// Perform basic content analysis if we have content buffer
if (attachment.content) {
// Convert to string for scanning, with a limit to prevent memory issues
const textContent = this.extractTextFromBuffer(attachment.content);
if (textContent) {
// Scan for malicious patterns in attachment content
for (const category in ContentScanner.MALICIOUS_PATTERNS) {
const patterns = ContentScanner.MALICIOUS_PATTERNS[category];
for (const pattern of patterns) {
if (pattern.test(textContent)) {
result.threatScore += 30;
if (!result.threatType) {
result.threatType = this.mapCategoryToThreatType(category);
result.threatDetails = `Attachment content contains suspicious patterns: ${filename}`;
}
break;
}
}
}
}
// Check for PE headers (Windows executables)
if (attachment.content.length > 64 &&
attachment.content[0] === 0x4D &&
attachment.content[1] === 0x5A) { // 'MZ' header
result.threatScore += 80;
result.threatType = ThreatCategory.EXECUTABLE;
result.threatDetails = `Attachment contains executable code: ${filename}`;
}
}
}
/**
* Extract links from HTML content
* @param html HTML content
* @returns Array of extracted links
*/
extractLinksFromHtml(html) {
const links = [];
// Simple regex-based extraction - a real implementation might use a proper HTML parser
const matches = html.match(/href=["'](https?:\/\/[^"']+)["']/gi);
if (matches) {
for (const match of matches) {
const linkMatch = match.match(/href=["'](https?:\/\/[^"']+)["']/i);
if (linkMatch && linkMatch[1]) {
links.push(linkMatch[1]);
}
}
}
return links;
}
/**
* Extract plain text from HTML
* @param html HTML content
* @returns Extracted text
*/
extractTextFromHtml(html) {
// Remove HTML tags and decode entities - simplified version
return html
.replace(/<style[^>]*>.*?<\/style>/gs, '')
.replace(/<script[^>]*>.*?<\/script>/gs, '')
.replace(/<[^>]+>/g, ' ')
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/\s+/g, ' ')
.trim();
}
/**
* Extract text from a binary buffer for scanning
* @param buffer Binary content
* @returns Extracted text (may be partial)
*/
extractTextFromBuffer(buffer) {
try {
// Limit the amount we convert to avoid memory issues
const sampleSize = Math.min(buffer.length, 100 * 1024); // 100KB max sample
const sample = buffer.slice(0, sampleSize);
// Try to convert to string, filtering out non-printable chars
return sample.toString('utf8')
.replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '') // Remove control chars
.replace(/\uFFFD/g, ''); // Remove replacement char
}
catch (error) {
logger.log('warn', `Error extracting text from buffer: ${error.message}`);
return '';
}
}
/**
* Check if an Office document likely contains macros
* This is a simplified check - real implementation would use specialized libraries
* @param attachment The attachment to check
* @returns Whether the file likely contains macros
*/
likelyContainsMacros(attachment) {
// Simple heuristic: look for VBA/macro related strings
// This is a simplified approach and not comprehensive
const content = this.extractTextFromBuffer(attachment.content);
const macroIndicators = [
/vbaProject\.bin/i,
/Microsoft VBA/i,
/\bVBA\b/,
/Auto_Open/i,
/AutoExec/i,
/DocumentOpen/i,
/AutoOpen/i,
/\bExecute\(/i,
/\bShell\(/i,
/\bCreateObject\(/i
];
for (const indicator of macroIndicators) {
if (indicator.test(content)) {
return true;
}
}
return false;
}
/**
* Map a pattern category to a threat type
* @param category The pattern category
* @returns The corresponding threat type
*/
mapCategoryToThreatType(category) {
switch (category) {
case 'phishing': return ThreatCategory.PHISHING;
case 'spam': return ThreatCategory.SPAM;
case 'malware': return ThreatCategory.MALWARE;
case 'suspiciousLinks': return ThreatCategory.SUSPICIOUS_LINK;
case 'scriptInjection': return ThreatCategory.XSS;
case 'sensitiveData': return ThreatCategory.SENSITIVE_DATA;
default: return ThreatCategory.BLACKLISTED_CONTENT;
}
}
/**
* Log a high threat finding to the security logger
* @param email The email containing the threat
* @param result The scan result
*/
logHighThreatFound(email, result) {
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.ERROR,
type: SecurityEventType.MALWARE,
message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
details: {
messageId: email.getMessageId(),
threatType: result.threatType,
threatDetails: result.threatDetails,
threatScore: result.threatScore,
scannedElements: result.scannedElements,
subject: email.subject
},
success: false,
domain: email.getFromDomain()
});
}
/**
* Log a threat finding to the security logger
* @param email The email containing the threat
* @param result The scan result
*/
logThreatFound(email, result) {
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.WARN,
type: SecurityEventType.SPAM,
message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
details: {
messageId: email.getMessageId(),
threatType: result.threatType,
threatDetails: result.threatDetails,
threatScore: result.threatScore,
scannedElements: result.scannedElements,
subject: email.subject
},
success: false,
domain: email.getFromDomain()
});
}
/**
* Get threat level description based on score
* @param score Threat score
* @returns Threat level description
*/
static getThreatLevel(score) {
if (score < 20) {
return 'none';
}
else if (score < 40) {
return 'low';
}
else if (score < 70) {
return 'medium';
}
else {
return 'high';
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jb250ZW50c2Nhbm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3NlY3VyaXR5L2NsYXNzZXMuY29udGVudHNjYW5uZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxLQUFLLEtBQUssTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFdEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ2xHLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFxQ3JDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksY0FXWDtBQVhELFdBQVksY0FBYztJQUN4QiwrQkFBYSxDQUFBO0lBQ2IsdUNBQXFCLENBQUE7SUFDckIscUNBQW1CLENBQUE7SUFDbkIsMkNBQXlCLENBQUE7SUFDekIscURBQW1DLENBQUE7SUFDbkMscURBQW1DLENBQUE7SUFDbkMsNkJBQVcsQ0FBQTtJQUNYLG1EQUFpQyxDQUFBO0lBQ2pDLDZEQUEyQyxDQUFBO0lBQzNDLDZDQUEyQixDQUFBO0FBQzdCLENBQUMsRUFYVyxjQUFjLEtBQWQsY0FBYyxRQVd6QjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFDakIsTUFBTSxDQUFDLFFBQVEsQ0FBaUI7SUFDaEMsU0FBUyxDQUFnQztJQUN6QyxPQUFPLENBQW1DO0lBRWxELHlDQUF5QztJQUNqQyxNQUFNLENBQVUsa0JBQWtCLEdBQUc7UUFDM0Msb0JBQW9CO1FBQ3BCLFFBQVEsRUFBRTtZQUNSLGdFQUFnRTtZQUNoRSx3Q0FBd0M7WUFDeEMsNEVBQTRFO1lBQzVFLHNEQUFzRDtZQUN0RCx3REFBd0Q7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