update
This commit is contained in:
@@ -580,10 +580,11 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
|
|||||||
const headersText = rawData.substring(0, headerEndIndex);
|
const headersText = rawData.substring(0, headerEndIndex);
|
||||||
const bodyText = rawData.substring(headerEndIndex + 4); // Skip the \r\n\r\n separator
|
const bodyText = rawData.substring(headerEndIndex + 4); // Skip the \r\n\r\n separator
|
||||||
|
|
||||||
// Parse headers
|
// Parse headers with enhanced injection detection
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
const headerLines = headersText.split('\r\n');
|
const headerLines = headersText.split('\r\n');
|
||||||
let currentHeader = '';
|
let currentHeader = '';
|
||||||
|
const criticalHeaders = new Set<string>(); // Track critical headers for duplication detection
|
||||||
|
|
||||||
for (const line of headerLines) {
|
for (const line of headerLines) {
|
||||||
// Check if this is a continuation of a previous header
|
// Check if this is a continuation of a previous header
|
||||||
@@ -601,14 +602,47 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
|
|||||||
const value = line.substring(separatorIndex + 1).trim();
|
const value = line.substring(separatorIndex + 1).trim();
|
||||||
|
|
||||||
// Check for header injection attempts in header values
|
// Check for header injection attempts in header values
|
||||||
if (detectHeaderInjection(value)) {
|
if (detectHeaderInjection(value, 'email-header')) {
|
||||||
SmtpLogger.warn('Header injection attempt detected in email header', {
|
SmtpLogger.warn('Header injection attempt detected in email header', {
|
||||||
headerName: name,
|
headerName: name,
|
||||||
headerValue: value.substring(0, 100) + (value.length > 100 ? '...' : ''),
|
headerValue: value.substring(0, 100) + (value.length > 100 ? '...' : ''),
|
||||||
sessionId: session.id
|
sessionId: session.id
|
||||||
});
|
});
|
||||||
// Skip this header to prevent injection
|
// Throw error to reject the email completely
|
||||||
continue;
|
throw new Error(`Header injection attempt detected in ${name} header`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced security: Check for duplicate critical headers (potential injection)
|
||||||
|
const criticalHeaderNames = ['from', 'to', 'subject', 'date', 'message-id'];
|
||||||
|
if (criticalHeaderNames.includes(name)) {
|
||||||
|
if (criticalHeaders.has(name)) {
|
||||||
|
SmtpLogger.warn('Duplicate critical header detected - potential header injection', {
|
||||||
|
headerName: name,
|
||||||
|
existingValue: headers[name]?.substring(0, 50) + '...',
|
||||||
|
newValue: value.substring(0, 50) + '...',
|
||||||
|
sessionId: session.id
|
||||||
|
});
|
||||||
|
// Throw error for duplicate critical headers
|
||||||
|
throw new Error(`Duplicate ${name} header detected - potential header injection`);
|
||||||
|
}
|
||||||
|
criticalHeaders.add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced security: Check for envelope mismatch (spoofing attempt)
|
||||||
|
if (name === 'from' && session.envelope?.mailFrom?.address) {
|
||||||
|
const emailFromHeader = value.match(/<([^>]+)>/)?.[1] || value.trim();
|
||||||
|
const envelopeFrom = session.envelope.mailFrom.address;
|
||||||
|
// Allow some flexibility but detect obvious spoofing attempts
|
||||||
|
if (emailFromHeader && envelopeFrom &&
|
||||||
|
!emailFromHeader.toLowerCase().includes(envelopeFrom.toLowerCase()) &&
|
||||||
|
!envelopeFrom.toLowerCase().includes(emailFromHeader.toLowerCase())) {
|
||||||
|
SmtpLogger.warn('Potential sender spoofing detected', {
|
||||||
|
envelopeFrom: envelopeFrom,
|
||||||
|
headerFrom: emailFromHeader,
|
||||||
|
sessionId: session.id
|
||||||
|
});
|
||||||
|
// Note: This is logged but not blocked as legitimate use cases exist
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for MIME-encoded headers (especially Subject)
|
// Special handling for MIME-encoded headers (especially Subject)
|
||||||
|
@@ -29,15 +29,45 @@ const HEADER_INJECTION_PATTERNS = [
|
|||||||
/**
|
/**
|
||||||
* Detects header injection attempts in input strings
|
* Detects header injection attempts in input strings
|
||||||
* @param input - The input string to check
|
* @param input - The input string to check
|
||||||
|
* @param context - The context where this input is being used ('smtp-command' or 'email-header')
|
||||||
* @returns true if header injection is detected, false otherwise
|
* @returns true if header injection is detected, false otherwise
|
||||||
*/
|
*/
|
||||||
export function detectHeaderInjection(input: string): boolean {
|
export function detectHeaderInjection(input: string, context: 'smtp-command' | 'email-header' = 'smtp-command'): boolean {
|
||||||
if (!input || typeof input !== 'string') {
|
if (!input || typeof input !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check against all header injection patterns
|
// Check for control characters and CRLF sequences (always dangerous)
|
||||||
return HEADER_INJECTION_PATTERNS.some(pattern => pattern.test(input));
|
const controlCharPatterns = [
|
||||||
|
/\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
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check control characters (always dangerous in any context)
|
||||||
|
if (controlCharPatterns.some(pattern => pattern.test(input))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For email headers, also check for header injection patterns
|
||||||
|
if (context === 'email-header') {
|
||||||
|
const headerPatterns = [
|
||||||
|
/(?:subject|from|to|cc|bcc|reply-to|return-path|received|delivered-to|x-.*?):/i // Email headers
|
||||||
|
];
|
||||||
|
return headerPatterns.some(pattern => pattern.test(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
// For SMTP commands, don't flag normal command syntax like "TO:" as header injection
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user