
node-ip का अनुरक्षित फोर्क जिसमें अनपैच्ड SSRF एडवाइज़री (CVE-2024-29415) को ठीक कर दिया गया है
ip का अनुरक्षित फोर्क — Node.js के लिए IP पता उपयोगिताएँ — जिसमें अनपैच्ड SSRF सलाहकार को ठीक किया गया है।
मूल पैकेज के ~38M डाउनलोड/माह हैं, फरवरी 2024 से कोई रिलीज़ नहीं हुई है, और एक HIGH-गंभीरता सलाहकार (CVE-2024-29415 / GHSA-2p57-rm9w-gvfp) है जो हर प्रकाशित संस्करण को प्रभावित करता है और हर इंस्टॉल पर npm audit को विफल कर देता है। यह फोर्क regexes को पैच करने के बजाय मूल कारण को ठीक करता है।
npm install @bybrave/ip2
// CommonJS — drop-in
const ip = require('@bybrave/ip2');
// ESM — named imports
import { isPrivate, isPublic, cidrSubnet } from '@bybrave/ip2';
// TypeScript types built in
मूल isPrivate() / isPublic() / isLoopback() कच्चे इनपुट स्ट्रिंग पर regexes चलाते थे। किसी पते की कोई भी वर्तनी जिसकी regexes ने अपेक्षा नहीं की थी — ऑक्टल या हेक्स ऑक्टेट, संक्षिप्त IPv4, असंपीड़ित IPv6 — चुपचाप सार्वजनिक के रूप में वर्गीकृत की जाती थी:
// original [email protected] — all of these are loopback/private, all reported PUBLIC:
ip.isPublic('127.1') // true 💥
ip.isPublic('012.1.2.3') // true 💥 (octal 10.1.2.3)
ip.isPublic('01200034567') // true 💥 (integer 10.0.14.119)
ip.isPublic('000:0:0000::01') // true 💥 (that's ::1)
यदि वे उत्तर आउटगोइंग अनुरोधों की रक्षा करते हैं, तो यह एक SSRF छेद है (CVE-2024-29415, CVE-2023-42282 का अपूर्ण फिक्स)।
यह फोर्क:
net.isIP के समान व्याकरण, साथ ही IPv6 ज़ोन IDs)। वर्गीकरण बाइट्स पर चलता है, कभी स्ट्रिंग्स पर नहीं।127.1, 0x7f.1, 0177.0.0.1, 2130706433) का अनुमान लगाने के बजाय throw करती हैं। बंद में विफल हों, खुले में नहीं। यदि आपको लेगेसी नोटेशन स्वीकार करने की आवश्यकता है, तो पहले स्पष्ट रूप से सामान्यीकृत करें: ip.fromLong(ip.normalizeToLong(addr))।isPrivate() का अब अर्थ है "वैश्विक रूप से पहुँच योग्य नहीं" — Python के ipaddress के समान परंपरा। यह CGNAT (100.64/10), TEST-NETs, बेंचमार्किंग, मल्टीकास्ट, आरक्षित, ULA, दस्तावेज़ीकरण रेंज और बहुत कुछ जोड़ता है — नीचे तालिका देखें।::ffff:a.b.c.d (v4-mapped), 64:ff9b::a.b.c.d (NAT64) और 2002:xxxx:xxxx:: (6to4) उनके अंदर के IPv4 पते के अनुसार निजी/सार्वजनिक होते हैं।// @bybrave/ip2
ip.isPublic('127.1') // throws Error: Invalid ip address: 127.1
ip.isPrivate('000:0:0000::01') // true (parsed as ::1)
ip.isLoopback('::fFFf:127.0.0.1')// true
ip.isPublic('64:ff9b::8.8.8.8') // true (NAT64 of a public address)
ip.isPrivate('100.64.0.1') // true (CGNAT — original said public)
[email protected] पर सभी फिक्स| ठीक किया गया | मूल समस्या |
|---|---|
| CVE-2024-29415 / GHSA-2p57-rm9w-gvfp — गैर-कैनोनिकल पतों के माध्यम से SSRF | #136, #150, #153, #158 |
isPrivate() CGNAT, TEST-NETs, बेंचमार्किंग, मल्टीकास्ट, आरक्षित, दस्तावेज़ीकरण रेंज को छोड़ देता है | — |
cidrSubnet(...).contains() IPv4-mapped IPv6 पतों के लिए false लौटाता है | #104 |
isV6Format() IPv4 पतों के लिए true लौटाता है | #67 |
isV4Format() ≥ 256 वाले ऑक्टेट स्वीकार करता है | #105 |
address('public') निजी पते लौटाता था और इसके विपरीत (फ़िल्टर उलटा था) | — |
loopback('ipv6') fe80::1 लौटाता था, जो लिंक-लोकल है, लूपबैक नहीं → अब ::1 | — |
cidr()/cidrSubnet() अमान्य प्रीफ़िक्स लंबाई (/33, /x) के लिए चुपचाप कचरा उत्पन्न करते थे → अब throw करते हैं | — |
address(name) अज्ञात इंटरफ़ेस नामों के लिए TypeError के साथ क्रैश होता था → अब undefined लौटाता है | — |
cidrSubnet()/subnet() IPv6 के लिए गलत उत्तर देते थे (/48 में एक पड़ोसी /48 शामिल था) → वास्तविक IPv6 सबनेट गणित | #89, #110 |
ब्राउज़र के लिए बंडलिंग os पर विफल होती थी (webpack 5) → browser फ़ील्ड इसे दूर मैप करता है | #127, #132 |
TypeScript प्रकार अंतर्निहित (कोई @types/ip आवश्यक नहीं), ESM नामित निर्यात | — |
- const ip = require('ip');
+ const ip = require('@bybrave/ip2');
API सतह समान है; व्यवहारिक अंतर सुरक्षा और शुद्धता के फिक्स हैं — अपग्रेड करने से पहले नीचे दिए गए ब्रेकिंग परिवर्तन देखें।
संस्करण 3.0.0 क्योंकि कठोरता ही फिक्स है — यदि आपने इस लाइब्रेरी को गैर-कैनोनिकल स्ट्रिंग्स दिए, तो v2 आपको गलत उत्तर दे रहा था, संगत उत्तर नहीं:
isPrivate / isPublic / isLoopback / toBuffer / toLong विकृत या गैर-कैनोनिकल इनपुट (ऑक्टल/हेक्स ऑक्टेट, 127.1, पूर्णांक स्ट्रिंग्स) पर throw करते हैं। यदि आपको लेगेसी नोटेशन स्वीकार करने ही हैं तो पहले normalizeToLong() का उपयोग करें।isLoopback('fe80::1') अब false है (लिंक-लोकल ≠ लूपबैक; अभी भी isPrivate)। isLoopback('::') अब false है (अनिर्दिष्ट ≠ लूपबैक; अभी भी isPrivate)।loopback('ipv6') 'fe80::1' के बजाय '::1' लौटाता है।isPrivate() अधिक रेंजों के लिए true लौटाता है (CGNAT, TEST-NETs, मल्टीकास्ट, …) — इसका अब अर्थ है "वैश्विक रूप से पहुँच योग्य नहीं"।address('public') अब वास्तव में सार्वजनिक पते लौटाता है। यदि आप LAN पता प्राप्त करने के लिए उलटे व्यवहार पर निर्भर थे, तो address('private') का उपयोग करें — वही जो यह आपको दे रहा था।मूल जो कुछ भी निर्यात करता है, वह सब, साथ ही isValid():
ip.address('public', 'ipv6') // address of a network interface
ip.isPrivate('10.0.0.1') // true — not globally reachable
ip.isPublic('8.8.8.8') // true
ip.isLoopback('127.8.8.8') // true
ip.isValid('300.1.2.3') // false (never throws)
ip.isV4Format('192.168.0.1') // true
ip.isV6Format('2001:db8::1') // true
ip.isEqual('::ffff:7f00:1', '127.0.0.1') // true
ip.toBuffer('127.0.0.1') // Buffer([127, 0, 0, 1])
ip.toString(buf, offset, length) // '127.0.0.1'
ip.toLong('127.0.0.1') // 2130706433
ip.fromLong(2130706433) // '127.0.0.1'
ip.normalizeToLong('127.1') // 2130706433 (lenient, -1 on error)
ip.fromPrefixLen(24) // '255.255.255.0'
ip.mask('192.168.1.134', '255.255.255.0') // '192.168.1.0'
ip.cidr('192.168.1.134/26') // '192.168.1.128'
ip.not('255.255.255.0') // '0.0.0.255'
ip.or('0.0.0.255', '192.168.1.10') // '192.168.1.255'
const s = ip.cidrSubnet('192.168.1.134/26')
s.networkAddress // '192.168.1.128'
s.firstAddress // '192.168.1.129'
s.lastAddress // '192.168.1.190'
s.broadcastAddress // '192.168.1.191'
s.subnetMaskLength // 26
s.numHosts // 62
s.contains('::ffff:192.168.1.180') // true (v4-mapped handled)