Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTobias Nießen <tniessen@tnie.de>2022-09-25 15:34:05 +0300
committerBeth Griggs <bethanyngriggs@gmail.com>2022-11-02 01:56:04 +0300
commit9ffddd7098751cf888c611edac654607d7548c6d (patch)
treed3ecb564be1159f479ddaab392b912d70adecc15 /src
parent7051ba4501883955daa6bf8e442fef0c32aa5ea3 (diff)
inspector: harden IP address validation again
Use inet_pton() to parse IP addresses, which restricts IP addresses to a small number of well-defined formats. In particular, octal and hexadecimal number formats are not allowed, and neither are leading zeros. Also explicitly reject 0.0.0.0/8 and ::/128 as non-routable. Refs: https://hackerone.com/reports/1710652 CVE-ID: CVE-2022-43548 PR-URL: https://github.com/nodejs-private/node-private/pull/354 Reviewed-by: Michael Dawson <midawson@redhat.com> Reviewed-by: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-by: Rich Trott <rtrott@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/inspector_socket.cc78
1 files changed, 62 insertions, 16 deletions
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
index 8cabdaec282..a28bd557c8a 100644
--- a/src/inspector_socket.cc
+++ b/src/inspector_socket.cc
@@ -6,6 +6,7 @@
#include "openssl/sha.h" // Sha-1 hash
+#include <algorithm>
#include <cstring>
#include <map>
@@ -162,25 +163,70 @@ static std::string TrimPort(const std::string& host) {
}
static bool IsIPAddress(const std::string& host) {
- if (host.length() >= 4 && host.front() == '[' && host.back() == ']')
- return true;
- if (host.front() == '0') return false;
- uint_fast16_t accum = 0;
- uint_fast8_t quads = 0;
- bool empty = true;
- auto endOctet = [&accum, &quads, &empty](bool final = false) {
- return !empty && accum <= 0xff && ++quads <= 4 && final == (quads == 4) &&
- (empty = true) && !(accum = 0);
- };
- for (char c : host) {
- if (isdigit(c)) {
- if ((accum = (accum * 10) + (c - '0')) > 0xff) return false;
- empty = false;
- } else if (c != '.' || !endOctet()) {
+ // TODO(tniessen): add CVEs to the following bullet points
+ // To avoid DNS rebinding attacks, we are aware of the following requirements:
+ // * the host name must be an IP address,
+ // * the IP address must be routable, and
+ // * the IP address must be formatted unambiguously.
+
+ // The logic below assumes that the string is null-terminated, so ensure that
+ // we did not somehow end up with null characters within the string.
+ if (host.find('\0') != std::string::npos) return false;
+
+ // All IPv6 addresses must be enclosed in square brackets, and anything
+ // enclosed in square brackets must be an IPv6 address.
+ if (host.length() >= 4 && host.front() == '[' && host.back() == ']') {
+ // INET6_ADDRSTRLEN is the maximum length of the dual format (including the
+ // terminating null character), which is the longest possible representation
+ // of an IPv6 address: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd
+ if (host.length() - 2 >= INET6_ADDRSTRLEN) return false;
+
+ // Annoyingly, libuv's implementation of inet_pton() deviates from other
+ // implementations of the function in that it allows '%' in IPv6 addresses.
+ if (host.find('%') != std::string::npos) return false;
+
+ // Parse the IPv6 address to ensure it is syntactically valid.
+ char ipv6_str[INET6_ADDRSTRLEN];
+ std::copy(host.begin() + 1, host.end() - 1, ipv6_str);
+ ipv6_str[host.length()] = '\0';
+ unsigned char ipv6[sizeof(struct in6_addr)];
+ if (uv_inet_pton(AF_INET6, ipv6_str, ipv6) != 0) return false;
+
+ // The only non-routable IPv6 address is ::/128. It should not be necessary
+ // to explicitly reject it because it will still be enclosed in square
+ // brackets and not even macOS should make DNS requests in that case, but
+ // history has taught us that we cannot be careful enough.
+ // Note that RFC 4291 defines both "IPv4-Compatible IPv6 Addresses" and
+ // "IPv4-Mapped IPv6 Addresses", which means that there are IPv6 addresses
+ // (other than ::/128) that represent non-routable IPv4 addresses. However,
+ // this translation assumes that the host is interpreted as an IPv6 address
+ // in the first place, at which point DNS rebinding should not be an issue.
+ if (std::all_of(ipv6, ipv6 + sizeof(ipv6), [](auto b) { return b == 0; })) {
return false;
}
+
+ // It is a syntactically valid and routable IPv6 address enclosed in square
+ // brackets. No client should be able to misinterpret this.
+ return true;
}
- return endOctet(true);
+
+ // Anything not enclosed in square brackets must be an IPv4 address. It is
+ // important here that inet_pton() accepts only the so-called dotted-decimal
+ // notation, which is a strict subset of the so-called numbers-and-dots
+ // notation that is allowed by inet_aton() and inet_addr(). This subset does
+ // not allow hexadecimal or octal number formats.
+ unsigned char ipv4[sizeof(struct in_addr)];
+ if (uv_inet_pton(AF_INET, host.c_str(), ipv4) != 0) return false;
+
+ // The only strictly non-routable IPv4 address is 0.0.0.0, and macOS will make
+ // DNS requests for this IP address, so we need to explicitly reject it. In
+ // fact, we can safely reject all of 0.0.0.0/8 (see Section 3.2 of RFC 791 and
+ // Section 3.2.1.3 of RFC 1122).
+ // Note that inet_pton() stores the IPv4 address in network byte order.
+ if (ipv4[0] == 0) return false;
+
+ // It is a routable IPv4 address in dotted-decimal notation.
+ return true;
}
// Constants for hybi-10 frame format.