CVE-2021-37711: Server-Side Request Forgery (SSRF) (CWE-918)
value: Versions prior to 6.4.3.1 contain an authenticated server-side request forgery vulnerability in file upload via URL. Version 6.4.3.1 contains a patch. As workarounds for older versions of 6.1, 6.2, and 6.3, corresponding security measures are also available via a plugin.
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: 2021-08-16T23:15Z
Repository: https://github.com/shopware/platform
Language: PHP
Files changed: src/Core/Content/Media/File/FileUrlValidator.php, src/Core/Content/Test/Media/File/FileUrlValidatorTest.php
Commit date: 2021-08-02 16:28:57 +0200
Commit message: NEXT-15673 - Fix file upload ip validation for IPv6

Diff: diff --git a/src/Core/Content/Media/File/FileUrlValidator.php b/src/Core/Content/Media/File/FileUrlValidator.php
index ef6c955c926..30ea43e6147 100644
--- a/src/Core/Content/Media/File/FileUrlValidator.php
+++ b/src/Core/Content/Media/File/FileUrlValidator.php
@@ -15,18 +15,44 @@ class FileUrlValidator implements FileUrlValidatorInterface
         $ip = gethostbyname($host);
 
         // Potentially IPv6
         $ip = trim($ip, '[]');
 
+        return $this->validateIp($ip);
+    }
+
+    private function validateIp(string $ip): bool
+    {
         $ip = filter_var(
             $ip,
             \FILTER_VALIDATE_IP,
             \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE
         );
 
         if (!$ip) {
             return false;
         }
 
+        if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+            return true;
+        }
+
+        // Convert IPv6 to packed format and back so we can check if there is a IPv4 representation of the IP
+        $packedIp = inet_pton($ip);
+        if (!$packedIp) {
+            return false;
+        }
+        $convertedIp = inet_ntop($packedIp);
+        if (!$convertedIp) {
+            return false;
+        }
+        $convertedIp = explode(':', $convertedIp);
+        $ipv4 = array_pop($convertedIp);
+
+        // Additionally filter IPv4 representation of the IP
+        if (filter_var($ipv4, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
+            return $this->validateIp($ipv4);
+        }
+
         return true;
     }
 }
diff --git a/src/Core/Content/Test/Media/File/FileUrlValidatorTest.php b/src/Core/Content/Test/Media/File/FileUrlValidatorTest.php
new file mode 100644
index 00000000000..b1995f973fb
--- /dev/null
+++ b/src/Core/Content/Test/Media/File/FileUrlValidatorTest.php
@@ -0,0 +1,37 @@
+<?php declare(strict_types=1);
+
+namespace Shopware\Core\Content\Test\Media\File;
+
+use PHPUnit\Framework\TestCase;
+use Shopware\Core\Content\Media\File\FileUrlValidator;
+
+class FileUrlValidatorTest extends TestCase
+{
+    /**
+     * @dataProvider fileSourceProvider
+     */
+    public function testIsValid(string $source, bool $expectedResult): void
+    {
+        $validator = new FileUrlValidator();
+
+        static::assertEquals($expectedResult, $validator->isValid($source));
+    }
+
+    public function fileSourceProvider(): array
+    {
+        return [
+            'reserved IPv4' => ['https://127.0.0.1', false],
+            'converted reserved IPv4' => ['https://0:0:0:0:0:FFFF:7F00:0001', false],
+            'reserved IPv4 mapped to IPv6' => ['https://[0:0:0:0:0:FFFF:127.0.0.1]', false],
+            'reserved IPv6' => ['https://FE80::', false],
+            'private IPv4' => ['https://192.168.0.0', false],
+            'converted private IPv4' => ['https://0:0:0:0:0:FFFF:C0A8:0000', false],
+            'private IPv4 mapped to IPv6' => ['https://[0:0:0:0:0:FFFF:192.168.0.0]', false],
+            'private IPv6' => ['https://FC00::', false],
+            'invalid IPv4' => ['https://378.0.0.1', false],
+            'valid IPv4' => ['https://8.8.8.8', true],
+            'valid IPv6' => ['https://2001:db8::8a2e:370:7334', true],
+            'valid IPv6 URL' => ['https://[2001:db8::8a2e:370:7334]', true],
+        ];
+    }
+}

Fixed code:         return $this->validateIp($ip);
    }

    private function validateIp(string $ip): bool
    {
        if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
            return true;
        }

        // Convert IPv6 to packed format and back so we can check if there is a IPv4 representation of the IP
        $packedIp = inet_pton($ip);
        if (!$packedIp) {
            return false;
        }
        $convertedIp = inet_ntop($packedIp);
        if (!$convertedIp) {
            return false;
        }
        $convertedIp = explode(':', $convertedIp);
        $ipv4 = array_pop($convertedIp);

        // Additionally filter IPv4 representation of the IP
        if (filter_var($ipv4, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
            return $this->validateIp($ipv4);
        }

<?php declare(strict_types=1);

namespace Shopware\Core\Content\Test\Media\File;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Content\Media\File\FileUrlValidator;

class FileUrlValidatorTest extends TestCase
{
    /**
     * @dataProvider fileSourceProvider
     */
    public function testIsValid(string $source, bool $expectedResult): void
    {
        $validator = new FileUrlValidator();

        static::assertEquals($expectedResult, $validator->isValid($source));
    }

    public function fileSourceProvider(): array
    {
        return [
            'reserved IPv4' => ['https://127.0.0.1', false],
            'converted reserved IPv4' => ['https://0:0:0:0:0:FFFF:7F00:0001', false],
            'reserved IPv4 mapped to IPv6' => ['https://[0:0:0:0:0:FFFF:127.0.0.1]', false],
            'reserved IPv6' => ['https://FE80::', false],
            'private IPv4' => ['https://192.168.0.0', false],
            'converted private IPv4' => ['https://0:0:0:0:0:FFFF:C0A8:0000', false],
            'private IPv4 mapped to IPv6' => ['https://[0:0:0:0:0:FFFF:192.168.0.0]', false],
            'private IPv6' => ['https://FC00::', false],
            'invalid IPv4' => ['https://378.0.0.1', false],
            'valid IPv4' => ['https://8.8.8.8', true],
            'valid IPv6' => ['https://2001:db8::8a2e:370:7334', true],
            'valid IPv6 URL' => ['https://[2001:db8::8a2e:370:7334]', true],
        ];
    }
}