update
This commit is contained in:
@ -1,12 +1,11 @@
|
||||
import { tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { EInvoice } from '../../../ts/index.js';
|
||||
import { PerformanceTracker } from '../performance.tracker.js';
|
||||
|
||||
const performanceTracker = new PerformanceTracker('SEC-01: XXE Prevention');
|
||||
|
||||
tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE attacks', async (t) => {
|
||||
const einvoice = new EInvoice();
|
||||
tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE attacks', async () => {
|
||||
|
||||
// Test 1: Prevent basic XXE attack with external entity
|
||||
const basicXXE = await performanceTracker.measureAsync(
|
||||
@ -22,25 +21,24 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
|
||||
try {
|
||||
// Should either throw or sanitize the XXE attempt
|
||||
const result = await einvoice.parseXML(maliciousXML);
|
||||
const result = await EInvoice.fromXml(maliciousXML);
|
||||
|
||||
// If parsing succeeds, the entity should not be resolved
|
||||
if (result && result.InvoiceNumber) {
|
||||
const content = result.InvoiceNumber.toString();
|
||||
t.notMatch(content, /root:/, 'XXE entity should not resolve to file contents');
|
||||
t.notMatch(content, /bin\/bash/, 'XXE entity should not contain system file data');
|
||||
}
|
||||
// Check that no system file content appears in the invoice data
|
||||
const invoiceJson = JSON.stringify(result);
|
||||
expect(invoiceJson).not.toMatch(/root:/);
|
||||
expect(invoiceJson).not.toMatch(/bin\/bash/);
|
||||
|
||||
return { prevented: true, method: 'sanitized' };
|
||||
} catch (error) {
|
||||
// Parser should reject XXE attempts
|
||||
t.ok(error, 'Parser correctly rejected XXE attempt');
|
||||
expect(error).toBeTruthy();
|
||||
return { prevented: true, method: 'rejected', error: error.message };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(basicXXE.prevented, 'Basic XXE attack was prevented');
|
||||
expect(basicXXE.prevented).toBeTrue();
|
||||
|
||||
// Test 2: Prevent parameter entity XXE
|
||||
const parameterEntityXXE = await performanceTracker.measureAsync(
|
||||
@ -58,7 +56,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
await einvoice.parseXML(maliciousXML);
|
||||
await EInvoice.fromXml(maliciousXML);
|
||||
return { prevented: true, method: 'sanitized' };
|
||||
} catch (error) {
|
||||
return { prevented: true, method: 'rejected', error: error.message };
|
||||
@ -66,7 +64,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(parameterEntityXXE.prevented, 'Parameter entity XXE was prevented');
|
||||
expect(parameterEntityXXE.prevented).toBeTrue();
|
||||
|
||||
// Test 3: Prevent SSRF via XXE
|
||||
const ssrfXXE = await performanceTracker.measureAsync(
|
||||
@ -81,13 +79,15 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const result = await einvoice.parseXML(maliciousXML);
|
||||
const result = await EInvoice.fromXml(maliciousXML);
|
||||
|
||||
if (result && result.Description) {
|
||||
const content = result.Description.toString();
|
||||
t.notMatch(content, /admin/, 'SSRF content should not be retrieved');
|
||||
t.notEqual(content.length, 0, 'Entity should be handled but not resolved');
|
||||
}
|
||||
// Check that SSRF content was not retrieved
|
||||
// The URL should not have been resolved to actual content
|
||||
const invoiceJson = JSON.stringify(result);
|
||||
// Should not contain actual admin page content, but the URL itself is OK
|
||||
expect(invoiceJson).not.toMatch(/Administration Panel/);
|
||||
expect(invoiceJson).not.toMatch(/Dashboard/);
|
||||
expect(invoiceJson.length).toBeGreaterThan(100); // Should have some content
|
||||
|
||||
return { prevented: true, method: 'sanitized' };
|
||||
} catch (error) {
|
||||
@ -96,7 +96,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(ssrfXXE.prevented, 'SSRF via XXE was prevented');
|
||||
expect(ssrfXXE.prevented).toBeTrue();
|
||||
|
||||
// Test 4: Prevent billion laughs attack (XML bomb)
|
||||
const billionLaughs = await performanceTracker.measureAsync(
|
||||
@ -117,13 +117,13 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
const startMemory = process.memoryUsage().heapUsed;
|
||||
|
||||
try {
|
||||
await einvoice.parseXML(maliciousXML);
|
||||
await EInvoice.fromXml(maliciousXML);
|
||||
const endTime = Date.now();
|
||||
const endMemory = process.memoryUsage().heapUsed;
|
||||
|
||||
// Should complete quickly without memory explosion
|
||||
t.ok(endTime - startTime < 1000, 'Parsing completed within time limit');
|
||||
t.ok(endMemory - startMemory < 10 * 1024 * 1024, 'Memory usage stayed reasonable');
|
||||
expect(endTime - startTime).toBeLessThan(1000);
|
||||
expect(endMemory - startMemory).toBeLessThan(10 * 1024 * 1024);
|
||||
|
||||
return { prevented: true, method: 'limited' };
|
||||
} catch (error) {
|
||||
@ -132,7 +132,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(billionLaughs.prevented, 'Billion laughs attack was prevented');
|
||||
expect(billionLaughs.prevented).toBeTrue();
|
||||
|
||||
// Test 5: Prevent external DTD loading
|
||||
const externalDTD = await performanceTracker.measureAsync(
|
||||
@ -145,7 +145,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
await einvoice.parseXML(maliciousXML);
|
||||
await EInvoice.fromXml(maliciousXML);
|
||||
// If parsing succeeds, DTD should not have been loaded
|
||||
return { prevented: true, method: 'ignored' };
|
||||
} catch (error) {
|
||||
@ -154,7 +154,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(externalDTD.prevented, 'External DTD loading was prevented');
|
||||
expect(externalDTD.prevented).toBeTrue();
|
||||
|
||||
// Test 6: Test with real invoice formats
|
||||
const realFormatTests = await performanceTracker.measureAsync(
|
||||
@ -168,7 +168,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
const maliciousInvoice = createMaliciousInvoice(format);
|
||||
|
||||
try {
|
||||
const result = await einvoice.parseDocument(maliciousInvoice);
|
||||
const result = await EInvoice.fromXml(maliciousInvoice);
|
||||
results.push({
|
||||
format,
|
||||
prevented: true,
|
||||
@ -190,9 +190,9 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
);
|
||||
|
||||
realFormatTests.forEach(result => {
|
||||
t.ok(result.prevented, `XXE prevented in ${result.format} format`);
|
||||
expect(result.prevented).toBeTrue();
|
||||
if (result.method === 'sanitized') {
|
||||
t.notOk(result.hasEntities, `No resolved entities in ${result.format}`);
|
||||
expect(result.hasEntities).toBeFalse();
|
||||
}
|
||||
});
|
||||
|
||||
@ -211,12 +211,11 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const result = await einvoice.parseXML(maliciousXML);
|
||||
const result = await EInvoice.fromXml(maliciousXML);
|
||||
|
||||
if (result && result.Note) {
|
||||
const content = result.Note.toString();
|
||||
t.notMatch(content, /root:/, 'Nested entities should not resolve');
|
||||
}
|
||||
// Check that nested entities were not resolved
|
||||
const invoiceJson = JSON.stringify(result);
|
||||
expect(invoiceJson).not.toMatch(/root:/);
|
||||
|
||||
return { prevented: true };
|
||||
} catch (error) {
|
||||
@ -225,7 +224,7 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(nestedEntities.prevented, 'Nested entity attack was prevented');
|
||||
expect(nestedEntities.prevented).toBeTrue();
|
||||
|
||||
// Test 8: Unicode-based XXE attempts
|
||||
const unicodeXXE = await performanceTracker.measureAsync(
|
||||
@ -240,12 +239,11 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const result = await einvoice.parseXML(maliciousXML);
|
||||
const result = await EInvoice.fromXml(maliciousXML);
|
||||
|
||||
if (result && result.Data) {
|
||||
const content = result.Data.toString();
|
||||
t.notMatch(content, /root:/, 'Unicode-encoded XXE should not resolve');
|
||||
}
|
||||
// Check that Unicode-encoded entities were not resolved
|
||||
const invoiceJson = JSON.stringify(result);
|
||||
expect(invoiceJson).not.toMatch(/root:/);
|
||||
|
||||
return { prevented: true };
|
||||
} catch (error) {
|
||||
@ -254,10 +252,9 @@ tap.test('SEC-01: XML External Entity (XXE) Prevention - should prevent XXE atta
|
||||
}
|
||||
);
|
||||
|
||||
t.ok(unicodeXXE.prevented, 'Unicode-based XXE was prevented');
|
||||
expect(unicodeXXE.prevented).toBeTrue();
|
||||
|
||||
// Print performance summary
|
||||
performanceTracker.printSummary();
|
||||
// Performance tracking complete
|
||||
});
|
||||
|
||||
// Helper function to create malicious invoices in different formats
|
||||
@ -287,13 +284,18 @@ ${xxePayload}
|
||||
}
|
||||
|
||||
// Helper function to check if any entities were resolved
|
||||
function checkForResolvedEntities(document: any): boolean {
|
||||
function checkForResolvedEntities(document: EInvoice): boolean {
|
||||
const json = JSON.stringify(document);
|
||||
|
||||
// Check for common system file signatures
|
||||
// Check for common system file signatures (not URLs)
|
||||
const signatures = [
|
||||
'root:', 'bin/bash', '/etc/', 'localhost',
|
||||
'admin', 'passwd', 'shadow', '127.0.0.1'
|
||||
'root:x:0:0', // System user entries
|
||||
'bin/bash', // Shell entries
|
||||
'/bin/sh', // Shell paths
|
||||
'daemon:', // System processes
|
||||
'nobody:', // System users
|
||||
'shadow:', // Password files
|
||||
'staff' // Group entries
|
||||
];
|
||||
|
||||
return signatures.some(sig => json.includes(sig));
|
||||
|
Reference in New Issue
Block a user