Few Bugs Sec/regular - Question | JoomShaper

is live, now with multi-currency selling.

Few Bugs Sec/regular

Goran

Goran

Helix Framework 2 weeks ago

Hello, here are few things found in hope you will address them:

  1. Platform\Media builds the media browser markup by raw concatenation

plugins/system/helixultimate/src/Platform/Media.php prints file and folder names straight into HTML:

// line 123 (folder branch) and line 131 (image branch)
$output .= '<div class="hu-media-label">' . $file['name'] . '</div>';

// line 126
$output .= '<li class="hu-media-image" data-path="' . $file['path'] . '" data-preview="' . $file['preview'] . '">';

// line 336
$output .= '<div class="hu-media-label">' . $report['title'] . '</div>';

A file whose name contains a double quote breaks out of data-path / data-preview, and a name containing < injects markup into the picker. Nothing escapes these values on the way in either: they come from the filesystem listing, so any file placed by FTP, by a migration, or by another extension's uploader ends up here verbatim.

This is the admin-side media picker, so the attacker needs a way to get a file with a crafted name onto the server first - but that is exactly what a migration from another host does routinely, and the person who then opens the picker is a Super User.

Suggested fix: run $file['name'], $file['path'], $file['preview'] and $report['title'] through htmlspecialchars($value, ENT_QUOTES, 'UTF-8') at those four points.

  1. The legacy fields/ folder imports Joomla\CMS\Filesystem\File, which does not exist in Joomla 6

plugins/system/helixultimate/fields/heliximage.php:13 and plugins/system/helixultimate/fields/helixgallery.php:10 both do:

use Joomla\CMS\Filesystem\File;

and then call File::stripExt(...) (heliximage.php:63, 66; helixgallery.php:75, 76, 80, 84).

On Joomla 6.1.3 that class is gone: there is no libraries/src/Filesystem/File.php, the class now lives in the framework package as Joomla\Filesystem\File (libraries/vendor/joomla/filesystem/src/File.php), and we found no class_alias for the old name anywhere in libraries/ outside vendor/. So the first call to File::stripExt() in either file is a fatal Class "Joomla\CMS\Filesystem\File" not found.

Your own src/fields/ copies of the same two fields already use the correct import (src/fields/heliximage.php:15 and src/fields/helixgallery.php:17 both use Joomla\Filesystem\File;), so this is only the legacy folder lagging behind.

It matters because the shipped options.xml of a Helix based template can still register that folder through addfieldpath="/plugins/system/helixultimate/fields", and Joomla resolves field types from the registered paths - so a template that uses the helixgallery or heliximage field type from the legacy path fatals on Joomla 6. We noticed it because our own template had that path registered; we have since pointed it at src/fields.

Suggested fix: either update the two use lines in the legacy folder, or drop the folder from the package if it is no longer meant to be used on Joomla 4+.

  1. saveMegaMenuSettings() reports success even when the save failed

plugins/system/helixultimate/src/HttpResponse/Response.php:313-317 - the method returns status = true regardless of what updateMenuItem() returned, so the admin UI shows a success toast even when nothing was written. This is unchanged from 2.2.8; we mention it because 2.2.10 did touch the surrounding code (a guest / invalid id guard was added at :251-260, which in 2.2.8 made $item->getParams() fatal on null).

Suggested fix: propagate the return value of updateMenuItem() into status.

  1. The page title fields have no filter or validate in params/megamenu.xml

2.2.10 hardened the OUTPUT side of the page title (features/title.php now whitelists the heading tag, escapes title and subtitle, validates the background colour with a regex and rejects .., NUL and quotes in the background image path). The INPUT side was not touched: plugins/system/helixultimate/params/megamenu.xml is byte identical between 2.2.8 and 2.2.10, and not one of its fields carries a filter or validate attribute - we grepped the file, filter= and validate= both return zero matches.

So helixultimate_page_title_alt, helixultimate_page_subtitle, helixultimate_page_title_bg_color and helixultimate_page_title_bg_image still reach #__menu.params exactly as typed. Two of them end up inside a style attribute and one decides an HTML tag name, so they are worth constraining at the form layer too:

  • helixultimate_page_title_heading - validate="options" so only the offered values pass
  • helixultimate_page_title_bg_color - filter="color" or a validation rule
  • helixultimate_page_title_alt / _subtitle - an explicit filter instead of relying on the caller's default

Not urgent on its own, since only accounts that may edit menu items can write these, but the output-side fix you shipped in 2.2.10 is doing work the form should not be handing it in the first place.

  1. Two version comparisons break on a two-digit major, and one require_once targets a Joomla 3 path

5a. src/Platform/HTMLOverride.php:156:

if (JVERSION < 5) {
    $staticOverridePath = self::parsePath(self::$overridePathLegacy);
}

and generateExtensionPath() in the same file:

$version = JVERSION;
...
if ($version < 4) {
    rray_splice($path, 1, 0, ['views']);
    rray_splice($path, 3, 0, ['tmpl']);
}

JVERSION is a string, so PHP 8 compares it as a string against the numeric literal. We ran it:

6.1.3  < 5 -> false | 6.1.3  < 4 -> false
5.0.0  < 5 -> false | 5.0.0  < 4 -> false
10.0.0 < 5 -> true  | 10.0.0 < 4 -> true

