Skip to content

Commit 6a3ada9

Browse files
Luke McFarlaneindutny
Luke McFarlane
authored andcommitted
lib: fixed CVE-2023-42282 and added unit test
1 parent 5dc3b2f commit 6a3ada9

File tree

2 files changed

+166
-4
lines changed

2 files changed

+166
-4
lines changed

lib/ip.js

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,26 @@ ip.isEqual = function (a, b) {
311311
};
312312

313313
ip.isPrivate = function (addr) {
314-
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i
315-
.test(addr)
314+
// check loopback addresses first
315+
if (ip.isLoopback(addr)) {
316+
return true;
317+
}
318+
319+
// ensure the ipv4 address is valid
320+
if (!ip.isV6Format(addr)) {
321+
const ipl = ip.normalizeToLong(addr);
322+
if (ipl < 0) {
323+
throw new Error('invalid ipv4 address');
324+
}
325+
// normalize the address for the private range checks that follow
326+
addr = ip.fromLong(ipl);
327+
}
328+
329+
// check private ranges
330+
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
316331
|| /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
317332
|| /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
318333
.test(addr)
319-
|| /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
320334
|| /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
321335
|| /^f[cd][0-9a-f]{2}:/i.test(addr)
322336
|| /^fe80:/i.test(addr)
@@ -329,9 +343,16 @@ ip.isPublic = function (addr) {
329343
};
330344

331345
ip.isLoopback = function (addr) {
346+
// If addr is an IPv4 address in long integer form (no dots and no colons), convert it
347+
if (!/\./.test(addr) && !/:/.test(addr)) {
348+
addr = ip.fromLong(Number(addr));
349+
}
350+
332351
return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
333352
.test(addr)
334-
|| /^fe80::1$/.test(addr)
353+
|| /^0177\./.test(addr)
354+
|| /^0x7f\./i.test(addr)
355+
|| /^fe80::1$/i.test(addr)
335356
|| /^::1$/.test(addr)
336357
|| /^::$/.test(addr);
337358
};
@@ -425,3 +446,51 @@ ip.fromLong = function (ipl) {
425446
ipl >> 8 & 255}.${
426447
ipl & 255}`);
427448
};
449+
450+
ip.normalizeToLong = function (addr) {
451+
const parts = addr.split('.').map(part => {
452+
// Handle hexadecimal format
453+
if (part.startsWith('0x') || part.startsWith('0X')) {
454+
return parseInt(part, 16);
455+
}
456+
// Handle octal format (strictly digits 0-7 after a leading zero)
457+
else if (part.startsWith('0') && part !== '0' && /^[0-7]+$/.test(part)) {
458+
return parseInt(part, 8);
459+
}
460+
// Handle decimal format, reject invalid leading zeros
461+
else if (/^[1-9]\d*$/.test(part) || part === '0') {
462+
return parseInt(part, 10);
463+
}
464+
// Return NaN for invalid formats to indicate parsing failure
465+
else {
466+
return NaN;
467+
}
468+
});
469+
470+
if (parts.some(isNaN)) return -1; // Indicate error with -1
471+
472+
let val = 0;
473+
const n = parts.length;
474+
475+
switch (n) {
476+
case 1:
477+
val = parts[0];
478+
break;
479+
case 2:
480+
if (parts[0] > 0xff || parts[1] > 0xffffff) return -1;
481+
val = (parts[0] << 24) | (parts[1] & 0xffffff);
482+
break;
483+
case 3:
484+
if (parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xffff) return -1;
485+
val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] & 0xffff);
486+
break;
487+
case 4:
488+
if (parts.some(part => part > 0xff)) return -1;
489+
val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
490+
break;
491+
default:
492+
return -1; // Error case
493+
}
494+
495+
return val >>> 0;
496+
};

test/api-test.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,57 @@ describe('IP library for node.js', () => {
251251
});
252252
});
253253

254+
describe('normalizeIpv4() method', () => {
255+
// Testing valid inputs with different notations
256+
it('should correctly normalize "127.0.0.1"', () => {
257+
assert.equal(ip.normalizeToLong('127.0.0.1'), 2130706433);
258+
});
259+
260+
it('should correctly handle "127.1" as two parts', () => {
261+
assert.equal(ip.normalizeToLong('127.1'), 2130706433);
262+
});
263+
264+
it('should correctly handle "127.0.1" as three parts', () => {
265+
assert.equal(ip.normalizeToLong('127.0.1'), 2130706433);
266+
});
267+
268+
269+
it('should correctly handle hexadecimal notation "0x7f.0x0.0x0.0x1"', () => {
270+
assert.equal(ip.normalizeToLong('0x7f.0x0.0x0.0x1'), 2130706433);
271+
});
272+
273+
// Testing with fewer than 4 parts
274+
it('should correctly handle "0x7f000001" as a single part', () => {
275+
assert.equal(ip.normalizeToLong('0x7f000001'), 2130706433);
276+
});
277+
278+
it('should correctly handle octal notation "010.0.0.01"', () => {
279+
assert.equal(ip.normalizeToLong('010.0.0.01'), 134217729);
280+
});
281+
282+
// Testing invalid inputs
283+
it('should return -1 for an invalid address "256.100.50.25"', () => {
284+
assert.equal(ip.normalizeToLong('256.100.50.25'), -1);
285+
});
286+
287+
it('should return -1 for an address with invalid octal "019.0.0.1"', () => {
288+
assert.equal(ip.normalizeToLong('019.0.0.1'), -1);
289+
});
290+
291+
it('should return -1 for an address with invalid hex "0xGG.0.0.1"', () => {
292+
assert.equal(ip.normalizeToLong('0xGG.0.0.1'), -1);
293+
});
294+
295+
// Testing edge cases
296+
it('should return -1 for an empty string', () => {
297+
assert.equal(ip.normalizeToLong(''), -1);
298+
});
299+
300+
it('should return -1 for a string with too many parts "192.168.0.1.100"', () => {
301+
assert.equal(ip.normalizeToLong('192.168.0.1.100'), -1);
302+
});
303+
});
304+
254305
describe('isPrivate() method', () => {
255306
it('should check if an address is localhost', () => {
256307
assert.equal(ip.isPrivate('127.0.0.1'), true);
@@ -300,6 +351,10 @@ describe('IP library for node.js', () => {
300351
assert.equal(ip.isPrivate('::1'), true);
301352
assert.equal(ip.isPrivate('fe80::1'), true);
302353
});
354+
355+
it('should correctly identify hexadecimal IP addresses like \'0x7f.1\' as private', () => {
356+
assert.equal(ip.isPrivate('0x7f.1'), true);
357+
});
303358
});
304359

305360
describe('loopback() method', () => {
@@ -413,4 +468,42 @@ describe('IP library for node.js', () => {
413468
assert.equal(ip.fromLong(4294967295), '255.255.255.255');
414469
});
415470
});
471+
472+
// IPv4 loopback in octal notation
473+
it('should return true for octal representation "0177.0.0.1"', () => {
474+
assert.equal(ip.isLoopback('0177.0.0.1'), true);
475+
});
476+
477+
it('should return true for octal representation "0177.0.1"', () => {
478+
assert.equal(ip.isLoopback('0177.0.1'), true);
479+
});
480+
481+
it('should return true for octal representation "0177.1"', () => {
482+
assert.equal(ip.isLoopback('0177.1'), true);
483+
});
484+
485+
// IPv4 loopback in hexadecimal notation
486+
it('should return true for hexadecimal representation "0x7f.0.0.1"', () => {
487+
assert.equal(ip.isLoopback('0x7f.0.0.1'), true);
488+
});
489+
490+
// IPv4 loopback in hexadecimal notation
491+
it('should return true for hexadecimal representation "0x7f.0.1"', () => {
492+
assert.equal(ip.isLoopback('0x7f.0.1'), true);
493+
});
494+
495+
// IPv4 loopback in hexadecimal notation
496+
it('should return true for hexadecimal representation "0x7f.1"', () => {
497+
assert.equal(ip.isLoopback('0x7f.1'), true);
498+
});
499+
500+
// IPv4 loopback as a single long integer
501+
it('should return true for single long integer representation "2130706433"', () => {
502+
assert.equal(ip.isLoopback('2130706433'), true);
503+
});
504+
505+
// IPv4 non-loopback address
506+
it('should return false for "192.168.1.1"', () => {
507+
assert.equal(ip.isLoopback('192.168.1.1'), false);
508+
});
416509
});

0 commit comments

Comments
 (0)