NodeRegistry - URL can be arbitrary dns resolvable names, IP's and even localhost or private subnets
As outlined in issue 6.9 the `NodeRegistry` allows anyone to register nodes with arbitrary URLs. The `url` is then used by `in3-server` or clients to connect to other nodes in the system. Signers can only be convicted if they sign wrong blockhashes. However, if they never provide any signatures they can stay in the registry for as long as they want and sabotage the network. The Registry implements an admin functionality that is available for the first year to remove misbehaving nodes (or spam entries) from the Registry. However, this is insufficient as an attacker might just re-register nodes after the minimum timeout they specify or spend some more finneys on registering more nodes. Depending on the eth-price this will be more or less profitable.\\nFrom an attackers perspective the `NodeRegistry` is a good source of information for reconnaissance, allows to de-anonymize and profile nodes based on dns entries or netblocks or responses to `in3_stats` (https://github.com/ConsenSys/slockit-in3-audit-2019-09/issues/49), makes a good list of target for DoS attacks on the system or makes it easy to exploit nodes for certain yet unknown security vulnerabilities.\\nSince nodes and potentially clients (not in scope) do not validate the rpc URL received from the `NodeRegistry` they will try to connect to whatever is stored in a nodes `url` entry.\\ncode/in3-server/src/chains/signatures.ts:L58-L75\\n```\\nconst config = nodes.nodes.find(\\_ => \\_.address.toLowerCase() === adr.toLowerCase())\\nif (!config) // TODO do we need to throw here or is it ok to simply not deliver the signature?\\n  throw new Error('The ' + adr + ' does not exist within the current registered active nodeList!')\\n\\n// get cache signatures and remaining blocks that have no signatures\\nconst cachedSignatures: Signature[] = []\\nconst blocksToRequest = blocks.filter(b => {\\n  const s = signatureCaches.get(b.hash) && false\\n  return s ? cachedSignatures.push(s) \\* 0 : true\\n})\\n\\n// send the sign-request\\nlet response: RPCResponse\\ntry {\\n  response = (blocksToRequest.length\\n    ? await handler.transport.handle(config.url, { id: handler.counter++ || 1, jsonrpc: '2.0', method: 'in3\\_sign', params: blocksToRequest })\\n    : { result: [] }) as RPCResponse\\n  if (response.error) {\\n```\\n\\nThis allows for a wide range of attacks not limited to:\\nAn attacker might register a node with an empty or invalid URL. The `in3-server` does not validate the URL and therefore will attempt to connect to the invalid URL, spending resources (cpu, file-descriptors, ..) to find out that it is invalid.\\nAn attacker might register a node with a URL that is pointing to another node's rpc endpoint and specify weights that suggest that it is capable of service a lot of requests to draw more traffic towards that node in an attempt to cause a DoS situation.\\nAn attacker might register a node for a http/https website at any port in an extortion attempt directed to website owners. The incubed network nodes will have to learn themselves that the URL is invalid and they will at least attempt to connect the website once.\\nAn attacker might update the node information in the `NodeRegistry` for a specific node every block, providing a new `url` (or a slightly different URLs issue 6.9) to avoid client/node URL blacklists.\\nAn attacker might provide IP addresses instead of DNS resolvable names with the `url` in an attempt to draw traffic to targets, avoiding canonicalization and blacklisting features.\\nAn attacker might provide a URL that points to private IP netblocks for IPv4 or IPv6 in various formats. Combined with the ability to ask another node to connect to an attacker defined `url` (via blockproof, signatures[] -> signer_address -> signer.url) this might allow an attacker to enumerate services in the LAN of node operators.\\nAn attacker might provide the loopback IPv4, IPv6 or resolvable name as the URL in an attempt to make the node connect to local loopback services (service discovery, bypassing authentication for some local running services - however this is very limited to the requests nodes may execute).\\nURLs may be provided in various formats: resolvable dns names, IPv4, IPv6 and depending on the http handler implementation even in Decimal, Hex or Octal form (i.e. http://2130706433/)\\nA valid DNS resolvable name might point to a localhost or private IP netblock.\\nSince none of the rpc endpoints provide signatures they cannot be convicted or removed (unless the `unregisterKey` does it within the first year. However, that will not solve the problem that someone can re-register the same URLs over and over again)
Resolution\\nThis issue has been addressed with the following commits:\\nIt is a design decision to base the Node registry on URLs (DNS resolvable names). This has the implications outlined in this issue and they cannot easily be mitigated. Adding a delay until nodes can be used after registration only delays the problem. Assuming that an entity curates the registry or a whitelist is in place centralizes the system. Adding DNS record verification still allows an owner of a DNS entry to point its name to any IP address they would like it to point to. It certainly makes it harder to add RPC URLs with DNS names that are not in control of the attacker but it also adds a whole lot more complexity to the system (including manual steps performed by the node operator). In the end, the system allows IP based URLs in the registry which cannot be used for DNS validation.\\nIt is a fundamental design decision of the system architecture to allow rpc urls in the Node Registry, therefore this issue can only be partially mitigated unless the system design is reworked. It is therefore suggested to add checks to both the registry contract (coarse validation to avoid adding invalid urls) and node implementations (rigorous validation of URL's and resolved IP addresses) and filter out any potentially harmful destinations.
```\\nconst config = nodes.nodes.find(\\_ => \\_.address.toLowerCase() === adr.toLowerCase())\\nif (!config) // TODO do we need to throw here or is it ok to simply not deliver the signature?\\n  throw new Error('The ' + adr + ' does not exist within the current registered active nodeList!')\\n\\n// get cache signatures and remaining blocks that have no signatures\\nconst cachedSignatures: Signature[] = []\\nconst blocksToRequest = blocks.filter(b => {\\n  const s = signatureCaches.get(b.hash) && false\\n  return s ? cachedSignatures.push(s) \\* 0 : true\\n})\\n\\n// send the sign-request\\nlet response: RPCResponse\\ntry {\\n  response = (blocksToRequest.length\\n    ? await handler.transport.handle(config.url, { id: handler.counter++ || 1, jsonrpc: '2.0', method: 'in3\\_sign', params: blocksToRequest })\\n    : { result: [] }) as RPCResponse\\n  if (response.error) {\\n```\\n