CVE-2023-37262: Server-Side Request Forgery (SSRF) (CWE-918)
value: CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles, and more to the game. Prior to versions 1.20.1-1.106.0, 1.19.4-1.106.0, 1.19.2-1.101.3, 1.18.2-1.101.3, and 1.16.5-1.101.3, if the cc-tweaked plugin is running on a Minecraft server hosted on a popular cloud hosting providers, like AWS, GCP, and Azure, those metadata services API endpoints are not forbidden (aka "blacklisted") by default. As such, any player can gain access to sensitive information exposed via those metadata servers, potentially allowing them to pivot or privilege escalate into the hosting provider. Versions 1.20.1-1.106.0, 1.19.4-1.106.0, 1.19.2-1.101.3, 1.18.2-1.101.3, and 1.16.5-1.101.3 contain a fix for this issue.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
CVSS v3 base score: 8.8
Published: 2023-07-07T21:15Z
Repository: https://github.com/cc-tweaked/CC-Tweaked
Language: Java
Files changed: projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java, projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java
Commit date: 2023-07-06 23:16:22 +0100
Commit message: Tighten up the $private HTTP rule

 - Block multicast and the fd00::/8 address ranges.
 - Block several cloud metadata providers which sit outside the standard
   address ranges.

Diff: diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java
index e9dae1d8..1dfd0981 100644
--- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java
+++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java
@@ -4,13 +4,17 @@
 
 package dan200.computercraft.core.apis.http.options;
 
 import com.google.common.net.InetAddresses;
 
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Set;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * A predicate on an address. Matches against a domain and an ip address.
  *
  * @see AddressRule#apply(Iterable, String, InetSocketAddress) for the actual handling of this rule.
@@ -105,15 +109,38 @@ public boolean matches(InetAddress socketAddress) {
     }
 
     final class PrivatePattern implements AddressPredicate {
         static final PrivatePattern INSTANCE = new PrivatePattern();
 
+        private static final Set<InetAddress> additionalAddresses = Arrays.stream(new String[]{
+            // Block various cloud providers internal IPs.
+            "100.100.100.200", // Alibaba
+            "192.0.0.192", // Oracle
+        }).map(InetAddresses::forString).collect(Collectors.toUnmodifiableSet());
+
         @Override
         public boolean matches(InetAddress socketAddress) {
-            return socketAddress.isAnyLocalAddress()
-                || socketAddress.isLoopbackAddress()
-                || socketAddress.isLinkLocalAddress()
-                || socketAddress.isSiteLocalAddress();
+            return
+                socketAddress.isAnyLocalAddress()      // 0.0.0.0, ::0
+                || socketAddress.isLoopbackAddress()   // 127.0.0.0/8, ::1
+                || socketAddress.isLinkLocalAddress()  // 169.254.0.0/16, fe80::/10
+                || socketAddress.isSiteLocalAddress()  // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10
+                || socketAddress.isMulticastAddress()  // 224.0.0.0/4, ff00::/8
+                || isUniqueLocalAddress(socketAddress) // fd00::/8
+                || additionalAddresses.contains(socketAddress);
+        }
+
+        /**
+         * Determine if an IP address lives inside the ULA address range.
+         *
+         * @param address The IP address to test.
+         * @return Whether this address sits in the ULA address range.
+         * @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a>
+         */
+        private boolean isUniqueLocalAddress(InetAddress address) {
+            // ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually
+            // defined right now, so let's be conservative.
+            return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd;
         }
     }
 
 }
diff --git a/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java b/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java
index 4682d45d..a7a7bf48 100644
--- a/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java
+++ b/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java
@@ -29,11 +29,18 @@ public void matchesPort() {
 
     @ParameterizedTest
     @ValueSource(strings = {
         "0.0.0.0", "[::]",
         "localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
-        "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
+        "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1",
+        // Multicast
+        "224.0.0.1", "ff02::1",
+        // Cloud metadata providers
+        "100.100.100.200", // Alibaba
+        "192.0.0.192", // Oracle
+        "fd00:ec2::254", // AWS
+        "169.254.169.254", // AWS, Digital Ocean, GCP, etc..
     })
     public void blocksLocalDomains(String domain) {
         assertEquals(apply(CoreConfig.httpRules, domain, 80).action, Action.DENY);
     }
 

Vulnerable code:             return socketAddress.isAnyLocalAddress()
                || socketAddress.isLoopbackAddress()
                || socketAddress.isLinkLocalAddress()
                || socketAddress.isSiteLocalAddress();
        "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
Fixed code: import java.net.Inet6Address;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
        private static final Set<InetAddress> additionalAddresses = Arrays.stream(new String[]{
            // Block various cloud providers internal IPs.
            "100.100.100.200", // Alibaba
            "192.0.0.192", // Oracle
        }).map(InetAddresses::forString).collect(Collectors.toUnmodifiableSet());

            return
                socketAddress.isAnyLocalAddress()      // 0.0.0.0, ::0
                || socketAddress.isLoopbackAddress()   // 127.0.0.0/8, ::1
                || socketAddress.isLinkLocalAddress()  // 169.254.0.0/16, fe80::/10
                || socketAddress.isSiteLocalAddress()  // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10
                || socketAddress.isMulticastAddress()  // 224.0.0.0/4, ff00::/8
                || isUniqueLocalAddress(socketAddress) // fd00::/8
                || additionalAddresses.contains(socketAddress);
        }

        /**
         * Determine if an IP address lives inside the ULA address range.
         *
         * @param address The IP address to test.
         * @return Whether this address sits in the ULA address range.
         * @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a>
         */
        private boolean isUniqueLocalAddress(InetAddress address) {
            // ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually
            // defined right now, so let's be conservative.
            return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd;
        "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1",
        // Multicast
        "224.0.0.1", "ff02::1",
        // Cloud metadata providers
        "100.100.100.200", // Alibaba
        "192.0.0.192", // Oracle
        "fd00:ec2::254", // AWS
        "169.254.169.254", // AWS, Digital Ocean, GCP, etc..