security/core#121 Add in hard coded list of file extensions that should be prohibited...
authorSeamus Lee <seamuslee001@gmail.com>
Thu, 2 Feb 2023 02:14:04 +0000 (13:14 +1100)
committerTim Otten <totten@civicrm.org>
Wed, 15 Feb 2023 06:17:23 +0000 (22:17 -0800)
Expand the list of extensions as per Tim and allow for users to define their own list via a constant

CRM/Utils/File.php
tests/phpunit/CRM/Utils/FileTest.php

index b9355b14c8f4fabf01e125f079b0fd431c85c3f3..a822f32c6f0393f2ad230334f8c8e9c9db89fad6 100644 (file)
@@ -371,8 +371,9 @@ class CRM_Utils_File {
       }
       Civi::$statics[__CLASS__]['file_extensions'] = $extensions;
     }
+    $restricted = CRM_Utils_Constant::value('CIVICRM_RESTRICTED_UPLOADS', '/(php|php\d|phtml|phar|pl|py|cgi|asp|js|sh|exe|pcgi\d)/i');
     // support lower and uppercase file extensions
-    return (bool) isset(Civi::$statics[__CLASS__]['file_extensions'][strtolower($ext)]);
+    return (bool) isset(Civi::$statics[__CLASS__]['file_extensions'][strtolower($ext)]) && !preg_match($restricted, strtolower($ext));
   }
 
   /**
index 567dc5491b1553a5ef8e628e5c37d9b08711be8e..333a49613d54604c276291067d3620a24af2ddb3 100644 (file)
@@ -6,6 +6,10 @@
  */
 class CRM_Utils_FileTest extends CiviUnitTestCase {
 
+  public function tearDown(): void {
+    $this->callAPISuccess('OptionValue', 'get', ['option_group_id' => 'safe_file_extension', 'value' => 17, 'api.option_value.delete' => ['id' => "\$value.id"]]);
+  }
+
   /**
    * Test is child path.
    */
@@ -646,4 +650,34 @@ class CRM_Utils_FileTest extends CiviUnitTestCase {
     }
   }
 
+  /**
+   * Generate examples to test the safe file extension
+   * @return array
+   */
+  public static function safeFileExtensionExamples(): array {
+    $cases = [
+      'PDF File Extension' => ['pdf', TRUE, TRUE],
+      'PHP File Extension' => ['php', FALSE, FALSE],
+      'PHAR' => ['phar', FALSE, FALSE],
+      'PHP5 File Extension' => ['php5', FALSE, FALSE],
+    ];
+    return $cases;
+  }
+
+  /**
+   * Test that modifying the safe File Extension option group still ensures some are blocked
+   * @dataProvider safeFileExtensionExamples
+   */
+  public function testSafeFileExtensionValidation($extension, $standardInstallCheck, $afterModificationCheck): void {
+    $this->assertEquals($standardInstallCheck, CRM_Utils_File::isExtensionSafe($extension));
+    $optionValue = $this->callAPISuccess('OptionValue', 'create', [
+      'option_group_id' => 'safe_file_extension',
+      'label' => $extension,
+      'name' => $extension,
+      'value' => 17,
+    ]);
+    unset(Civi::$statics['CRM_Utils_File']['file_extensions']);
+    $this->assertEquals($standardInstallCheck, CRM_Utils_File::isExtensionSafe($extension));
+  }
+
 }