CVE-2023-1320 (PHP, published 2023-03-10T16:15Z)
Description: value: Cross-site Scripting (XSS) - Stored in GitHub repository osticket/osticket prior to v1.16.6.
Severity: nan (CVSS v3 base score: 6.1)
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/osticket/osticket
Files changed:
bootstrap.php
include/ajax.draft.php
include/class.file.php
include/class.forms.php
Commit message: xss: Draft Files

This mitigates an XSS vulnerability with files uploaded through drafts.


Vulnerable code:
        if (!isset($_POST['data']) && !isset($_FILES['file']))
        if (isset($_FILES['file'])) {
            $file = AttachmentFile::format($_FILES['file']);

            # Allow for data-uri uploaded files
            $fp = fopen($file[0]['tmp_name'], 'rb');
            if (fread($fp, 5) == 'data:') {
                $data = 'data:';
                while ($block = fread($fp, 8192))
                  $data .= $block;
                $file[0] = Format::parseRfc2397($data);
                list(,$ext) = explode('/', $file[0]['type'], 2);
                $file[0] += array(
                    'name' => Misc::randCode(8).'.'.$ext,
                    'size' => strlen($file[0]['data']),
                );
            }
            fclose($fp);
            # TODO: Detect unacceptable attachment extension
            # TODO: Verify content-type and check file-content to ensure image
            $type = $file[0]['type'];
            if (strpos($file[0]['type'], 'image/') !== 0)
                return Http::response(403,
                    JsonDataEncoder::encode(array(
                        'error' => 'File type is not allowed',
                    ))
                );
            # TODO: Verify file size is acceptable
            if ($file[0]['size'] > $cfg->getMaxFileSize())
                        'error' => 'File is too large',
            // Paste uploads in Chrome will have a name of 'blob'
            if ($file[0]['name'] == 'blob')
                $file[0]['name'] = 'screenshot-'.Misc::randCode(4);

            $ids = $draft->attachments->upload($file);

            if (!$ids) {
                if ($file[0]['error']) {
                    return Http::response(403,
                        JsonDataEncoder::encode(array(
                            'error' => $file[0]['error'],
                        ))
                    );
                }
                else
                    return Http::response(500, 'Unable to attach image');

            $id = (is_array($ids)) ? $ids[0] : $ids;
        }
        else {
            $type = explode('/', $_POST['contentType']);
            $info = array(
                'data' => base64_decode($_POST['data']),
                'name' => Misc::randCode(10).'.'.$type[1],
                // TODO: Ensure _POST['contentType']
                'type' => $_POST['contentType'],
            );
            // TODO: Detect unacceptable filetype
            // TODO: Verify content-type and check file-content to ensure image
            $id = $draft->attachments->save($info);
        if (!isset($this->_mimetype)) {
            // Try to to auto-detect mime type
            $finfo = new finfo(FILEINFO_MIME);
            $this->_mimetype = $finfo->buffer($this->getContents(),
                    FILEINFO_MIME_TYPE);
        }

Fixed code:
        if (!function_exists('exif_imagetype')) {
            function exif_imagetype ($filename) {
                if ((list($width,$height,$type,) = getimagesize($filename)) !== false)
                    return $type;

                return false;
            }
        }

        if (!isset($_FILES['file']))
        $file = AttachmentFile::format($_FILES['file']);

        # Allow for data-uri uploaded files
        $fp = fopen($file[0]['tmp_name'], 'rb');
        if (fread($fp, 5) == 'data:') {
            $data = 'data:';
            while ($block = fread($fp, 8192))
              $data .= $block;
            $file[0] = Format::parseRfc2397($data);
            list(,$ext) = explode('/', $file[0]['type'], 2);
            $file[0] += array(
                'name' => Misc::randCode(8).'.'.$ext,
                'size' => strlen($file[0]['data']),
            );
        }
        fclose($fp);

        // Check file type to ensure image
        $type = $file[0]['type'];
        if (strpos($file[0]['type'], 'image/') !== 0)
            return Http::response(403,
                JsonDataEncoder::encode(array(
                    'error' => 'File type is not allowed',
                ))
            );
        // Check if file is truly an image
        if (!FileUploadField::isValidFile($file[0]))
            return Http::response(403,
                JsonDataEncoder::encode(array(
                    'error' => 'File is not valid',
                ))
            );
        // Verify file size is acceptable
        if ($file[0]['size'] > $cfg->getMaxFileSize())
            return Http::response(403,
                JsonDataEncoder::encode(array(
                    'error' => 'File is too large',
                ))
            );

        // Paste uploads in Chrome will have a name of 'blob'
        if ($file[0]['name'] == 'blob')
            $file[0]['name'] = 'screenshot-'.Misc::randCode(4);

        $ids = $draft->attachments->upload($file);

        if (!$ids) {
            if ($file[0]['error']) {
                        'error' => $file[0]['error'],
            else
                return Http::response(500, 'Unable to attach image');

        $id = (is_array($ids)) ? $ids[0] : $ids;
        if (!isset($this->_mimetype))
            $this->_mimetype = self::mime_type($this->getRealPath());

    /*
     * Given a filepath - auto detect the mime type
     *
     */
    static function mime_type($filepath) {
        // Try to to auto-detect mime type
        $type = null;
        if (function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $type = finfo_file($finfo, $filepath);
            finfo_close($finfo);
        }
        return $type ?: mime_content_type($filepath);
    }
        // Make sure mime type is valid
        if (strcasecmp(FileObject::mime_type($file['tmp_name']),
                    $file['type']) !== 0)
            return false;