So the day Joomla reaches a two-digit major, Helix silently switches to overrides_legacy and starts building Joomla 3 style paths with views. version_compare(JVERSION, '5.0', '<') fixes both.

5b. layout/fields/menutype.php:128-136 still loads the Joomla 3 model file:

$classUrl  = JPATH_ADMINISTRATOR . '/components/com_menus/models/menutypes.php';
$helperUrl = JPATH_ADMINISTRATOR . '/components/com_menus/helpers/menus.php';
if (!\class_exists('MenusModelMenutypes')) { require_once $classUrl; }

On Joomla 6.1.3 administrator/components/com_menus/models/menutypes.php does not exist - the class is now Joomla\Component\Menus\Administrator\Model\MenutypesModel in administrator/components/com_menus/src/Model/MenutypesModel.php. (helpers/menus.php does still exist.) So the first call to getMenuTypes() is a fatal. It does not surface today because that field has no reachable dispatcher, but it is the same legacy-folder problem as section 2.

  1. error.php lost a null guard that older Helix had

templates/shaper_helixultimate/error.php:213 (2.2.10):

$custom_style = $params->get('custom_style');
$preset = ($custom_style) ? 'default' : json_decode($params->get('preset', '{"preset":"preset1"}'))->preset;

If the stored preset parameter is not valid JSON, or is valid JSON without a preset key, json_decode() returns null or an object without that property and the arrow access fails - on the error page itself, which is the one page that must never fail.

Our own copy of the same file still carries the guarded form, which we believe came from an older Helix release:

$presetData = json_decode($params->get('preset', '{"preset":"preset1"}'));
$preset     = $custom_style ? 'default' : (isset($presetData->preset) ? $presetData->preset : 'default');

Worth restoring the guard, since the failure mode is an unrenderable error page.

  1. sanitizeEmbed() uses the tag and attribute lists as a blacklist, so it strips the very embeds it should keep

src/Platform/Helper.php:1157-1171:

$filter = InputFilter::getInstance(
    ['iframe', 'audio', 'video', 'source', 'a', 'img'],
    ['src', 'href', 'type', 'controls', 'width', 'height', 'allow', 'allowfullscreen', 'frameborder', 'alt', 'class', 'style'],
    1,
    1
);

return $filter->clean($html, 'html');

The third and fourth arguments are the tag and attribute methods. In Joomla\Filter\InputFilter ONLY_ALLOW_DEFINED_TAGS = 0 and ONLY_BLOCK_DEFINED_TAGS = 1 (same for the attribute constants), so 1, 1 means block exactly these six tags and these twelve attributes, allow everything else - the opposite of what the method name and the list contents suggest.

We ran the three combinations against the same input <iframe src="/www.youtube.com/embed/x" allowfullscreen></iframe><b>tekst</b>:

tags=1 attrs=1 xssAuto=1  (your current call) -> <b>tekst</b>
tags=0 attrs=0 xssAuto=1                      -> tekst
tags=0 attrs=0 xssAuto=0                      -> <iframe src="/www.youtube.com/embed/x"></iframe>tekst

So with the shipped arguments the iframe is removed and the unrelated <b> survives. Switching to the allowlist is not enough on its own either: with $xssAuto left at its default of 1, Joomla removes iframe regardless, so only 0, 0, 0 actually lets an embed through.

The callers are overrides/layouts/joomla/content/blog/video.php:99 and :101, blog/audio.php:19, plus the two overrides_legacy copies. The practical effect since 2.2.10 is that an article using the Helix video or audio field renders the embed with its <iframe> stripped out.

This is not a security hole - <script> and event attributes are still removed - it is the feature not working.

  1. The template customizer preview cannot pass the new helixMode=edit gate when shared_session is off

2.2.10 added an authorisation gate in Helper::loadTemplateData() (src/Platform/Helper.php:296-298):

$user              = $app->getIdentity();
$canPreviewDraft   = $user && $user->id && ($user->authorise('core.edit', 'com_templates') || $user->authorise('core.admin'));
$requestFromIframe = ($app->input->get('helixMode', '') === 'edit') && $canPreviewDraft;

but the preview it guards is a front end URL: src/Platform/Platform.php:145 builds Uri::root(true) . '/index.php?templateStyle=' . $style->id . '&helixMode=edit' and the customizer loads it in an iframe.

With Joomla's shared_session disabled (the default), the administrator session does not exist on the site side, so $user in that front request is a guest and the gate can never pass. We measured it on Joomla 6.1.3:

admin login via curl                          -> /administrator/index.php 200, logout links present
same cookie jar, front /index.php?option=com_users&view=profile -> 303 to the login page   (guest)
after a separate front-end login with the same account          -> 200                     (gate can pass)

So after upgrading, the live preview shows the saved state instead of the unsaved draft, unless the administrator happens to also be logged in on the site front end in the same browser. Before 2.2.10 no front-end login was needed.

A token tied to the administrator session (or a one-time preview key generated when the customizer opens) would keep the hardening without depending on the front-end ACL.

Thank you.

1
1 Answers
Ziaul Kabir
Ziaul Kabir
Accepted Answer
Support Agent 2 weeks ago #234071

Hello Goran,

Thank you for taking the time to provide such a detailed report, including the suggested fixes and supporting technical information.

I have forwarded your findings to our development team for further review and investigation. They will carefully assess each reported issue and determine the appropriate fixes.

We appreciate your detailed feedback and the effort you put into identifying and documenting these issues.

Best Regards,

0