CVE-2020-15249 (PHP, published 2020-11-23T20:15Z)
Description: value: 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.
Severity: LOW (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/octobercms/library
Files changed:
src/Exception/SystemException.php
src/Filesystem/Definitions.php
src/Halcyon/Builder.php
Commit message: Security fixes for v1.0.469

Temporarily disables SVG uploads
Improves path validation in FileDatasource & reorgs / cleans up Halcyon Builder
Adds XSS filtering to SystemException messages (related https://github.com/octobercms/library/commit/c16fefde03080fe2b3937e565d9cdf7b70291384#diff-4915b631d3234e0c5232490be34effe4)


Vulnerable code:
 * @author Alexey Bobkov, Samuel Georges
            'svg',
            'svg',
     * Switches mode to select a single template by its name.
     * @param  string  $fileName
     * @return $this
    public function whereFileName($fileName)
        $this->selectSingle = $this->model->getFileNameParts($fileName);

        return $this;
     * Set the directory name which the query is targeting.
     * @param  string  $dirName
     * @return $this
    public function from($dirName)
        $this->from = $dirName;
        return $this;
     * Set the "offset" value of the query.
    public function offset($value)
        $this->offset = max(0, $value);
     * Alias to set the "offset" value of the query.
    public function skip($value)
        return $this->offset($value);
     * Set the "limit" value of the query.
    public function limit($value)
        if ($value >= 0) {
            $this->limit = $value;
        }
     * Alias to set the "limit" value of the query.
    public function take($value)
        return $this->limit($value);
     * 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.
     * @param  string  $column
     * @param  string  $key
     * @return array
    public function lists($column, $key = null)
        $select = is_null($key) ? [$column] : [$column, $key];

        if (!is_null($this->cacheMinutes)) {
            $results = $this->getCached($select);
        }
        else {
            $results = $this->getFresh($select);
        }
        $collection = new Collection($results);
        return $collection->lists($column, $key);
     * 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 compiled file content representation of the query.
     *
     * @return string
     */
    public function toCompiled()
    {
        return $this->processor->processUpdate($this, []);
     * @param  string  $fileName
    public function delete($fileName = null)
    /**
     * Get the model instance being queried.
     *
     * @return \October\Rain\Halcyon\Model
     */
    public function getModel()
    {
        return $this->model;
    }

     * @return mixed
     *

Fixed code:
use Exception;
use October\Rain\Html\HtmlBuilder;

 * @author Alexey Bobkov, Samuel Georges, Luke Towers
    /**
     * 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);
    }
     * Get the compiled file content representation of the query.
     * @return string
    public function toCompiled()
        return $this->processor->processUpdate($this, []);
     * Get an array with the values of a given column.
     * @param  string  $column
     * @param  string  $key
     * @return array
    public function lists($column, $key = null)
        $select = is_null($key) ? [$column] : [$column, $key];
        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 "limit" value of the query.
    public function limit($value)
        if ($value >= 0) {
            $this->limit = $value;
        }
     * Alias to set the "limit" value of the query.
    public function take($value)
        return $this->limit($value);
     * Set the "offset" value of the query.
    public function offset($value)
        $this->offset = max(0, $value);
     * Alias to set the "offset" value of the query.
    public function skip($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;
     * Switches mode to select a single template by its name.
     * @param  string  $fileName
     * @return $this
    public function whereFileName($fileName)
        $this->validateFileName($fileName);
        $this->selectSingle = $this->model->getFileNameParts($fileName);
        return $this;
     * 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);
    public function delete()
    /**
     * 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;
    }

     * @return void