VYPR
Low severityNVD Advisory· Published Nov 23, 2020· Updated Aug 4, 2024

Stored XSS by authenticated backend user with access to upload files

CVE-2020-15249

Description

October is a free, open-source, self-hosted CMS platform based on the Laravel PHP Framework. In October CMS from version 1.0.319 and before version 1.0.469, backend users with access to upload files were permitted to upload SVG files without any sanitization applied to the uploaded files. Since SVG files support being parsed as HTML by browsers, this means that they could theoretically upload Javascript that would be executed on a path under the website's domain (i.e. /storage/app/media/evil.svg), but they would have to convince their target to visit that location directly in the target's browser as the backend does not display SVGs inline anywhere, SVGs are only displayed as image resources in the backend and are thus unable to be executed. Issue has been patched in Build 469 (v1.0.469) & v1.1.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
october/backendPackagist
>= 1.0.319, < 1.0.4691.0.469

Affected products

1

Patches

1
80aab47f044a

Security fixes for v1.0.469

https://github.com/octobercms/libraryLuke TowersSep 4, 2020via ghsa
3 files changed · +113 98
  • src/Exception/SystemException.php+18 1 modified
    @@ -1,12 +1,29 @@
     <?php namespace October\Rain\Exception;
     
    +use Exception;
    +use October\Rain\Html\HtmlBuilder;
    +
     /**
      * This class represents a critical system exception.
      * System exceptions are logged in the error log.
      *
      * @package october\exception
    - * @author Alexey Bobkov, Samuel Georges
    + * @author Alexey Bobkov, Samuel Georges, Luke Towers
      */
     class SystemException extends ExceptionBase
     {
    +    /**
    +     * Override the constructor to escape all messages to protect against potential XSS
    +     * from user provided inputs being included in the exception message
    +     *
    +     * @param string $message Error message.
    +     * @param int $code Error code.
    +     * @param Exception $previous Previous exception.
    +     */
    +    public function __construct($message = "", $code = 0, Exception $previous = null)
    +    {
    +        $message = HtmlBuilder::clean($message);
    +
    +        parent::__construct($message, $code, $previous);
    +    }
     }
    
  • src/Filesystem/Definitions.php+0 2 modified
    @@ -103,7 +103,6 @@ protected function defaultExtensions()
                 'png',
                 'webp',
                 'gif',
    -            'svg',
                 'js',
                 'map',
                 'ico',
    @@ -163,7 +162,6 @@ protected function assetExtensions()
                 'js',
                 'woff',
                 'woff2',
    -            'svg',
                 'ttf',
                 'eot',
                 'json',
    
  • src/Halcyon/Builder.php+95 95 modified
    @@ -142,79 +142,99 @@ public function __construct(DatasourceInterface $datasource, Processor $processo
         }
     
         /**
    -     * Switches mode to select a single template by its name.
    +     * Get the compiled file content representation of the query.
          *
    -     * @param  string  $fileName
    -     * @return $this
    +     * @return string
          */
    -    public function whereFileName($fileName)
    +    public function toCompiled()
         {
    -        $this->selectSingle = $this->model->getFileNameParts($fileName);
    -
    -        return $this;
    +        return $this->processor->processUpdate($this, []);
         }
     
         /**
    -     * Set the directory name which the query is targeting.
    +     * Get an array with the values of a given column.
          *
    -     * @param  string  $dirName
    -     * @return $this
    +     * @param  string  $column
    +     * @param  string  $key
    +     * @return array
          */
    -    public function from($dirName)
    +    public function lists($column, $key = null)
         {
    -        $this->from = $dirName;
    +        $select = is_null($key) ? [$column] : [$column, $key];
     
    -        return $this;
    +        if (!is_null($this->cacheMinutes)) {
    +            $results = $this->getCached($select);
    +        }
    +        else {
    +            $results = $this->getFresh($select);
    +        }
    +
    +        $collection = new Collection($results);
    +
    +        return $collection->lists($column, $key);
         }
     
         /**
    -     * Set the "offset" value of the query.
    +     * Set the "limit" value of the query.
          *
          * @param  int  $value
          * @return $this
          */
    -    public function offset($value)
    +    public function limit($value)
         {
    -        $this->offset = max(0, $value);
    +        if ($value >= 0) {
    +            $this->limit = $value;
    +        }
     
             return $this;
         }
     
         /**
    -     * Alias to set the "offset" value of the query.
    +     * Alias to set the "limit" value of the query.
          *
          * @param  int  $value
          * @return \October\Rain\Halcyon\Builder|static
          */
    -    public function skip($value)
    +    public function take($value)
         {
    -        return $this->offset($value);
    +        return $this->limit($value);
         }
     
         /**
    -     * Set the "limit" value of the query.
    +     * Set the "offset" value of the query.
          *
          * @param  int  $value
          * @return $this
          */
    -    public function limit($value)
    +    public function offset($value)
         {
    -        if ($value >= 0) {
    -            $this->limit = $value;
    -        }
    +        $this->offset = max(0, $value);
     
             return $this;
         }
     
         /**
    -     * Alias to set the "limit" value of the query.
    +     * Alias to set the "offset" value of the query.
          *
          * @param  int  $value
          * @return \October\Rain\Halcyon\Builder|static
          */
    -    public function take($value)
    +    public function skip($value)
         {
    -        return $this->limit($value);
    +        return $this->offset($value);
    +    }
    +
    +    /**
    +     * Set the directory name which the query is targeting.
    +     *
    +     * @param  string  $dirName
    +     * @return $this
    +     */
    +    public function from($dirName)
    +    {
    +        $this->from = $dirName;
    +
    +        return $this;
         }
     
         /**
    @@ -239,46 +259,18 @@ public function first()
         }
     
         /**
    -     * Execute the query as a "select" statement.
    -     *
    -     * @param  array  $columns
    -     * @return \October\Rain\Halcyon\Collection|static[]
    -     */
    -    public function get($columns = ['*'])
    -    {
    -        if (!is_null($this->cacheMinutes)) {
    -            $results = $this->getCached($columns);
    -        }
    -        else {
    -            $results = $this->getFresh($columns);
    -        }
    -
    -        $models = $this->getModels($results ?: []);
    -
    -        return $this->model->newCollection($models);
    -    }
    -
    -    /**
    -     * Get an array with the values of a given column.
    +     * Switches mode to select a single template by its name.
          *
    -     * @param  string  $column
    -     * @param  string  $key
    -     * @return array
    +     * @param  string  $fileName
    +     * @return $this
          */
    -    public function lists($column, $key = null)
    +    public function whereFileName($fileName)
         {
    -        $select = is_null($key) ? [$column] : [$column, $key];
    -
    -        if (!is_null($this->cacheMinutes)) {
    -            $results = $this->getCached($select);
    -        }
    -        else {
    -            $results = $this->getFresh($select);
    -        }
    +        $this->validateFileName($fileName);
     
    -        $collection = new Collection($results);
    +        $this->selectSingle = $this->model->getFileNameParts($fileName);
     
    -        return $collection->lists($column, $key);
    +        return $this;
         }
     
         /**
    @@ -321,30 +313,23 @@ protected function runSelect()
         }
     
         /**
    -     * Set a model instance for the model being queried.
    +     * Execute the query as a "select" statement.
          *
    -     * @param  \October\Rain\Halcyon\Model  $model
    -     * @return $this
    +     * @param  array  $columns
    +     * @return \October\Rain\Halcyon\Collection|static[]
          */
    -    public function setModel(Model $model)
    +    public function get($columns = ['*'])
         {
    -        $this->model = $model;
    -
    -        $this->extensions = $this->model->getAllowedExtensions();
    -
    -        $this->from($this->model->getObjectTypeDirName());
    +        if (!is_null($this->cacheMinutes)) {
    +            $results = $this->getCached($columns);
    +        }
    +        else {
    +            $results = $this->getFresh($columns);
    +        }
     
    -        return $this;
    -    }
    +        $models = $this->getModels($results ?: []);
     
    -    /**
    -     * Get the compiled file content representation of the query.
    -     *
    -     * @return string
    -     */
    -    public function toCompiled()
    -    {
    -        return $this->processor->processUpdate($this, []);
    +        return $this->model->newCollection($models);
         }
     
         /**
    @@ -408,10 +393,9 @@ public function update(array $values)
         /**
          * Delete a record from the database.
          *
    -     * @param  string  $fileName
          * @return int
          */
    -    public function delete($fileName = null)
    +    public function delete()
         {
             $this->validateFileName();
     
    @@ -442,6 +426,33 @@ public function lastModified()
             );
         }
     
    +    /**
    +     * Set a model instance for the model being queried.
    +     *
    +     * @param  \October\Rain\Halcyon\Model  $model
    +     * @return $this
    +     */
    +    public function setModel(Model $model)
    +    {
    +        $this->model = $model;
    +
    +        $this->extensions = $this->model->getAllowedExtensions();
    +
    +        $this->from($this->model->getObjectTypeDirName());
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Get the model instance being queried.
    +     *
    +     * @return \October\Rain\Halcyon\Model
    +     */
    +    public function getModel()
    +    {
    +        return $this->model;
    +    }
    +
         /**
          * Get the hydrated models.
          *
    @@ -468,16 +479,6 @@ public function getModels(array $results)
             return $models->all();
         }
     
    -    /**
    -     * Get the model instance being queried.
    -     *
    -     * @return \October\Rain\Halcyon\Model
    -     */
    -    public function getModel()
    -    {
    -        return $this->model;
    -    }
    -
         //
         // Validation (Hard)
         //
    @@ -778,9 +779,8 @@ public static function clearInternalCache()
          *
          * @param  string  $method
          * @param  array   $parameters
    -     * @return mixed
    -     *
          * @throws \BadMethodCallException
    +     * @return void
          */
         public function __call($method, $parameters)
         {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.