332 lines
10 KiB
TypeScript
332 lines
10 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../../../ts/plugins.js';
|
|
import * as net from 'net';
|
|
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
|
|
import type { ITestServer } from '../../helpers/server.loader.js';
|
|
|
|
const TEST_PORT = 2525;
|
|
let testServer: ITestServer;
|
|
|
|
tap.test('setup - start test server', async (toolsArg) => {
|
|
testServer = await startTestServer({ port: TEST_PORT });
|
|
await toolsArg.delayFor(1000);
|
|
});
|
|
|
|
tap.test('Header Injection Prevention - CRLF injection in headers', async (tools) => {
|
|
const done = tools.defer();
|
|
|
|
const socket = net.createConnection({
|
|
host: 'localhost',
|
|
port: TEST_PORT,
|
|
timeout: 30000
|
|
});
|
|
|
|
let dataBuffer = '';
|
|
let step = 'greeting';
|
|
|
|
socket.on('data', (data) => {
|
|
dataBuffer += data.toString();
|
|
console.log('Server response:', data.toString());
|
|
|
|
if (step === 'greeting' && dataBuffer.includes('220 ')) {
|
|
step = 'ehlo';
|
|
socket.write('EHLO testclient\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
|
|
step = 'mail';
|
|
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'mail' && dataBuffer.includes('250')) {
|
|
step = 'rcpt';
|
|
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
|
|
step = 'data';
|
|
socket.write('DATA\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'data' && dataBuffer.includes('354')) {
|
|
// Attempt header injection with CRLF sequences
|
|
const email = [
|
|
`From: sender@example.com`,
|
|
`To: recipient@example.com`,
|
|
`Subject: Test\r\nBcc: hidden@attacker.com`, // CRLF injection attempt
|
|
`Date: ${new Date().toUTCString()}`,
|
|
`Message-ID: <header-inject-${Date.now()}@example.com>`,
|
|
`X-Custom: normal\r\nX-Injected: malicious`, // Another injection attempt
|
|
'',
|
|
'This email tests header injection prevention.',
|
|
'.',
|
|
''
|
|
].join('\r\n');
|
|
|
|
socket.write(email);
|
|
dataBuffer = '';
|
|
} else if (dataBuffer.includes('250 ') || dataBuffer.includes('550 ')) {
|
|
const accepted = dataBuffer.includes('250');
|
|
const rejected = dataBuffer.includes('550');
|
|
|
|
console.log(`Header injection attempt: ${accepted ? 'accepted' : 'rejected'}`);
|
|
|
|
if (rejected) {
|
|
console.log('Header injection prevention active - malicious headers detected');
|
|
}
|
|
|
|
expect(accepted || rejected).toEqual(true);
|
|
|
|
socket.write('QUIT\r\n');
|
|
socket.end();
|
|
done.resolve();
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
done.reject(err);
|
|
});
|
|
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('Header Injection Prevention - Command injection in MAIL FROM', async (tools) => {
|
|
const done = tools.defer();
|
|
|
|
const socket = net.createConnection({
|
|
host: 'localhost',
|
|
port: TEST_PORT,
|
|
timeout: 30000
|
|
});
|
|
|
|
let dataBuffer = '';
|
|
let step = 'greeting';
|
|
|
|
socket.on('data', (data) => {
|
|
dataBuffer += data.toString();
|
|
console.log('Server response:', data.toString());
|
|
|
|
if (step === 'greeting' && dataBuffer.includes('220 ')) {
|
|
step = 'ehlo';
|
|
socket.write('EHLO testclient\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
|
|
step = 'mail';
|
|
// Attempt command injection in MAIL FROM
|
|
socket.write('MAIL FROM:<test@example.com> SIZE=1000\r\nRCPT TO:<hidden@attacker.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'mail') {
|
|
// Server should reject or handle this properly
|
|
const properResponse = dataBuffer.includes('250') ||
|
|
dataBuffer.includes('501') ||
|
|
dataBuffer.includes('500');
|
|
|
|
console.log('Command injection attempt handled');
|
|
expect(properResponse).toEqual(true);
|
|
|
|
socket.write('QUIT\r\n');
|
|
socket.end();
|
|
done.resolve();
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
done.reject(err);
|
|
});
|
|
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('Header Injection Prevention - HTML/Script injection in body', async (tools) => {
|
|
const done = tools.defer();
|
|
|
|
const socket = net.createConnection({
|
|
host: 'localhost',
|
|
port: TEST_PORT,
|
|
timeout: 30000
|
|
});
|
|
|
|
let dataBuffer = '';
|
|
let step = 'greeting';
|
|
|
|
socket.on('data', (data) => {
|
|
dataBuffer += data.toString();
|
|
console.log('Server response:', data.toString());
|
|
|
|
if (step === 'greeting' && dataBuffer.includes('220 ')) {
|
|
step = 'ehlo';
|
|
socket.write('EHLO testclient\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
|
|
step = 'mail';
|
|
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'mail' && dataBuffer.includes('250')) {
|
|
step = 'rcpt';
|
|
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
|
|
step = 'data';
|
|
socket.write('DATA\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'data' && dataBuffer.includes('354')) {
|
|
// Email with HTML/Script content
|
|
const email = [
|
|
`From: sender@example.com`,
|
|
`To: recipient@example.com`,
|
|
`Subject: HTML Injection Test`,
|
|
`Date: ${new Date().toUTCString()}`,
|
|
`Message-ID: <html-inject-${Date.now()}@example.com>`,
|
|
`Content-Type: text/html`,
|
|
'',
|
|
'<html><body>',
|
|
'<h1>Test Email</h1>',
|
|
'<script>alert("XSS Attack")</script>',
|
|
'<iframe src="http://malicious-site.com"></iframe>',
|
|
'Injected-Header: malicious-value', // Attempted header injection in body
|
|
'</body></html>',
|
|
'.',
|
|
''
|
|
].join('\r\n');
|
|
|
|
socket.write(email);
|
|
dataBuffer = '';
|
|
} else if (dataBuffer.includes('250 ') || dataBuffer.includes('550 ')) {
|
|
const accepted = dataBuffer.includes('250');
|
|
console.log(`HTML/Script content: ${accepted ? 'accepted (may be sanitized)' : 'rejected'}`);
|
|
expect(true).toEqual(true);
|
|
|
|
socket.write('QUIT\r\n');
|
|
socket.end();
|
|
done.resolve();
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
done.reject(err);
|
|
});
|
|
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('Header Injection Prevention - Null byte injection', async (tools) => {
|
|
const done = tools.defer();
|
|
|
|
const socket = net.createConnection({
|
|
host: 'localhost',
|
|
port: TEST_PORT,
|
|
timeout: 30000
|
|
});
|
|
|
|
let dataBuffer = '';
|
|
let step = 'greeting';
|
|
|
|
socket.on('data', (data) => {
|
|
dataBuffer += data.toString();
|
|
console.log('Server response:', data.toString());
|
|
|
|
if (step === 'greeting' && dataBuffer.includes('220 ')) {
|
|
step = 'ehlo';
|
|
socket.write('EHLO testclient\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
|
|
step = 'mail';
|
|
// Attempt null byte injection
|
|
socket.write('MAIL FROM:<sender\x00@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'mail') {
|
|
// Should be rejected or sanitized
|
|
const handled = dataBuffer.includes('250') ||
|
|
dataBuffer.includes('501') ||
|
|
dataBuffer.includes('550');
|
|
|
|
console.log('Null byte injection attempt handled');
|
|
expect(handled).toEqual(true);
|
|
|
|
socket.write('QUIT\r\n');
|
|
socket.end();
|
|
done.resolve();
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
done.reject(err);
|
|
});
|
|
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('Header Injection Prevention - Unicode and encoding attacks', async (tools) => {
|
|
const done = tools.defer();
|
|
|
|
const socket = net.createConnection({
|
|
host: 'localhost',
|
|
port: TEST_PORT,
|
|
timeout: 30000
|
|
});
|
|
|
|
let dataBuffer = '';
|
|
let step = 'greeting';
|
|
|
|
socket.on('data', (data) => {
|
|
dataBuffer += data.toString();
|
|
console.log('Server response:', data.toString());
|
|
|
|
if (step === 'greeting' && dataBuffer.includes('220 ')) {
|
|
step = 'ehlo';
|
|
socket.write('EHLO testclient\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
|
|
step = 'mail';
|
|
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'mail' && dataBuffer.includes('250')) {
|
|
step = 'rcpt';
|
|
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
|
|
step = 'data';
|
|
socket.write('DATA\r\n');
|
|
dataBuffer = '';
|
|
} else if (step === 'data' && dataBuffer.includes('354')) {
|
|
// Unicode tricks and encoding attacks
|
|
const email = [
|
|
`From: sender@example.com`,
|
|
`To: recipient@example.com`,
|
|
`Subject: =?UTF-8?B?${Buffer.from('Test\r\nBcc: hidden@attacker.com').toString('base64')}?=`, // Encoded injection
|
|
`Date: ${new Date().toUTCString()}`,
|
|
`Message-ID: <unicode-inject-${Date.now()}@example.com>`,
|
|
`X-Test: \u000D\u000AX-Injected: true`, // Unicode CRLF
|
|
'',
|
|
'Testing unicode and encoding attacks.',
|
|
'\x00\x0D\x0AExtra-Header: injected', // Null byte + CRLF
|
|
'.',
|
|
''
|
|
].join('\r\n');
|
|
|
|
socket.write(email);
|
|
dataBuffer = '';
|
|
} else if (dataBuffer.includes('250 ') || dataBuffer.includes('550 ')) {
|
|
const result = dataBuffer.includes('250') ? 'accepted' : 'rejected';
|
|
console.log(`Unicode/encoding attack: ${result}`);
|
|
expect(true).toEqual(true);
|
|
|
|
socket.write('QUIT\r\n');
|
|
socket.end();
|
|
done.resolve();
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
done.reject(err);
|
|
});
|
|
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('cleanup - stop test server', async () => {
|
|
await stopTestServer(testServer);
|
|
});
|
|
|
|
export default tap.start(); |