CVE-2020-26255: Unrestricted Upload of File with Dangerous Type (CWE-434)
value: Kirby is a CMS. In Kirby CMS (getkirby/cms) before version 3.4.5, and Kirby Panel before version 2.5.14 , an editor with full access to the Kirby Panel can upload a PHP .phar file and execute it on the server. This vulnerability is critical if you might have potential attackers in your group of authenticated Panel users, as they can gain access to the server with such a Phar file. Visitors without Panel access *cannot* use this attack vector. The problem has been patched in Kirby 2.5.14 and Kirby 3.4.5. Please update to one of these or a later version to fix the vulnerability. Note: Kirby 2 reaches end of life on December 31, 2020. We therefore recommend to upgrade your Kirby 2 sites to Kirby 3. If you cannot upgrade, we still recommend to update to Kirby 2.5.14.
CWE description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
CVSS v3 base score: 9.1
Published: 2020-12-08T15:15Z
Repository: https://github.com/getkirby-v2/panel
Language: PHP
Files changed: app/src/panel/models/page/uploader.php
Commit date: 2020-12-01 11:11:19 +0100
Commit message: Prevent phar upload in file upload validation

Diff: diff --git a/app/src/panel/models/page/uploader.php b/app/src/panel/models/page/uploader.php
index ca284ec..fba999a 100644
--- a/app/src/panel/models/page/uploader.php
+++ b/app/src/panel/models/page/uploader.php
@@ -1,6 +1,6 @@
-<?php 
+<?php
 
 namespace Kirby\Panel\Models\Page;
 
 use Error;
 use Exception;
@@ -25,11 +25,11 @@ class Uploader {
     $this->filename  = $this->blueprint->files()->sanitize() ? '{safeFilename}' : '{filename}';
 
     if($this->file) {
       $this->replace();
     } else {
-      $this->upload();      
+      $this->upload();
     }
 
   }
 
   public function upload() {
@@ -53,26 +53,26 @@ class Uploader {
     $file  = $this->move($upload, $event);
 
     // create the initial meta file
     // without triggering the update hook
     try {
-      $file->createMeta(false);      
+      $file->createMeta(false);
     } catch(Exception $e) {
       // don't react on meta errors
       // the meta file can still be generated later
     }
 
     // make sure that the file is being marked as updated
     touch($file->root());
 
-    kirby()->trigger($event, $file);          
+    kirby()->trigger($event, $file);
 
   }
 
   public function replace() {
 
-    $file   = $this->file;    
+    $file   = $this->file;
     $upload = new Upload($file->root(), array(
       'overwrite' => true,
       'accept' => function($upload) use($file) {
         if($upload->mime() != $file->mime()) {
           throw new Error(l('files.replace.error.type'));
@@ -133,11 +133,11 @@ class Uploader {
   }
 
   public function checkUpload($file) {
 
     $filesettings        = $this->blueprint->files();
-    $forbiddenExtensions = array('php', 'html', 'htm', 'exe', kirby()->option('content.file.extension', 'txt'));
+    $forbiddenExtensions = array('php', 'phar', 'html', 'htm', 'exe', kirby()->option('content.file.extension', 'txt'));
     $forbiddenMimes      = array_merge(f::$mimes['php'], array('text/html', 'application/x-msdownload'));
     $extension           = strtolower($file->extension());
 
     // files without extension are not allowed
     if(empty($extension)) {
@@ -148,11 +148,11 @@ class Uploader {
     if(in_array($extension, $forbiddenExtensions)) {
       throw new Exception(l('files.add.error.extension.forbidden'));
     }
 
     // especially block any connection that contains php
-    if(str::contains($extension, 'php')) {
+    if(str::contains($extension, 'php') || str::contains($extension, 'phar')) {
       throw new Exception(l('files.add.error.extension.forbidden'));
     }
 
     // block forbidden mimes
     if(in_array(strtolower($file->mime()), $forbiddenMimes)) {
@@ -185,10 +185,10 @@ class Uploader {
     }
 
     // Files blueprint option 'height'
     if($file->type() == 'image' and $filesettings->height() and $file->height() > $filesettings->height()) {
       throw new Exception('Page only allows image height of ' . $filesettings->height().'px');
-    } 
+    }
 
   }
 
-}
\ No newline at end of file
+}

Vulnerable code: <?php 
      $this->upload();      
      $file->createMeta(false);      
    kirby()->trigger($event, $file);          
    $file   = $this->file;    
    $forbiddenExtensions = array('php', 'html', 'htm', 'exe', kirby()->option('content.file.extension', 'txt'));
    if(str::contains($extension, 'php')) {
    } 
}
Fixed code: <?php
      $this->upload();
      $file->createMeta(false);
    kirby()->trigger($event, $file);
    $file   = $this->file;
    $forbiddenExtensions = array('php', 'phar', 'html', 'htm', 'exe', kirby()->option('content.file.extension', 'txt'));
    if(str::contains($extension, 'php') || str::contains($extension, 'phar')) {
    }
}