Skip to content

Commit

Permalink
lib: fixed CVE-2023-42282 and added unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke McFarlane authored and indutny committed Feb 19, 2024
1 parent 4b2f4e7 commit 32f468f
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 4 deletions.
77 changes: 73 additions & 4 deletions lib/ip.js
Expand Up @@ -306,12 +306,26 @@ ip.isEqual = function (a, b) {
};

ip.isPrivate = function (addr) {
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i
.test(addr)
// check loopback addresses first
if (ip.isLoopback(addr)) {
return true;
}

// ensure the ipv4 address is valid
if (!ip.isV6Format(addr)) {
const ipl = ip.normalizeToLong(addr);
if (ipl < 0) {
throw new Error('invalid ipv4 address');
}
// normalize the address for the private range checks that follow
addr = ip.fromLong(ipl);
}

// check private ranges
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
|| /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
|| /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
.test(addr)
|| /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
|| /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
|| /^f[cd][0-9a-f]{2}:/i.test(addr)
|| /^fe80:/i.test(addr)
Expand All @@ -324,9 +338,16 @@ ip.isPublic = function (addr) {
};

ip.isLoopback = function (addr) {
// If addr is an IPv4 address in long integer form (no dots and no colons), convert it
if (!/\./.test(addr) && !/:/.test(addr)) {
addr = ip.fromLong(Number(addr));
}

return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
.test(addr)
|| /^fe80::1$/.test(addr)
|| /^0177\./.test(addr)
|| /^0x7f\./i.test(addr)
|| /^fe80::1$/i.test(addr)
|| /^::1$/.test(addr)
|| /^::$/.test(addr);
};
Expand Down Expand Up @@ -420,3 +441,51 @@ ip.fromLong = function (ipl) {
ipl >> 8 & 255}.${
ipl & 255}`);
};

ip.normalizeToLong = function (addr) {
const parts = addr.split('.').map(part => {
// Handle hexadecimal format
if (part.startsWith('0x') || part.startsWith('0X')) {
return parseInt(part, 16);
}
// Handle octal format (strictly digits 0-7 after a leading zero)
else if (part.startsWith('0') && part !== '0' && /^[0-7]+$/.test(part)) {
return parseInt(part, 8);
}
// Handle decimal format, reject invalid leading zeros
else if (/^[1-9]\d*$/.test(part) || part === '0') {
return parseInt(part, 10);
}
// Return NaN for invalid formats to indicate parsing failure
else {
return NaN;
}
});

if (parts.some(isNaN)) return -1; // Indicate error with -1

let val = 0;
const n = parts.length;

switch (n) {
case 1:
val = parts[0];
break;
case 2:
if (parts[0] > 0xff || parts[1] > 0xffffff) return -1;
val = (parts[0] << 24) | (parts[1] & 0xffffff);
break;
case 3:
if (parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xffff) return -1;
val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] & 0xffff);
break;
case 4:
if (parts.some(part => part > 0xff)) return -1;
val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
break;
default:
return -1; // Error case
}

return val >>> 0;
};
93 changes: 93 additions & 0 deletions test/api-test.js
Expand Up @@ -251,6 +251,57 @@ describe('IP library for node.js', () => {
});
});

describe('normalizeIpv4() method', () => {
// Testing valid inputs with different notations
it('should correctly normalize "127.0.0.1"', () => {
assert.equal(ip.normalizeToLong('127.0.0.1'), 2130706433);
});

it('should correctly handle "127.1" as two parts', () => {
assert.equal(ip.normalizeToLong('127.1'), 2130706433);
});

it('should correctly handle "127.0.1" as three parts', () => {
assert.equal(ip.normalizeToLong('127.0.1'), 2130706433);
});


it('should correctly handle hexadecimal notation "0x7f.0x0.0x0.0x1"', () => {
assert.equal(ip.normalizeToLong('0x7f.0x0.0x0.0x1'), 2130706433);
});

// Testing with fewer than 4 parts
it('should correctly handle "0x7f000001" as a single part', () => {
assert.equal(ip.normalizeToLong('0x7f000001'), 2130706433);
});

it('should correctly handle octal notation "010.0.0.01"', () => {
assert.equal(ip.normalizeToLong('010.0.0.01'), 134217729);
});

// Testing invalid inputs
it('should return -1 for an invalid address "256.100.50.25"', () => {
assert.equal(ip.normalizeToLong('256.100.50.25'), -1);
});

it('should return -1 for an address with invalid octal "019.0.0.1"', () => {
assert.equal(ip.normalizeToLong('019.0.0.1'), -1);
});

it('should return -1 for an address with invalid hex "0xGG.0.0.1"', () => {
assert.equal(ip.normalizeToLong('0xGG.0.0.1'), -1);
});

// Testing edge cases
it('should return -1 for an empty string', () => {
assert.equal(ip.normalizeToLong(''), -1);
});

it('should return -1 for a string with too many parts "192.168.0.1.100"', () => {
assert.equal(ip.normalizeToLong('192.168.0.1.100'), -1);
});
});

describe('isPrivate() method', () => {
it('should check if an address is localhost', () => {
assert.equal(ip.isPrivate('127.0.0.1'), true);
Expand Down Expand Up @@ -300,6 +351,10 @@ describe('IP library for node.js', () => {
assert.equal(ip.isPrivate('::1'), true);
assert.equal(ip.isPrivate('fe80::1'), true);
});

it('should correctly identify hexadecimal IP addresses like \'0x7f.1\' as private', () => {
assert.equal(ip.isPrivate('0x7f.1'), true);
});
});

describe('loopback() method', () => {
Expand Down Expand Up @@ -413,4 +468,42 @@ describe('IP library for node.js', () => {
assert.equal(ip.fromLong(4294967295), '255.255.255.255');
});
});

// IPv4 loopback in octal notation
it('should return true for octal representation "0177.0.0.1"', () => {
assert.equal(ip.isLoopback('0177.0.0.1'), true);
});

it('should return true for octal representation "0177.0.1"', () => {
assert.equal(ip.isLoopback('0177.0.1'), true);
});

it('should return true for octal representation "0177.1"', () => {
assert.equal(ip.isLoopback('0177.1'), true);
});

// IPv4 loopback in hexadecimal notation
it('should return true for hexadecimal representation "0x7f.0.0.1"', () => {
assert.equal(ip.isLoopback('0x7f.0.0.1'), true);
});

// IPv4 loopback in hexadecimal notation
it('should return true for hexadecimal representation "0x7f.0.1"', () => {
assert.equal(ip.isLoopback('0x7f.0.1'), true);
});

// IPv4 loopback in hexadecimal notation
it('should return true for hexadecimal representation "0x7f.1"', () => {
assert.equal(ip.isLoopback('0x7f.1'), true);
});

// IPv4 loopback as a single long integer
it('should return true for single long integer representation "2130706433"', () => {
assert.equal(ip.isLoopback('2130706433'), true);
});

// IPv4 non-loopback address
it('should return false for "192.168.1.1"', () => {
assert.equal(ip.isLoopback('192.168.1.1'), false);
});
});

0 comments on commit 32f468f

Please sign in to comment.