Vulnerability: An issue was discovered in osTicket before 1.10.7 and 1.12.x before 1.12.1. The Ticket creation form allows users to upload files along with queries. It was found that the file-upload functionality has fewer (or no) mitigations implemented for file content checks; also, the output is not handled properly, causing persistent XSS that leads to cookie stealing or malicious actions. For example, a non-agent user can upload a .html file, and Content-Disposition will be set to inline instead of attachment.
Vulnerability ID: CVE-2019-14748
CWE: CWE-79
Severity score: 2.9
Project: https://github.com/osTicket/osTicket
Published: 2019-08-07
File: include/class.file.php
Commit message: security: HTML File Browser Execution (Windows: Firefox/IE)

This addresses an issue reported by Aishwarya Iyer where attached HTML files
are executed in the browser instead of forcing download in Firefox and IE
for Windows specifically. This is caused by an incorrect
`Content-Disposition` set in the `AttachmentFile::download` function.
Instead of attachments having a disposition of `attachment` (which forces
download) they have a disposition of `inline` (which displays the file
contents in the browser). This updates the download function to use whatever
disposition is passed (for S3 plugin), if none it defaults to `attachment`.
In addition, this overwrites the disposition and sets it to `attachment`
after the `$bk->sendRedirectURL()` so that S3 attachments still work and the
issue of an attacker passing their own disposition is mitigated.
Commit author: JediKev

Vulnerable version (before fix):
 static function _genUrlSignature($id, $key, $signature, $expires) {     }      function download($disposition=false, $expires=false) {         $disposition = $disposition ?: 'inline';         $bk = $this->open();         if ($bk->sendRedirectUrl($disposition))             return;         $ttl = ($expires) ? $expires - Misc::gmtime() : false;         $this->makeCacheable($ttl);         $type = $this->getType() ?: 'application/octet-stream';         Http::download($this->getName(), $type, null, 'inline');         header('Content-Length: '.$this->getSize());         $this->sendData(false);         exit();

Fixed version (after fix):
 static function _genUrlSignature($id, $key, $signature, $expires) {     }      function download($disposition=false, $expires=false) {         $disposition = ($disposition && strcasecmp($disposition, 'inline') == 0               && strpos($this->getType(), 'image/') !== false)             ? 'inline' : 'attachment';         $bk = $this->open();         if ($bk->sendRedirectUrl($disposition))             return;         $ttl = ($expires) ? $expires - Misc::gmtime() : false;         $this->makeCacheable($ttl);         $type = $this->getType() ?: 'application/octet-stream';         Http::download($this->getName(), $type, null, $disposition);         header('Content-Length: '.$this->getSize());         $this->sendData(false);         exit();

Patch:
patch: @@ -240,14 +240,16 @@ static function _genUrlSignature($id, $key, $signature, $expires) {
     }
 
     function download($disposition=false, $expires=false) {
-        $disposition = $disposition ?: 'inline';
+        $disposition = ($disposition && strcasecmp($disposition, 'inline') == 0
+              && strpos($this->getType(), 'image/') !== false)
+            ? 'inline' : 'attachment';
         $bk = $this->open();
         if ($bk->sendRedirectUrl($disposition))
             return;
         $ttl = ($expires) ? $expires - Misc::gmtime() : false;
         $this->makeCacheable($ttl);
         $type = $this->getType() ?: 'application/octet-stream';
-        Http::download($this->getName(), $type, null, 'inline');
+        Http::download($this->getName(), $type, null, $disposition);
         header('Content-Length: '.$this->getSize());
         $this->sendData(false);
         exit();
path: include/class.file.php