From e6ab1b989474549fc2c75cdd24b2cb72813fb91f Mon Sep 17 00:00:00 2001 From: Marco Ermini Date: Mon, 2 Feb 2026 09:08:44 +0100 Subject: [PATCH] Fix CVE-2024-29415: SSRF bypass via IPv4 shorthand notation The isLoopback() function failed to detect IPv4 shorthand loopback addresses like "127.1" and "127.0.1", allowing SSRF bypass. Fixed by normalizing all IPv4 formats using normalizeToLong() before checking if the first octet equals 127. Added 8 tests for CVE-2024-29415 coverage. --- lib/ip.js | 31 +++++++++++++++++++++---------- test/api-test.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/lib/ip.js b/lib/ip.js index 9022443..1553db7 100644 --- a/lib/ip.js +++ b/lib/ip.js @@ -338,18 +338,29 @@ 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)); + // Check IPv6 loopback addresses first + if (/:/.test(addr)) { + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr) + || /^fe80::1$/i.test(addr) + || /^::1$/.test(addr) + || /^::$/.test(addr); } - return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/ - .test(addr) - || /^0177\./.test(addr) - || /^0x7f\./i.test(addr) - || /^fe80::1$/i.test(addr) - || /^::1$/.test(addr) - || /^::$/.test(addr); + // For IPv4 addresses, normalize to handle all formats: + // - Standard: 127.0.0.1 + // - Shorthand: 127.1, 127.0.1 + // - Octal: 0177.0.0.1, 0177.1 + // - Hex: 0x7f.0.0.1, 0x7f.1 + // - Long integer: 2130706433 + const ipLong = ip.normalizeToLong(addr); + if (ipLong < 0) { + return false; + } + + // Loopback range is 127.0.0.0/8 (127.0.0.0 - 127.255.255.255) + // In long format: 2130706432 (127.0.0.0) to 2147483647 (127.255.255.255) + // First octet check: (ipLong >>> 24) === 127 + return (ipLong >>> 24) === 127; }; ip.loopback = function (family) { diff --git a/test/api-test.js b/test/api-test.js index 0db838d..63b1d84 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -506,4 +506,41 @@ describe('IP library for node.js', () => { it('should return false for "192.168.1.1"', () => { assert.equal(ip.isLoopback('192.168.1.1'), false); }); + + // CVE-2024-29415: SSRF bypass via IPv4 shorthand notation + describe('CVE-2024-29415 - IPv4 shorthand loopback detection', () => { + it('should detect "127.1" as loopback (shorthand for 127.0.0.1)', () => { + assert.equal(ip.isLoopback('127.1'), true); + }); + + it('should detect "127.0.1" as loopback (shorthand for 127.0.0.1)', () => { + assert.equal(ip.isLoopback('127.0.1'), true); + }); + + it('should detect "127.1" as private', () => { + assert.equal(ip.isPrivate('127.1'), true); + }); + + it('should detect "127.0.1" as private', () => { + assert.equal(ip.isPrivate('127.0.1'), true); + }); + + // Ensure non-loopback shorthand is not falsely detected + it('should not detect "192.1" as loopback', () => { + assert.equal(ip.isLoopback('192.1'), false); + }); + + it('should not detect "10.1" as loopback', () => { + assert.equal(ip.isLoopback('10.1'), false); + }); + + // Edge cases at loopback range boundaries + it('should not detect "126.255.255.255" as loopback', () => { + assert.equal(ip.isLoopback('126.255.255.255'), false); + }); + + it('should not detect "128.0.0.0" as loopback', () => { + assert.equal(ip.isLoopback('128.0.0.0'), false); + }); + }); });