
فرع مُحافظ عليه من node-ip مع إصلاح الاستشارة غير المُصححة لـ SSRF (CVE-2024-29415)
شوكة مدعومة من ip — أدوات عناوين IP لـ Node.js — مع إصلاح النصيحة الأمنية SSRF غير المصححة.
الحزمة الأصلية لديها حوالي 38 مليون تنزيل شهريًا، لم يتم إصدار تحديث منذ فبراير 2024، ونصيحة أمنية عالية الخطورة (CVE-2024-29415 / GHSA-2p57-rm9w-gvfp) تؤثر على كل إصدار منشور وتجعل npm audit يفشل في كل تثبيت. تقوم هذه الشوكة بإصلاح السبب الجذري بدلاً من تصحيح التعبيرات النمطية.
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() الأصلية تقوم بتشغيل تعبيرات نمطية على سلسلة الإدخال الخام. أي تهجئة لعنوان لم تتوقعها التعبيرات النمطية - ثماني أو سداسي عشري، 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). التصنيف يعمل على البايتات، وليس على السلاسل.127.1, 0x7f.1, 0177.0.0.1, 2130706433) تطرح استثناءً بدلاً من التخمين. فشل مغلق، وليس مفتوح. إذا كنت بحاجة لقبول ترميزات قديمة، قم بالتطبيع صراحة أولاً: ip.fromLong(ip.normalizeToLong(addr)).isPrivate() الآن تعني "غير قابل للوصول عالميًا" — نفس اصطلاح ipaddress في Python. هذا يضيف CGNAT (100.64/10)، TEST-NETs، اختبارات الأداء، البث المتعدد، المحجوز، ULA، نطاقات التوثيق والمزيد — انظر الجدول أدناه.::ffff:a.b.c.d (معين إلى v4)، (NAT64) و (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]- const ip = require('ip');
+ const ip = require('@bybrave/ip2');
واجهة API هي نفسها؛ الاختلافات السلوكية هي إصلاحات الأمان والصحة — انظر التغييرات الجذرية أدناه قبل الترقية.
الإصدار 3.0.0 لأن الصرامة هي الإصلاح — إذا كنت تغذي هذه المكتبة بسلاسل غير قانونية، كان الإصدار 2 يعطيك إجابات خاطئة، وليس متوافقة:
isPrivate / isPublic / isLoopback / toBuffer / toLong تطرح استثناءً على إدخال مشوه أو غير قانوني (ثمانيات ثمانية/سداسية عشرية، 127.1، سلاسل أعداد صحيحة). استخدم normalizeToLong() أولاً إذا كان يجب عليك قبول ترميزات قديمة.isLoopback('fe80::1') الآن false (ارتباط محلي ≠ حلقة محلية؛ لا تزال isPrivate). isLoopback('::') الآن false (غير محدد ≠ حلقة محلية؛ لا تزال isPrivate).loopback('ipv6') ترجع بدلاً من .كل شيء تصدره الأصلية، بالإضافة إلى 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)
IPv4: 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10 (CGNAT)، 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 (بما في ذلك البث).
IPv6: ::/96 (غير محدد، حلقة محلية، متوافق مع v4)، 64:ff9b:1::/48, 100::/64, 2001::/23, 2001:db8::/32, 3fff::/20, 5f00::/16, fc00::/7, fe80::/10, fec0::/10, ff00::/8. نطاقات الانتقال ::ffff:0:0/96, 64:ff9b::/96 و 2002::/16 تتبع عنوان IPv4 المضمن فيها.
إذا وفرت لك هذه الحزمة وقتًا، يمكنك دعم الصيانة:
Bitcoin (BTC): bc1q37557q5jpeaxqydzwvf3jgj7zhnfpn2td3q40q
MIT. مبني على node-ip بواسطة Fedor Indutny.
64:ff9b::a.b.c.d2002:xxxx:xxxx::| الإصلاح | المشكلة الأصلية |
|---|
| CVE-2024-29415 / GHSA-2p57-rm9w-gvfp — SSRF عبر عناوين غير قانونية | #136, #150, #153, #158 |
isPrivate() تفتقد CGNAT، TEST-NETs، اختبارات الأداء، البث المتعدد، المحجوز، نطاقات التوثيق | — |
cidrSubnet(...).contains() ترجع false لعناوين IPv6 المعينة إلى IPv4 | #104 |
isV6Format() ترجع true لعناوين IPv4 | #67 |
isV4Format() تقبل ثمانيات ≥ 256 | #105 |
address('public') كانت ترجع عناوين خاصة والعكس صحيح (المرشح مقلوب) | — |
loopback('ipv6') كانت ترجع fe80::1، وهو ارتباط محلي وليس حلقة محلية → الآن ::1 | — |
cidr()/cidrSubnet() كانت تنتج بصمت بيانات غير صالحة لأطوال بادئة غير صحيحة (/33, /x) → الآن تطرح استثناءً | — |
address(name) كانت تتعطل مع TypeError لأسماء واجهة غير معروفة → الآن ترجع undefined | — |
أنواع TypeScript مدمجة (لا حاجة لـ @types/ip)، تصديرات ESM مسماة | — |
'::1''fe80::1'isPrivate() ترجع true لنطاقات أكثر (CGNAT، TEST-NETs، البث المتعدد، …) — الآن تعني "غير قابل للوصول عالميًا".address('public') الآن ترجع فعلياً عناوين عامة. إذا كنت تعتمد على السلوك المقلوب للحصول على عنوان شبكة محلية، استخدم address('private') — هذا ما كانت تعطيك إياه.