einvoice/test/suite/einvoice_security/test.sec-02.xml-bomb.ts

452 lines
12 KiB
TypeScript
Raw Normal View History

2025-05-29 13:35:36 +00:00
import { expect, tap } from '@git.zone/tstest/tapbundle';
2025-05-25 19:45:37 +00:00
import * as plugins from '../plugins.js';
import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
const performanceTracker = new PerformanceTracker('SEC-02: XML Bomb Prevention');
2025-05-29 13:35:36 +00:00
tap.test('SEC-02: XML Bomb Prevention - should prevent XML bomb attacks', async () => {
2025-05-25 19:45:37 +00:00
// Test 1: Billion Laughs Attack (Exponential Entity Expansion)
const billionLaughs = await performanceTracker.measureAsync(
'billion-laughs-attack',
async () => {
const bombXML = `<?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;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<Invoice>
<Description>&lol9;</Description>
</Invoice>`;
const startTime = Date.now();
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const endMemory = process.memoryUsage();
const timeTaken = endTime - startTime;
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
// Should not take excessive time or memory
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(5000);
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'limited',
timeTaken,
memoryIncrease
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(billionLaughs.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 2: Quadratic Blowup Attack
const quadraticBlowup = await performanceTracker.measureAsync(
'quadratic-blowup-attack',
async () => {
// Create a string that repeats many times
const longString = 'A'.repeat(50000);
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY x "${longString}">
]>
<Invoice>
<Field1>&x;</Field1>
<Field2>&x;</Field2>
<Field3>&x;</Field3>
<Field4>&x;</Field4>
<Field5>&x;</Field5>
<Field6>&x;</Field6>
<Field7>&x;</Field7>
<Field8>&x;</Field8>
<Field9>&x;</Field9>
<Field10>&x;</Field10>
</Invoice>`;
const startTime = Date.now();
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const endMemory = process.memoryUsage();
const timeTaken = endTime - startTime;
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
// Should handle without quadratic memory growth
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(2000);
expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken,
memoryIncrease
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(quadraticBlowup.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 3: Recursive Entity Reference
const recursiveEntity = await performanceTracker.measureAsync(
'recursive-entity-attack',
async () => {
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY a "&b;">
<!ENTITY b "&c;">
<!ENTITY c "&a;">
]>
<Invoice>
<ID>&a;</ID>
</Invoice>`;
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled'
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(recursiveEntity.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 4: External Entity Expansion Attack
const externalEntityExpansion = await performanceTracker.measureAsync(
'external-entity-expansion',
async () => {
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % pe1 "<!ENTITY &#x25; pe2 'value2'>">
<!ENTITY % pe2 "<!ENTITY &#x25; pe3 'value3'>">
<!ENTITY % pe3 "<!ENTITY &#x25; pe4 'value4'>">
%pe1;
%pe2;
%pe3;
]>
<Invoice>
<Data>test</Data>
</Invoice>`;
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled'
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(externalEntityExpansion.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 5: Deep Nesting Attack
const deepNesting = await performanceTracker.measureAsync(
'deep-nesting-attack',
async () => {
let xmlContent = '<Invoice>';
const depth = 10000;
// Create deeply nested structure
for (let i = 0; i < depth; i++) {
xmlContent += '<Level' + i + '>';
}
xmlContent += 'data';
for (let i = depth - 1; i >= 0; i--) {
xmlContent += '</Level' + i + '>';
}
xmlContent += '</Invoice>';
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>${xmlContent}`;
const startTime = Date.now();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const timeTaken = endTime - startTime;
// Should handle deep nesting without stack overflow
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(5000);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken
};
} catch (error) {
// Stack overflow or depth limit reached
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(deepNesting.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 6: Attribute Blowup
const attributeBlowup = await performanceTracker.measureAsync(
'attribute-blowup-attack',
async () => {
let attributes = '';
2025-05-29 13:35:36 +00:00
for (let i = 0; i < 1000; i++) { // Reduced for faster testing
2025-05-25 19:45:37 +00:00
attributes += ` attr${i}="value${i}"`;
}
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice ${attributes}>
<ID>test</ID>
</Invoice>`;
const startTime = Date.now();
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const endMemory = process.memoryUsage();
const timeTaken = endTime - startTime;
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(10000);
expect(memoryIncrease).toBeLessThan(200 * 1024 * 1024);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken,
memoryIncrease
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(attributeBlowup.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 7: Comment Bomb
const commentBomb = await performanceTracker.measureAsync(
'comment-bomb-attack',
async () => {
2025-05-29 13:35:36 +00:00
const longComment = '<!-- ' + 'A'.repeat(100000) + ' -->'; // Reduced for faster testing
2025-05-25 19:45:37 +00:00
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
${longComment}
<ID>test</ID>
${longComment}
</Invoice>`;
const startTime = Date.now();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const timeTaken = endTime - startTime;
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(5000);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(commentBomb.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 8: Processing Instruction Bomb
const processingInstructionBomb = await performanceTracker.measureAsync(
'pi-bomb-attack',
async () => {
let pis = '';
2025-05-29 13:35:36 +00:00
for (let i = 0; i < 1000; i++) { // Reduced for faster testing
2025-05-25 19:45:37 +00:00
pis += `<?pi${i} data="value${i}"?>`;
}
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
${pis}
<Invoice>
<ID>test</ID>
</Invoice>`;
const startTime = Date.now();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const timeTaken = endTime - startTime;
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(10000);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(processingInstructionBomb.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 9: CDATA Bomb
const cdataBomb = await performanceTracker.measureAsync(
'cdata-bomb-attack',
async () => {
const largeCDATA = '<![CDATA[' + 'X'.repeat(50000000) + ']]>';
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<Description>${largeCDATA}</Description>
</Invoice>`;
const startTime = Date.now();
const startMemory = process.memoryUsage();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const endMemory = process.memoryUsage();
const timeTaken = endTime - startTime;
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(5000);
expect(memoryIncrease).toBeLessThan(200 * 1024 * 1024);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken,
memoryIncrease
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(cdataBomb.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
// Test 10: Namespace Bomb
const namespaceBomb = await performanceTracker.measureAsync(
'namespace-bomb-attack',
async () => {
let namespaces = '';
for (let i = 0; i < 10000; i++) {
namespaces += ` xmlns:ns${i}="http://example.com/ns${i}"`;
}
const bombXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice ${namespaces}>
<ID>test</ID>
</Invoice>`;
const startTime = Date.now();
try {
2025-05-29 13:35:36 +00:00
await EInvoice.fromXml(bombXML);
2025-05-25 19:45:37 +00:00
const endTime = Date.now();
const timeTaken = endTime - startTime;
2025-05-29 13:35:36 +00:00
expect(timeTaken).toBeLessThan(10000);
2025-05-25 19:45:37 +00:00
return {
prevented: true,
method: 'handled',
timeTaken
};
} catch (error) {
return {
prevented: true,
method: 'rejected',
error: error.message
};
}
}
);
2025-05-29 13:35:36 +00:00
expect(namespaceBomb.prevented).toBeTrue();
2025-05-25 19:45:37 +00:00
2025-05-29 13:35:36 +00:00
// Performance summary is handled by the tracker
2025-05-25 19:45:37 +00:00
});
// Run the test
tap.start();