einvoice/test/suite/einvoice_security/test.sec-06.memory-dos.ts

323 lines
9.9 KiB
TypeScript
Raw Normal View History

2025-05-29 13:35:36 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-26 04:04:51 +00:00
import * as plugins from '../plugins.js';
2025-05-29 13:35:36 +00:00
import { EInvoice, FormatDetector } from '../../../ts/index.js';
2025-05-26 04:04:51 +00:00
import { PerformanceTracker } from '../performance.tracker.js';
const performanceTracker = new PerformanceTracker('SEC-06: Memory DoS Prevention');
2025-05-29 13:35:36 +00:00
tap.test('SEC-06: Memory DoS Prevention - should prevent memory exhaustion attacks', async () => {
// Test 1: Large attribute count attack (reduced for practical testing)
2025-05-26 04:04:51 +00:00
const largeAttributeAttack = await performanceTracker.measureAsync(
'large-attribute-count-attack',
async () => {
2025-05-29 13:35:36 +00:00
// Create XML with many attributes (reduced from 1M to 10K for practical testing)
2025-05-26 04:04:51 +00:00
let attributes = '';
2025-05-29 13:35:36 +00:00
const attrCount = 10000;
2025-05-26 04:04:51 +00:00
for (let i = 0; i < attrCount; i++) {
attributes += ` attr${i}="value${i}"`;
}
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
2025-05-29 13:35:36 +00:00
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" ${attributes}>
2025-05-26 04:04:51 +00:00
<ID>test</ID>
2025-05-29 13:35:36 +00:00
<IssueDate>2024-01-01</IssueDate>
2025-05-26 04:04:51 +00:00
</Invoice>`;
const startMemory = process.memoryUsage();
const startTime = Date.now();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(maliciousXML);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
const endTime = Date.now();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
const timeTaken = endTime - startTime;
return {
prevented: memoryIncrease < 100 * 1024 * 1024, // Less than 100MB
memoryIncrease,
timeTaken,
attributeCount: attrCount
};
} catch (error) {
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Large attribute attack result:', largeAttributeAttack);
expect(largeAttributeAttack.prevented).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 2: Deep nesting attack (reduced depth)
const deepNestingAttack = await performanceTracker.measureAsync(
'deep-nesting-attack',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create deeply nested XML (reduced from 50K to 500 for practical testing)
const depth = 500;
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">';
2025-05-26 04:04:51 +00:00
for (let i = 0; i < depth; i++) {
2025-05-29 13:35:36 +00:00
xml += `<Note>`;
2025-05-26 04:04:51 +00:00
}
xml += 'data';
2025-05-29 13:35:36 +00:00
for (let i = 0; i < depth; i++) {
xml += `</Note>`;
2025-05-26 04:04:51 +00:00
}
2025-05-29 13:35:36 +00:00
xml += '<ID>test</ID><IssueDate>2024-01-01</IssueDate></Invoice>';
2025-05-26 04:04:51 +00:00
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(xml);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < 50 * 1024 * 1024, // Less than 50MB
memoryIncrease,
depth
};
} catch (error) {
// Stack overflow or depth limit is also prevention
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Deep nesting attack result:', deepNestingAttack);
expect(deepNestingAttack.prevented).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 3: Large element content
const largeContentAttack = await performanceTracker.measureAsync(
'large-content-attack',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create XML with very large content
const contentSize = 10 * 1024 * 1024; // 10MB
const largeContent = 'A'.repeat(contentSize);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
2025-05-26 04:04:51 +00:00
<ID>test</ID>
2025-05-29 13:35:36 +00:00
<Note>${largeContent}</Note>
<IssueDate>2024-01-01</IssueDate>
2025-05-26 04:04:51 +00:00
</Invoice>`;
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(xml);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
2025-05-29 13:35:36 +00:00
// Should handle large content efficiently
efficient: memoryIncrease < contentSize * 3, // Allow up to 3x content size
2025-05-26 04:04:51 +00:00
memoryIncrease,
2025-05-29 13:35:36 +00:00
contentSize
2025-05-26 04:04:51 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
efficient: true,
rejected: true,
error: error.message
2025-05-26 04:04:51 +00:00
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Large content attack result:', largeContentAttack);
expect(largeContentAttack.efficient).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 4: Entity expansion attack
const entityExpansionAttack = await performanceTracker.measureAsync(
'entity-expansion-attack',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Billion laughs attack variant
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
2025-05-26 04:04:51 +00:00
]>
2025-05-29 13:35:36 +00:00
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>&lol5;</ID>
<IssueDate>2024-01-01</IssueDate>
2025-05-26 04:04:51 +00:00
</Invoice>`;
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(xml);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
2025-05-29 13:35:36 +00:00
prevented: memoryIncrease < 10 * 1024 * 1024, // Less than 10MB
memoryIncrease
2025-05-26 04:04:51 +00:00
};
} catch (error) {
2025-05-29 13:35:36 +00:00
// Parser should reject or limit entity expansion
2025-05-26 04:04:51 +00:00
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Entity expansion attack result:', entityExpansionAttack);
expect(entityExpansionAttack.prevented).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 5: Quadratic blowup via attribute value normalization
const quadraticBlowupAttack = await performanceTracker.measureAsync(
'quadratic-blowup-attack',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create attribute with many spaces that might be normalized
const spaceCount = 100000;
const spaces = ' '.repeat(spaceCount);
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID attr="${spaces}">test</ID>
<IssueDate>2024-01-01</IssueDate>
2025-05-26 04:04:51 +00:00
</Invoice>`;
2025-05-29 13:35:36 +00:00
const startTime = Date.now();
2025-05-26 04:04:51 +00:00
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(xml);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
const endTime = Date.now();
const timeTaken = endTime - startTime;
2025-05-26 04:04:51 +00:00
return {
2025-05-29 13:35:36 +00:00
prevented: timeTaken < 5000, // Should process in under 5 seconds
timeTaken,
spaceCount
2025-05-26 04:04:51 +00:00
};
} catch (error) {
return {
prevented: true,
2025-05-29 13:35:36 +00:00
rejected: true,
error: error.message
2025-05-26 04:04:51 +00:00
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Quadratic blowup attack result:', quadraticBlowupAttack);
expect(quadraticBlowupAttack.prevented).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 6: Multiple large attachments
const largeAttachmentsAttack = await performanceTracker.measureAsync(
'large-attachments-attack',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Create multiple large base64 attachments
const attachmentSize = 1 * 1024 * 1024; // 1MB each
const attachmentCount = 10;
const base64Data = Buffer.from('A'.repeat(attachmentSize)).toString('base64');
let attachments = '';
for (let i = 0; i < attachmentCount; i++) {
attachments += `
<AdditionalDocumentReference>
<ID>${i}</ID>
<Attachment>
<EmbeddedDocumentBinaryObject mimeCode="application/pdf">
${base64Data}
</EmbeddedDocumentBinaryObject>
</Attachment>
</AdditionalDocumentReference>`;
2025-05-26 04:04:51 +00:00
}
2025-05-29 13:35:36 +00:00
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>test</ID>
<IssueDate>2024-01-01</IssueDate>
${attachments}
2025-05-26 04:04:51 +00:00
</Invoice>`;
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(xml);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
2025-05-29 13:35:36 +00:00
// Should handle attachments efficiently
efficient: memoryIncrease < attachmentSize * attachmentCount * 5,
2025-05-26 04:04:51 +00:00
memoryIncrease,
2025-05-29 13:35:36 +00:00
totalSize: attachmentSize * attachmentCount
2025-05-26 04:04:51 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
efficient: true,
2025-05-26 04:04:51 +00:00
rejected: true,
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Large attachments attack result:', largeAttachmentsAttack);
expect(largeAttachmentsAttack.efficient).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
// Test 7: Format detection with large input
const largeFormatDetection = await performanceTracker.measureAsync(
'large-format-detection',
2025-05-26 04:04:51 +00:00
async () => {
2025-05-29 13:35:36 +00:00
// Large input for format detection
const size = 5 * 1024 * 1024; // 5MB
const content = '<xml>' + 'A'.repeat(size) + '</xml>';
2025-05-26 04:04:51 +00:00
const startMemory = process.memoryUsage();
2025-05-29 13:35:36 +00:00
const startTime = Date.now();
2025-05-26 04:04:51 +00:00
try {
2025-05-29 13:35:36 +00:00
const format = FormatDetector.detectFormat(content);
2025-05-26 04:04:51 +00:00
const endMemory = process.memoryUsage();
2025-05-29 13:35:36 +00:00
const endTime = Date.now();
2025-05-26 04:04:51 +00:00
return {
2025-05-29 13:35:36 +00:00
efficient: endTime - startTime < 1000, // Should be fast
memoryIncrease: endMemory.heapUsed - startMemory.heapUsed,
timeTaken: endTime - startTime,
format
2025-05-26 04:04:51 +00:00
};
} catch (error) {
return {
2025-05-29 13:35:36 +00:00
efficient: true,
2025-05-26 04:04:51 +00:00
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
console.log('Large format detection result:', largeFormatDetection);
expect(largeFormatDetection.efficient).toEqual(true);
2025-05-26 04:04:51 +00:00
2025-05-29 13:35:36 +00:00
console.log('Memory DoS prevention tests completed');
2025-05-26 04:04:51 +00:00
});
// Run the test
tap.start();