update
This commit is contained in:
@@ -11,6 +11,7 @@ import type { ISmtpSession, ISmtpTransactionResult } from './interfaces.js';
|
|||||||
import type { IDataHandler, ISessionManager } from './interfaces.js';
|
import type { IDataHandler, ISessionManager } from './interfaces.js';
|
||||||
import { SmtpResponseCode, SMTP_PATTERNS, SMTP_DEFAULTS } from './constants.js';
|
import { SmtpResponseCode, SMTP_PATTERNS, SMTP_DEFAULTS } from './constants.js';
|
||||||
import { SmtpLogger } from './utils/logging.js';
|
import { SmtpLogger } from './utils/logging.js';
|
||||||
|
import { detectHeaderInjection } from './utils/validation.js';
|
||||||
import { Email } from '../../core/classes.email.js';
|
import { Email } from '../../core/classes.email.js';
|
||||||
import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
|
import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
|
||||||
|
|
||||||
@@ -599,6 +600,17 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
|
|||||||
const name = line.substring(0, separatorIndex).trim().toLowerCase();
|
const name = line.substring(0, separatorIndex).trim().toLowerCase();
|
||||||
const value = line.substring(separatorIndex + 1).trim();
|
const value = line.substring(separatorIndex + 1).trim();
|
||||||
|
|
||||||
|
// Check for header injection attempts in header values
|
||||||
|
if (detectHeaderInjection(value)) {
|
||||||
|
SmtpLogger.warn('Header injection attempt detected in email header', {
|
||||||
|
headerName: name,
|
||||||
|
headerValue: value.substring(0, 100) + (value.length > 100 ? '...' : ''),
|
||||||
|
sessionId: session.id
|
||||||
|
});
|
||||||
|
// Skip this header to prevent injection
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Special handling for MIME-encoded headers (especially Subject)
|
// Special handling for MIME-encoded headers (especially Subject)
|
||||||
if (name === 'subject' && value.includes('=?')) {
|
if (name === 'subject' && value.includes('=?')) {
|
||||||
try {
|
try {
|
||||||
|
@@ -5,6 +5,59 @@
|
|||||||
|
|
||||||
import { SmtpState } from '../interfaces.js';
|
import { SmtpState } from '../interfaces.js';
|
||||||
import { SMTP_PATTERNS } from '../constants.js';
|
import { SMTP_PATTERNS } from '../constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header injection patterns to detect malicious input
|
||||||
|
* These patterns detect common header injection attempts
|
||||||
|
*/
|
||||||
|
const HEADER_INJECTION_PATTERNS = [
|
||||||
|
/\r\n/, // CRLF sequence
|
||||||
|
/\n/, // LF alone
|
||||||
|
/\r/, // CR alone
|
||||||
|
/\x00/, // Null byte
|
||||||
|
/\x0A/, // Line feed hex
|
||||||
|
/\x0D/, // Carriage return hex
|
||||||
|
/%0A/i, // URL encoded LF
|
||||||
|
/%0D/i, // URL encoded CR
|
||||||
|
/%0a/i, // URL encoded LF lowercase
|
||||||
|
/%0d/i, // URL encoded CR lowercase
|
||||||
|
/\\\n/, // Escaped newline
|
||||||
|
/\\\r/, // Escaped carriage return
|
||||||
|
/(?:subject|from|to|cc|bcc|reply-to|return-path|received|delivered-to|x-.*?):/i // Email headers
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects header injection attempts in input strings
|
||||||
|
* @param input - The input string to check
|
||||||
|
* @returns true if header injection is detected, false otherwise
|
||||||
|
*/
|
||||||
|
export function detectHeaderInjection(input: string): boolean {
|
||||||
|
if (!input || typeof input !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against all header injection patterns
|
||||||
|
return HEADER_INJECTION_PATTERNS.some(pattern => pattern.test(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes input by removing or escaping potentially dangerous characters
|
||||||
|
* @param input - The input string to sanitize
|
||||||
|
* @returns Sanitized string
|
||||||
|
*/
|
||||||
|
export function sanitizeInput(input: string): string {
|
||||||
|
if (!input || typeof input !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove control characters and potential injection sequences
|
||||||
|
return input
|
||||||
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control chars except \t, \n, \r
|
||||||
|
.replace(/\r\n/g, ' ') // Replace CRLF with space
|
||||||
|
.replace(/[\r\n]/g, ' ') // Replace individual CR/LF with space
|
||||||
|
.replace(/%0[aAdD]/gi, '') // Remove URL encoded CRLF
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
import { SmtpLogger } from './logging.js';
|
import { SmtpLogger } from './logging.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +88,12 @@ export function validateMailFrom(args: string): {
|
|||||||
return { isValid: false, errorMessage: 'Missing arguments' };
|
return { isValid: false, errorMessage: 'Missing arguments' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for header injection attempts
|
||||||
|
if (detectHeaderInjection(args)) {
|
||||||
|
SmtpLogger.warn('Header injection attempt detected in MAIL FROM command', { args });
|
||||||
|
return { isValid: false, errorMessage: 'Invalid syntax - illegal characters detected' };
|
||||||
|
}
|
||||||
|
|
||||||
// Handle "MAIL FROM:" already in the args
|
// Handle "MAIL FROM:" already in the args
|
||||||
let cleanArgs = args;
|
let cleanArgs = args;
|
||||||
if (args.toUpperCase().startsWith('MAIL FROM')) {
|
if (args.toUpperCase().startsWith('MAIL FROM')) {
|
||||||
@@ -118,6 +177,12 @@ export function validateRcptTo(args: string): {
|
|||||||
return { isValid: false, errorMessage: 'Missing arguments' };
|
return { isValid: false, errorMessage: 'Missing arguments' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for header injection attempts
|
||||||
|
if (detectHeaderInjection(args)) {
|
||||||
|
SmtpLogger.warn('Header injection attempt detected in RCPT TO command', { args });
|
||||||
|
return { isValid: false, errorMessage: 'Invalid syntax - illegal characters detected' };
|
||||||
|
}
|
||||||
|
|
||||||
// Handle "RCPT TO:" already in the args
|
// Handle "RCPT TO:" already in the args
|
||||||
let cleanArgs = args;
|
let cleanArgs = args;
|
||||||
if (args.toUpperCase().startsWith('RCPT TO')) {
|
if (args.toUpperCase().startsWith('RCPT TO')) {
|
||||||
@@ -187,6 +252,12 @@ export function validateEhlo(args: string): {
|
|||||||
return { isValid: false, errorMessage: 'Missing domain name' };
|
return { isValid: false, errorMessage: 'Missing domain name' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for header injection attempts
|
||||||
|
if (detectHeaderInjection(args)) {
|
||||||
|
SmtpLogger.warn('Header injection attempt detected in EHLO command', { args });
|
||||||
|
return { isValid: false, errorMessage: 'Invalid domain name format' };
|
||||||
|
}
|
||||||
|
|
||||||
// Extract hostname from EHLO command if present in args
|
// Extract hostname from EHLO command if present in args
|
||||||
let hostname = args;
|
let hostname = args;
|
||||||
const match = args.match(/^(?:EHLO|HELO)\s+([^\s]+)$/i);
|
const match = args.match(/^(?:EHLO|HELO)\s+([^\s]+)$/i);
|
||||||
|
Reference in New Issue
Block a user