diff --git a/lib/inspector.js b/lib/inspector.js index b623d96b68c3f7..6d01c363dad5cd 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -18,6 +18,8 @@ const { ERR_INSPECTOR_NOT_WORKER, } = require('internal/errors').codes; +const { isLoopback } = require('internal/net'); + const { hasInspector } = internalBinding('config'); if (!hasInspector) throw new ERR_INSPECTOR_NOT_AVAILABLE(); @@ -171,6 +173,17 @@ function inspectorOpen(port, host, wait) { if (isUint32(port)) { validateInt32(port, 'port', 0, 65535); } + if (host && !isLoopback(host)) { + process.emitWarning( + 'Binding the inspector to a public IP with an open port is insecure, ' + + 'as it allows external hosts to connect to the inspector ' + + 'and perform a remote code execution attack. ' + + 'Documentation can be found at ' + + 'https://nodejs.org/api/cli.html#--inspecthostport', + 'SecurityWarning', + ); + } + open(port, host); if (wait) waitForDebugger(); diff --git a/lib/internal/net.js b/lib/internal/net.js index cc616000777e4d..3f4c6bd944c085 100644 --- a/lib/internal/net.js +++ b/lib/internal/net.js @@ -68,6 +68,22 @@ function makeSyncWrite(fd) { }; } +/** + * https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml + * https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml + * https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml + */ +function isLoopback(host) { + const hostLower = host.toLowerCase(); + + return ( + hostLower === 'localhost' || + hostLower.startsWith('127.') || + hostLower.startsWith('[::1]') || + hostLower.startsWith('[0:0:0:0:0:0:0:1]') + ); +} + module.exports = { kReinitializeHandle: Symbol('kReinitializeHandle'), isIP, @@ -75,4 +91,5 @@ module.exports = { isIPv6, makeSyncWrite, normalizedArgsSymbol: Symbol('normalizedArgs'), + isLoopback, }; diff --git a/test/parallel/test-inspector-host-warning.js b/test/parallel/test-inspector-host-warning.js new file mode 100644 index 00000000000000..2bcda645e29844 --- /dev/null +++ b/test/parallel/test-inspector-host-warning.js @@ -0,0 +1,16 @@ +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const inspector = require('inspector'); +inspector.open(0, '0.0.0.0', false); +common.expectWarning( + 'SecurityWarning', + 'Binding the inspector to a public IP with an open port is insecure, ' + + 'as it allows external hosts to connect to the inspector ' + + 'and perform a remote code execution attack. ' + + 'Documentation can be found at ' + + 'https://nodejs.org/api/cli.html#--inspecthostport' +); +inspector.close(); diff --git a/test/parallel/test-internal-net-isLoopback.js b/test/parallel/test-internal-net-isLoopback.js new file mode 100644 index 00000000000000..df63bfc262eaf6 --- /dev/null +++ b/test/parallel/test-internal-net-isLoopback.js @@ -0,0 +1,32 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('internal/net'); + +const loopback = [ + 'localhost', + '127.0.0.1', + '127.0.0.255', + '127.1.2.3', + '[::1]', + '[0:0:0:0:0:0:0:1]', +]; + +const loopbackNot = [ + 'example.com', + '192.168.1.1', + '10.0.0.1', + '255.255.255.255', + '[2001:db8::1]', + '[fe80::1]', + '8.8.8.8', +]; + +for (const address of loopback) { + assert.strictEqual(net.isLoopback(address), true); +} + +for (const address of loopbackNot) { + assert.strictEqual(net.isLoopback(address), false); +}