CVE-2015-6568: Improper Input Validation (CWE-20)
value: Wolf CMS before 0.8.3.1 allows unrestricted file rename and PHP Code Execution because admin/plugin/file_manager/browse/ (aka the filemanager) does not prevent a change of a file extension to ".php" after originally using the parameter "filename" for uploading a JPEG image. Exploitation requires a registered user who has access to upload functionality.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2017-04-14T16:59Z
Repository: https://github.com/wolfcms/wolfcms
Language: PHP
Files changed: wolf/plugins/file_manager/FileManagerController.php, wolf/plugins/file_manager/i18n/en-message.php, wolf/plugins/file_manager/views/index.php, wolf/plugins/file_manager/views/view.php
Commit date: 2015-08-10 16:01:40 +0200
Commit message: Fix #619 and #625

Diff: diff --git a/wolf/plugins/file_manager/FileManagerController.php b/wolf/plugins/file_manager/FileManagerController.php
index eb11d0f..77d2405 100644
--- a/wolf/plugins/file_manager/FileManagerController.php
+++ b/wolf/plugins/file_manager/FileManagerController.php
@@ -25,11 +25,11 @@
 
 /* Security measure */
 if (!defined('IN_CMS')) { exit(); }
 
 /**
- * 
+ *
  */
 class FileManagerController extends PluginController {
 
     var $path;
     var $fullpath;
@@ -53,10 +53,18 @@ class FileManagerController extends PluginController {
 
     public function index() {
         $this->browse();
     }
 
+    static function htmlContextCleaner($input) {
+        $bad_chars = array("<", ">");
+        $safe_chars = array("&lt;", "&gt;");
+        $output = str_replace($bad_chars, $safe_chars, $input);
+
+        return stripslashes($output);
+    }
+
     public function browse() {
         $params = func_get_args();
 
         $this->path = join('/', $params);
         // make sure there's a / at the end
@@ -92,11 +100,11 @@ class FileManagerController extends PluginController {
 
         // clean up nicely
         $this->fullpath = preg_replace('/\/\//', '/', $this->fullpath);
 
         $this->display('file_manager/views/index', array(
-            'dir' => $this->path,
+            'dir' => htmlContextCleaner($this->path),
             //'files' => $this->_getListFiles()
             'files' => $this->_listFiles()
         ));
     }
 
@@ -131,19 +139,19 @@ class FileManagerController extends PluginController {
         // Clean up nicely
         $filename = str_replace('//', '', $filename);
 
         // We don't allow leading slashes
         $filename = preg_replace('/^\//', '', $filename);
-        
+
         // Check if file had URL_SUFFIX - if so, append it to filename
         $filename .= (isset($_GET['has_url_suffix']) && $_GET['has_url_suffix']==='1') ? URL_SUFFIX : '';
-        
+
         $file = FILES_DIR . '/' . $filename;
         if (!$this->_isImage($file) && file_exists($file)) {
             $content = file_get_contents($file);
         }
-        
+
         $this->display('file_manager/views/view', array(
             'csrf_token' => SecureToken::generateToken(BASE_URL.'plugin/file_manager/save/'.$filename),
             'is_image' => $this->_isImage($file),
             'filename' => $filename,
             'content' => $content
@@ -154,11 +162,11 @@ class FileManagerController extends PluginController {
         $data = $_POST['file'];
 
         // security (remove all ..)
         $data['name'] = str_replace('..', '', $data['name']);
         $file = FILES_DIR . DS . $data['name'];
-        
+
         // CSRF checks
         if (isset($_POST['csrf_token'])) {
             $csrf_token = $_POST['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/save/'.$data['name'])) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -167,11 +175,11 @@ class FileManagerController extends PluginController {
         }
         else {
             Flash::set('error', __('No CSRF token found!'));
             redirect(get_url('plugin/file_manager/view/'.$data['name']));
         }
-        
+
         if (file_exists($file)) {
             if (file_put_contents($file, $data['content']) !== false) {
                 Flash::set('success', __('File has been saved with success!'));
             } else {
                 Flash::set('error', __('File is not writable! File has not been saved!'));
@@ -195,11 +203,11 @@ class FileManagerController extends PluginController {
     public function create_file() {
         if (!AuthUser::hasPermission('file_manager_mkfile')) {
             Flash::set('error', __('You do not have sufficient permissions to create a file.'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         // CSRF checks
         if (isset($_POST['csrf_token'])) {
             $csrf_token = $_POST['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/create_file')) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -229,11 +237,11 @@ class FileManagerController extends PluginController {
     public function create_directory() {
         if (!AuthUser::hasPermission('file_manager_mkdir')) {
             Flash::set('error', __('You do not have sufficient permissions to create a directory.'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         // CSRF checks
         if (isset($_POST['csrf_token'])) {
             $csrf_token = $_POST['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/create_directory')) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -267,11 +275,11 @@ class FileManagerController extends PluginController {
         }
 
         $paths = func_get_args();
 
         $file = urldecode(join('/', $paths));
-        
+
         // CSRF checks
         if (isset($_GET['csrf_token'])) {
             $csrf_token = $_GET['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/delete/'.$file)) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -302,11 +310,11 @@ class FileManagerController extends PluginController {
     public function upload() {
         if (!AuthUser::hasPermission('file_manager_upload')) {
             Flash::set('error', __('You do not have sufficient permissions to upload a file.'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         // CSRF checks
         if (isset($_POST['csrf_token'])) {
             $csrf_token = $_POST['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/upload')) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -315,11 +323,11 @@ class FileManagerController extends PluginController {
         }
         else {
             Flash::set('error', __('No CSRF token found!'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         $mask = Plugin::getSetting('umask', 'file_manager');
         umask(octdec($mask));
 
         $data = $_POST['upload'];
         $path = str_replace('..', '', $data['path']);
@@ -327,10 +335,16 @@ class FileManagerController extends PluginController {
 
         // Clean filenames
         $filename = preg_replace('/ /', '_', $_FILES['upload_file']['name']);
         $filename = preg_replace('/[^a-z0-9_\-\.]/i', '', $filename);
 
+        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+        if (in_array($ext, ['php', 'php3', 'php4', 'inc'])) {
+            Flash::set('error', __('Not allowed to upload files with extension :ext', $ext));
+            redirect(get_url('plugin/file_manager/browse/'));
+        }
+
         if (isset($_FILES)) {
             $file = $this->_upload_file($filename, FILES_DIR . '/' . $path . '/', $_FILES['upload_file']['tmp_name'], $overwrite);
 
             if ($file === false)
                 Flash::set('error', __('File has not been uploaded!'));
@@ -354,11 +368,11 @@ class FileManagerController extends PluginController {
         }
         else {
             Flash::set('error', __('No CSRF token found!'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         $data = $_POST['file'];
         $data['name'] = str_replace('..', '', $data['name']);
         $file = FILES_DIR . '/' . $data['name'];
 
         if (file_exists($file)) {
@@ -376,11 +390,11 @@ class FileManagerController extends PluginController {
     public function rename() {
         if (!AuthUser::hasPermission('file_manager_rename')) {
             Flash::set('error', __('You do not have sufficient permissions to rename this file or directory.'));
             redirect(get_url('plugin/file_manager/browse/'));
         }
-        
+
         // CSRF checks
         if (isset($_POST['csrf_token'])) {
             $csrf_token = $_POST['csrf_token'];
             if (!SecureToken::validateToken($csrf_token, BASE_URL.'plugin/file_manager/rename')) {
                 Flash::set('error', __('Invalid CSRF token found!'));
@@ -402,10 +416,18 @@ class FileManagerController extends PluginController {
         $data['new_name'] = preg_replace('/[^a-z0-9_\-\.]/i', '', $data['new_name']);
 
         $path = substr($data['current_name'], 0, strrpos($data['current_name'], '/'));
         $file = FILES_DIR . '/' . $data['current_name'];
 
+        // Check if trying to rename to php file (.php / .php3 etc)
+        $ext = strtolower(pathinfo($data['new_name'], PATHINFO_EXTENSION));
+
+        if (in_array($ext, ['php', 'php3', 'php4', 'inc'])) {
+            Flash::set('error', __('Not allowed to rename to :ext', $ext));
+            redirect(get_url('plugin/file_manager/browse/' . $path));
+        }
+
         // Check another file doesn't already exist with same name
         if (file_exists(FILES_DIR . '/' . $path . '/' . $data['new_name'])) {
             Flash::set('error', __('A file or directory with that name already exists!'));
             redirect(get_url('plugin/file_manager/browse/' . $path));
         }
@@ -440,22 +462,22 @@ class FileManagerController extends PluginController {
                     continue;
 
                 $name = $cur->getFilename();
                 if (Plugin::getSetting('show_hidden', 'file_manager') == '0' && $name[0] === '.')
                     continue;
-                
+
                 if (Plugin::getSetting('show_backups', 'file_manager') == '0' && $name[strlen($name)-1] === '~')
                     continue;
 
                 $object = new stdClass;
                 $object->name = $cur->getFilename();
                 $object->is_dir = $cur->isDir();
                 $object->is_file = $cur->isFile();
                 $object->size = convert_size($cur->getSize());
                 $object->mtime = date('D, j M, Y', $cur->getMTime());
                 list($object->perms, $object->chmod) = $this->_getPermissions($cur->getPerms());
-                
+
                 // Find the file type
                 $object->type = $this->_getFileType($cur);
 
                 // make the link depending on if it's a file or a dir
                 if ($cur->isDir()) {
@@ -475,17 +497,17 @@ class FileManagerController extends PluginController {
                     return 1;
                 } else {
                     return strnatcmp($a->name, $b->name);
                 }
             });
-            
+
             return $files;
         }
 
         return array();
     }
-    
+
     private function _getFileType($file) {
         $default = 'unknown';
 
         $types = array(
             'png'   => 'image',
@@ -657,11 +679,11 @@ class FileManagerController extends PluginController {
             redirect(get_url('login'));
         } else if (!AuthUser::hasPermission('admin_edit')) {
             Flash::set('error', __('You do not have permission to access the requested page!'));
             redirect(get_url());
         }
-        
+
         $settings = Plugin::getAllSettings('file_manager');
 
         if (!$settings) {
             Flash::set('error', 'Files - ' . __('unable to retrieve plugin settings.'));
             return;
@@ -676,11 +698,11 @@ class FileManagerController extends PluginController {
             redirect(get_url('login'));
         } else if (!AuthUser::hasPermission('admin_edit')) {
             Flash::set('error', __('You do not have permission to access the requested page!'));
             redirect(get_url());
         }
-        
+
         if (!isset($_POST['settings'])) {
             Flash::set('error', 'File Manager - ' . __('form was not posted.'));
             redirect(get_url('plugin/file_manager/settings'));
         } else {
             $settings = $_POST['settings'];
diff --git a/wolf/plugins/file_manager/i18n/en-message.php b/wolf/plugins/file_manager/i18n/en-message.php
index 1de2ace..d54e798 100644
--- a/wolf/plugins/file_manager/i18n/en-message.php
+++ b/wolf/plugins/file_manager/i18n/en-message.php
@@ -39,10 +39,12 @@ return array(
     'Files' => 'Files',
     'General settings' => 'General settings',
     'Modified' => 'Modified',
     'Modify' => 'Modify',
     'No' => 'No',
+    'Not allowed to upload files with extension :ext' => 'Not allowed to upload files with extension :ext',
+    'Not allowed to rename to :ext' => 'Not allowed to rename to :ext',
     'Permission denied!' => 'Permission denied!',
     'Permissions' => 'Permissions',
     'Provides interface to manage files from the administration.' => 'Provides interface to manage files from the administration.',
     'Rename' => 'Rename',
     'Save' => 'Save',
@@ -70,6 +72,6 @@ return array(
     'plugin settings saved.' => 'plugin settings saved.',
     'unable to remove stored settings!' => 'unable to remove stored settings!',
     'unable to retrieve plugin settings.' => 'unable to retrieve plugin settings.',
     'unable to store plugin settings!' => 'unable to store plugin settings!',
     'uninstalled.' => 'uninstalled.'
-);
\ No newline at end of file
+);
diff --git a/wolf/plugins/file_manager/views/index.php b/wolf/plugins/file_manager/views/index.php
index c6ba53b..2f76df2 100644
--- a/wolf/plugins/file_manager/views/index.php
+++ b/wolf/plugins/file_manager/views/index.php
@@ -21,20 +21,29 @@
  */
 
 /* Security measure */
 if (!defined('IN_CMS')) { exit(); }
 
+function htmlContextCleaner($input) {
+    $bad_chars = array("<", ">");
+    $safe_chars = array("&lt;", "&gt;");
+    $output = str_replace($bad_chars, $safe_chars, $input);
+
+    return stripslashes($output);
+}
+
   $out = '';
   $progres_path = '';
-  $paths = explode('/', $dir); 
+  $paths = explode('/', $dir);
   $nb_path = count($paths)-1; // -1 to didn't display current dir as a link
   foreach ($paths as $i => $path) {
     if ($i+1 == $nb_path) {
       $out .= $path;
     } else if ($path != '') {
+      $path = preg_replace('/.*:\/\/[^\/]+\//', '/', $path);
       $progres_path .= $path.'/';
-      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.$path.'</a>/';
+      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.htmlContextCleaner($path).'</a>/';
     }
   }
 ?>
 <h1><a href="<?php echo get_url('plugin/file_manager'); ?>">public</a>/<?php echo $out; ?></h1>
 <table id="files-list" class="index" cellpadding="0" cellspacing="0" border="0">
@@ -72,27 +81,27 @@ if (!defined('IN_CMS')) { exit(); }
 
 <div id="popups">
 
   <div class="popup" id="chmod-popup" style="display:none;">
     <h3><?php echo __('Change mode'); ?></h3>
-    <form action="<?php echo get_url('plugin/file_manager/chmod'); ?>" method="post"> 
+    <form action="<?php echo get_url('plugin/file_manager/chmod'); ?>" method="post">
       <div>
         <input id="csrf_token" name="csrf_token" type="hidden" value="<?php echo SecureToken::generateToken(BASE_URL.'plugin/file_manager/chmod'); ?>" />
         <input id="chmod_file_name" name="file[name]" type="hidden" value="" />
-        <input id="chmod_file_mode" maxlength="4" name="file[mode]" type="text" value="" /> 
+        <input id="chmod_file_mode" maxlength="4" name="file[mode]" type="text" value="" />
         <input id="chmod_file_button" name="commit" type="submit" value="<?php echo __('Change mode'); ?>" />
       </div>
       <p><a class="close-link" href="#" onclick="toggle_chmod_popup(); return false;"><?php echo __('Close'); ?></a></p>
     </form>
   </div>
   <div class="popup" id="rename-popup" style="display:none;">
       <h3><?php echo __('Rename'); ?></h3>
-      <form action="<?php echo get_url('plugin/file_manager/rename'); ?>" method="post"> 
+      <form action="<?php echo get_url('plugin/file_manager/rename'); ?>" method="post">
         <div>
           <input id="csrf_token" name="csrf_token" type="hidden" value="<?php echo SecureToken::generateToken(BASE_URL.'plugin/file_manager/rename'); ?>" />
           <input id="rename_file_current_name" name="file[current_name]" type="hidden" value="" />
-          <input id="rename_file_new_name" maxlength="50" name="file[new_name]" type="text" value="" /> 
+          <input id="rename_file_new_name" maxlength="50" name="file[new_name]" type="text" value="" />
           <input id="rename_file_button" name="commit" type="submit" value="<?php echo __('Rename'); ?>" />
         </div>
         <p><a class="close-link" href="#" onclick="toggle_rename_popup(); return false;"><?php echo __('Close'); ?></a></p>
       </form>
     </div>
diff --git a/wolf/plugins/file_manager/views/view.php b/wolf/plugins/file_manager/views/view.php
index a4310d0..ec857bc 100644
--- a/wolf/plugins/file_manager/views/view.php
+++ b/wolf/plugins/file_manager/views/view.php
@@ -21,20 +21,30 @@
  */
 
 /* Security measure */
 if (!defined('IN_CMS')) { exit(); }
 
+function htmlContextCleaner($input) {
+    $bad_chars = array("<", ">");
+    $safe_chars = array("&lt;", "&gt;");
+    $output = str_replace($bad_chars, $safe_chars, $input);
+
+    return stripslashes($output);
+}
+
+
   $out = '';
   $progres_path = '';
-  $paths = explode('/', $filename); 
+  $paths = explode('/', $filename);
   $nb_path = count($paths);
   foreach ($paths as $i => $path) {
     if ($i+1 == $nb_path) {
       $out .= $path;
     } else {
+      $path = preg_replace('/.*:\/\/[^\/]+\//', '/', $path);
       $progres_path .= $path.'/';
-      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.$path.'</a>/';
+      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.htmlContextCleaner($path).'</a>/';
     }
   }
 ?>
 <h1><a href="<?php echo get_url('plugin/file_manager'); ?>">public</a>/<?php echo $out; ?></h1>
 <?php if ($is_image) { ?>

Vulnerable code:  * 
            'dir' => $this->path,
        
        
        
        
        
        
        
        
        
        
        
        
                
                
            
    
        
        
);
  $paths = explode('/', $dir); 
      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.$path.'</a>/';
    <form action="<?php echo get_url('plugin/file_manager/chmod'); ?>" method="post"> 
        <input id="chmod_file_mode" maxlength="4" name="file[mode]" type="text" value="" /> 
      <form action="<?php echo get_url('plugin/file_manager/rename'); ?>" method="post"> 
          <input id="rename_file_new_name" maxlength="50" name="file[new_name]" type="text" value="" /> 
  $paths = explode('/', $filename); 
      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.$path.'</a>/';
Fixed code:  *
    static function htmlContextCleaner($input) {
        $bad_chars = array("<", ">");
        $safe_chars = array("&lt;", "&gt;");
        $output = str_replace($bad_chars, $safe_chars, $input);

        return stripslashes($output);
    }

            'dir' => htmlContextCleaner($this->path),










        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if (in_array($ext, ['php', 'php3', 'php4', 'inc'])) {
            Flash::set('error', __('Not allowed to upload files with extension :ext', $ext));
            redirect(get_url('plugin/file_manager/browse/'));
        }



        // Check if trying to rename to php file (.php / .php3 etc)
        $ext = strtolower(pathinfo($data['new_name'], PATHINFO_EXTENSION));

        if (in_array($ext, ['php', 'php3', 'php4', 'inc'])) {
            Flash::set('error', __('Not allowed to rename to :ext', $ext));
            redirect(get_url('plugin/file_manager/browse/' . $path));
        }







    'Not allowed to upload files with extension :ext' => 'Not allowed to upload files with extension :ext',
    'Not allowed to rename to :ext' => 'Not allowed to rename to :ext',
);
function htmlContextCleaner($input) {
    $bad_chars = array("<", ">");
    $safe_chars = array("&lt;", "&gt;");
    $output = str_replace($bad_chars, $safe_chars, $input);

    return stripslashes($output);
}

  $paths = explode('/', $dir);
      $path = preg_replace('/.*:\/\/[^\/]+\//', '/', $path);
      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.htmlContextCleaner($path).'</a>/';
    <form action="<?php echo get_url('plugin/file_manager/chmod'); ?>" method="post">
        <input id="chmod_file_mode" maxlength="4" name="file[mode]" type="text" value="" />
      <form action="<?php echo get_url('plugin/file_manager/rename'); ?>" method="post">
          <input id="rename_file_new_name" maxlength="50" name="file[new_name]" type="text" value="" />
function htmlContextCleaner($input) {
    $bad_chars = array("<", ">");
    $safe_chars = array("&lt;", "&gt;");
    $output = str_replace($bad_chars, $safe_chars, $input);

    return stripslashes($output);
}


  $paths = explode('/', $filename);
      $path = preg_replace('/.*:\/\/[^\/]+\//', '/', $path);
      $out .= '<a href="'.get_url('plugin/file_manager/browse/'.rtrim($progres_path, '/')).'">'.htmlContextCleaner($path).'</a>/';