security: Inline SVGs

This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to inline handling of SVG attachments.
This commit is contained in:
JediKev
2026-06-17 20:33:15 +00:00
parent 6eb6b98996
commit 078516ed03
5 changed files with 50 additions and 3 deletions

View File

@@ -58,7 +58,7 @@ if ($cfg->isAuthRequiredForFiles()
// and the user has access to the parent ticket!!
if ($file->verifySignature($_GET['signature'], $_GET['expires'])) {
try {
if (($s = @$_GET['s']) && strpos($file->getType(), 'image/') === 0)
if (($s = @$_GET['s']) && $file->isInlineSafeImage())
return $file->display($s);
// Download the file..

View File

@@ -3,6 +3,7 @@
if(!defined('INCLUDE_DIR')) die('!');
require_once(INCLUDE_DIR.'class.draft.php');
require_once(INCLUDE_DIR.'class.file.php');
class DraftAjaxAPI extends AjaxController {
@@ -84,7 +85,7 @@ class DraftAjaxAPI extends AjaxController {
// Check file type to ensure image
$type = $file[0]['type'];
if (strpos($file[0]['type'], 'image/') !== 0)
if (!AttachmentFile::isInlineSafeImageType($file[0]['type']))
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File type is not allowed',
@@ -367,6 +368,9 @@ class DraftAjaxAPI extends AjaxController {
$files = array();
foreach ($images as $f) {
if (!$f->isInlineSafeImage())
continue;
$url = $f->getDownloadUrl();
$files[] = array(
// Don't send special sizing for thread items 'cause they

View File

@@ -175,6 +175,10 @@ class AttachmentFile extends VerySimpleModel
}
function display($scale=false, $ttl=86400) {
// Force download if not safe viewable image
if (!$this->isInlineSafeImage())
return $this->download(false, 'attachment');
$this->makeCacheable($ttl);
if ($scale && extension_loaded('gd')
@@ -214,6 +218,12 @@ class AttachmentFile extends VerySimpleModel
'type' => $options['type']))))
$options['id'] = $a->getId();
// Force attachment disposition if not inline-safe image
if (isset($options['disposition'])
&& strcasecmp($options['disposition'], 'inline') == 0
&& !$this->isInlineSafeImage())
$options['disposition'] = 'attachment';
return static::generateDownloadUrl($this->getId(),
strtolower($this->getKey()), $this->getSignature(),
$options);
@@ -287,7 +297,7 @@ class AttachmentFile extends VerySimpleModel
$inline = ($thisstaff ? ($thisstaff->getImageAttachmentView() === 'inline') : false);
$disposition = ((($disposition && strcasecmp($disposition, 'inline') == 0)
|| $inline)
&& strpos($this->getType(), 'image/') !== false)
&& $this->isInlineSafeImage())
? 'inline' : 'attachment';
$ttl = ($expires) ? $expires - Misc::gmtime() : false;
$bk = $this->open();
@@ -721,6 +731,29 @@ class AttachmentFile extends VerySimpleModel
->filter(array('ft' => 'B'))
->order_by('created');
}
/**
* Determines whether a MIME type is safe to render inline as a passive image.
*
* @param string $type
* @return bool
*/
static function isInlineSafeImageType($type) {
$type = strtolower(trim((string) $type));
return strpos($type, 'image/') === 0
&& $type !== 'image/svg+xml'
&& $type !== 'image/svg';
}
/**
* Determines whether this file is safe to render inline as a passive image.
*
* @return bool
*/
function isInlineSafeImage() {
return static::isInlineSafeImageType($this->getType());
}
}
class FileStorageBackend {

View File

@@ -604,6 +604,9 @@ class Format {
if (!($file = AttachmentFile::lookup($match[2])))
return $match[0];
if (!$file->isInlineSafeImage())
return $match[0];
return sprintf('"%s" data-cid="%s"',
$file->getDownloadUrl($options), $match[2]);
}, $html);

View File

@@ -1757,6 +1757,10 @@ implements TemplateVariable {
$files[$i]['inline'] = true;
}
foreach ($entry->normalizeFileInfo($files) as $F) {
if (!empty($F['inline'])
&& (!isset($F['file']) || !$F['file']->isInlineSafeImage()))
$F['inline'] = false;
// Deduplicate on the `key` attribute. The key is
// necessary for the CID rewrite below
$attached_files[$F['key']] = $F;
@@ -1769,6 +1773,9 @@ implements TemplateVariable {
// discarded, only the unique hash-code (key) will be available to
// retrieve the image later
foreach ($attached_files as $key => $a) {
if (empty($a['inline']))
continue;
if (isset($a['cid']) && $a['cid']) {
$body = preg_replace('/src=("|\'|\b)(?:cid:)?'
. preg_quote($a['cid'], '/').'\1/i',