Skip to content

Commit de49c91

Browse files
jasnelladuh95
authored andcommitted
tls: fix ip normalization regression
The regression is a low-severity bug that's not easy to exploit. Basically it just means the hostname check can be bypassed. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: nodejs-private/node-private#985 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> CVE-ID: CVE-2026-105483
1 parent 843a162 commit de49c91

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

‎lib/tls.js‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,14 +456,26 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
456456
let valid = false;
457457
let reason = 'Unknown reason';
458458

459-
// An IP literal must not be IDNA-normalized: domainToASCII() returns '' for
460-
// an IPv6 literal (it is not a domain), so matching against the normalized
461-
// host would skip IP-SAN matching for IPv6 entirely. Match IP hosts against
462-
// the original hostname (net.isIP() rejects non-ASCII, so there is no IDNA
463-
// confusion to guard against here); the normalized form is kept for the
464-
// DNS-name path below.
459+
// Decide whether the host is an IP address by checking both forms of it,
460+
// because neither form is enough on its own:
461+
//
462+
// - The original hostname is needed for IPv6. domainToASCII() returns '' for
463+
// an IPv6 literal such as '::1' (it is not a domain).
464+
// - The normalized hostname is needed for hosts that net.isIP() rejects but
465+
// that normalize to an IPv4 address: '\uFF11\uFF12\uFF17.0.0.1' (fullwidth
466+
// digits), '127\u30020.0.1' (ideographic full stop) and '127.1' all become
467+
// '127.0.0.1'. The resolver treats them as that address too, so they must
468+
// be matched against IP SANs. Letting them fall through to the DNS-name
469+
// path would match an IP address against DNS names and the CN instead.
470+
let ip = '';
465471
if (net.isIP(hostname)) {
466-
valid = ips.includes(canonicalizeIP(hostname));
472+
ip = hostname;
473+
} else if (net.isIP(hostnameASCIIWithoutFQDN)) {
474+
ip = hostnameASCIIWithoutFQDN;
475+
}
476+
477+
if (ip) {
478+
valid = ips.includes(canonicalizeIP(ip));
467479
if (!valid) {
468480
reason =
469481
`IP: ${hostname} is not in the cert's list: ` + ips.join(', ');

‎test/parallel/test-tls-check-server-identity.js‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,47 @@ const tests = [
394394
},
395395
error: 'IP: 127.0.0.1 is not in the cert\'s list: '
396396
},
397+
// A host that net.isIP() rejects but that normalizes to an IPv4 address is
398+
// an IP address, not a DNS name: it is matched against "IP Address:" SANs,
399+
// never against DNS names or the CN.
400+
{
401+
host: '\uFF11\uFF12\uFF17.0.0.1', // "127.0.0.1" with fullwidth digits.
402+
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
403+
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
404+
},
405+
{
406+
host: '\uFF11\uFF12\uFF17.0.0.1',
407+
cert: { subjectaltname: 'DNS:*.0.0.1', subject: {} },
408+
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
409+
},
410+
{
411+
host: '\uFF11\uFF12\uFF17.0.0.1',
412+
cert: { subject: { CN: '127.0.0.1' } },
413+
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
414+
},
415+
{
416+
host: '\uFF11\uFF12\uFF17.0.0.1',
417+
cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
418+
},
419+
{
420+
host: '\uFF11\uFF12\uFF17.0.0.1',
421+
cert: { subjectaltname: 'IP Address:127.0.0.2', subject: {} },
422+
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: 127.0.0.2'
423+
},
424+
{
425+
host: '127\u30020.0.1', // "127.0.0.1" with an ideographic full stop.
426+
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
427+
error: 'IP: 127\u30020.0.1 is not in the cert\'s list: '
428+
},
429+
{
430+
host: '127.1',
431+
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
432+
error: 'IP: 127.1 is not in the cert\'s list: '
433+
},
434+
{
435+
host: '127.1',
436+
cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
437+
},
397438
{
398439
host: 'localhost', cert: {
399440
subjectaltname: 'DNS:a.com',

0 commit comments

Comments
 (0)