diff --git a/changelog.txt b/changelog.txt index ac0bf00..d409588 100644 --- a/changelog.txt +++ b/changelog.txt @@ -29,6 +29,15 @@ - fixed `attachmentfs()` in the mail: the file of the storage is read as a Buffer and the message is completed (it threw a `TypeError` and the SMTP connection stayed open) - fixed `attachment()` with a `Buffer` in the mail: the Base64 of the whole Buffer is split into lines (the bytes were split into pieces of 68 bytes and each one got its own padding) - fixed `@{hostname()}` view engine command +- security: hardened the hostnames against the Node.js security release of 2026-10-21 (patched in Node.js 22.23.4, 24.22.1 and 26.12.1) ; the framework does not require a specific version of Node.js, the hostnames are normalized and validated in the framework itself + - CVE-2026-105482: `dns.resolve*()` truncates a name at a NUL byte (`attacker.example\0.trusted.example`) + - CVE-2026-105483, CVE-2026-106069, CVE-2026-106072: `tls.checkServerIdentity()` with IPv4 literals written with non-ASCII digits, leading zeros or in a non-canonical form (`0x7f.0.0.1`, `127.1`, `2130706433`) +- added `U.validhost(host)` that accepts only plain hostnames and canonical IP literals +- added `U.tohost(host)` that converts a hostname to the canonical ASCII form (punycode, dotted IPv4) and returns an empty string for an invalid hostname +- fixed `U.resolve()` (used by `REQUEST()`): an invalid hostname gives an error before the DNS query +- fixed `U.connect()`: the `host` is normalized and an invalid hostname gives the `Invalid hostname` error +- fixed the SMTP `server` in the mail: the hostname is normalized (international names to punycode, IPv4 to the canonical form) and an invalid hostname gives the `Invalid SMTP server hostname.` error +- note: the issue CVE-2026-105481 (unsolicited data on a reused keep-alive socket, `http.Agent`) is fixed only by updating Node.js ; the Brotli issue CVE-2026-111497 does not affect the framework (only `gzip` and `deflate` are decompressed) ======================== 0.0.19 diff --git a/mail.js b/mail.js index a17f838..4186328 100755 --- a/mail.js +++ b/mail.js @@ -477,8 +477,13 @@ Mailer.send = function(opt, messages, callback) { if (opt.secure && !opt.port) opt.port = 465; - if (!opt.server) { - const err = new Error('No SMTP server configuration.'); + const server = opt.server ? F.TUtils.tohost(opt.server) : ''; + + if (server && server !== opt.server) + opt = Object.assign({}, opt, { server: server }); + + if (!server) { + const err = new Error(opt.server ? 'Invalid SMTP server hostname.' : 'No SMTP server configuration.'); let iscallback = false; if (callback) { diff --git a/utils.js b/utils.js index 49a94b8..6f675f9 100755 --- a/utils.js +++ b/utils.js @@ -489,12 +489,41 @@ exports.toURLEncode = function(value) { return builder.length ? builder.join('&') : ''; }; +const REG_HOST_NUMERIC = /^(0x[0-9a-f]+|[0-9]+)(\.(0x[0-9a-f]+|[0-9]+))*$/i; +const REG_HOST_IPV4 = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/; + +// Accepts plain hostnames and canonical IP literals only. It rejects NUL bytes, non-ASCII characters +// and non-canonical IPv4 forms (0127.0.0.1, 0x7f.1, 2130706433, 127.1) which Node.js, certificate +// checks and the OS can interpret differently. +exports.validhost = function(host) { + + if (!host || typeof(host) !== 'string' || /[^\x21-\x7e]/.test(host)) + return false; + + return REG_HOST_NUMERIC.test(host) ? REG_HOST_IPV4.test(host) : true; +}; + +// Converts a host to its canonical ASCII form (punycode for international names, canonical dotted IPv4) +// and returns an empty string when the host is not valid. +exports.tohost = function(host) { + + if (!host || typeof(host) !== 'string') + return ''; + + if (host.indexOf(':') === -1) + host = F.Url.domainToASCII(host); + + return exports.validhost(host) ? host : ''; +}; + exports.resolve = function(url, callback, param) { let uri; try { uri = exports.parseURI(url); + if (!exports.validhost(uri.hostname)) + throw new Error('Invalid hostname'); } catch (e) { callback(e); return; @@ -5921,6 +5950,15 @@ exports.connect = function(opt, callback) { delete opt.tls; + if (opt.host) { + opt.host = exports.tohost(opt.host); + if (!opt.host) { + meta.destroy = meta.close = NOOP; + callback && setImmediate(callback, new Error('Invalid hostname')); + return meta; + } + } + var close = function() { if (meta.socket1) {