Skip to content

Commit

Permalink
This release contains a security patch related to the usage of Twig c…
Browse files Browse the repository at this point in the history
…ode in the Twig

Snippet plugin, and in the site-wide <head> and <body>.

Critical security patch
-----------------------

The Twig template engine currently has a vulnerability with how some of its filters are
implemented, where it is possible for a designer or an administrator who is aware of the
vulnerability to execute arbitrary CLI code on the server.

This update disables the ability for designers/administrators to call the affected
filters, as a work-around in place of an actual patch for this from Twig's developers.

Other fixes
-----------

We've fixed a visual glitch where administrators could always see the "Delete archived
versions" and "Rescan text/image extract" buttons in the Content Items panel in Organizer,
even if they didn't have the permissions needed to actually press them.
  • Loading branch information
Chris Turnbull committed Feb 2, 2024
1 parent 78f6d5a commit 72afb59
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@
"wow.js": "^1.2.2",
"zxcvbn": "^4.4.2"
},
"version": "9.5.60240"
"version": "9.5.60437"
}
2 changes: 1 addition & 1 deletion zenario/admin/db_updates/latest_revision_no.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
define('ZENARIO_MINOR_VERSION', '5');
define('ZENARIO_CHANGELOG_URL', 'https://zenar.io/zenario-95');
define('ZENARIO_IS_BUILD', true);
define('ZENARIO_REVISION', '60240');
define('ZENARIO_REVISION', '60437');

define('TINYMCE_DIR', 'zenario/libs/manually_maintained/lgpl/tinymce_4_7_3.1/');
2 changes: 1 addition & 1 deletion zenario/autoload/fun/syncInlineFileContentLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
}

//Update the Content in the cache table
$text = trim(strip_tags($content));
$text = trim(html_entity_decode(strip_tags($content)));
\ze\row::set('content_cache', ['text' => $text, 'text_wordcount' => str_word_count($text)], ['content_id' => $cID, 'content_type' => $cType, 'content_version' => $cVersion]);


Expand Down
24 changes: 23 additions & 1 deletion zenario/autoload/phi.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
class phi {

private static $initRun = false;
private static $twig;
private static $vars = [
'e' => M_E,
'pi' => M_PI,
Expand All @@ -52,6 +51,19 @@ class phi {
];


private static $twig;

//A dummy filter, just used as a work-around to block other filters from working
public static function dummyFilter() {
return '';
}

protected static function blockFilter($name) {
$dummyFilter = new \Twig\TwigFilter($name, ['\\ze\\phi', 'dummyFilter']);
self::$twig->addFilter($dummyFilter);
}


private static $testing = false;
private static $output = null;
private static $outputs = [];
Expand Down Expand Up @@ -134,6 +146,16 @@ public static function init() {
//Include a Twig extension that enables support for breaks and continues
self::$twig->addExtension(new \MNBreakAndContinueTwigExtension());

//Remove the "filter", "map" and "reduce" filters, as these have a very bad security vulnerability
//involving executing arbitrary functions/making arbitrary CLI calls, and I don't think we
//use them anywhere anyway.
//I'm also removing the "sort" filter. I can't actually reproduce the vulnerability with this one
//but it also accepts functions as an input so I'm blocking it out of paranoia.
self::blockFilter('filter');
self::blockFilter('map');
self::blockFilter('reduce');
self::blockFilter('sort');


//Set up the whitelist of allowed functions
$whitelist = [
Expand Down
20 changes: 20 additions & 0 deletions zenario/autoload/twig.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
class twig {
private static $twig;

//A dummy filter, just used as a work-around to block other filters from working
public static function dummyFilter() {
return '';
}

protected static function blockFilter($name) {
$dummyFilter = new \Twig\TwigFilter($name, ['\\ze\\twig', 'dummyFilter']);
self::$twig->addFilter($dummyFilter);
}

public static function init() {
//Initialise Twig
self::$twig = new \Twig\Environment(new \Zenario_Twig_Loader(), [
Expand All @@ -55,6 +65,16 @@ public static function init() {

//Add the I18n extension to add support for translating text
self::$twig->addExtension(new \Twig_Extensions_Extension_I18n());

//Remove the "filter", "map" and "reduce" filters, as these have a very bad security vulnerability
//involving executing arbitrary functions/making arbitrary CLI calls, and I don't think we
//use them anywhere anyway.
//I'm also removing the "sort" filter. I can't actually reproduce the vulnerability with this one
//but it also accepts functions as an input so I'm blocking it out of paranoia.
self::blockFilter('filter');
self::blockFilter('map');
self::blockFilter('reduce');
self::blockFilter('sort');


//Create instances of any modules that say they are usable in Twig Frameworks
Expand Down
2 changes: 1 addition & 1 deletion zenario/js/admin_organizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ zenarioO.go = function(path, branch, refiner, queued, lastInQueue, backwards, do

if (!zenarioO.followPathOnMap(path)) {
//add some debug information here
zenarioA.showMessage('The requested path "' + requestedPath + '" was not found in the system. If you have just updated or added files to the CMS, you will need to reload the page.', undefined, 'error', false, true);
zenarioA.showMessage('A script attempted to access the path "' + requestedPath + '" in Organizer, but this was not found. This may be because the current administrator does not have access to that panel, because the module for that panel is not running, or because the path does not exist. It may help to reload the page.', undefined, 'error', false, true);
return false;
}

Expand Down
60 changes: 30 additions & 30 deletions zenario/js/admin_organizer.min.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions zenario/js/panel_type_hierarchy_with_lazy_load.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ methods.returnSearchingEnabled = function() {
};

methods.returnDoSortingAndSearchingOnServer = function() {

//If we're not actually showing the hierarchy view, and are showing a flat list instead,
//we should use the server-side ordering.
if (!thus.showFlatView()) {
return false;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,57 @@ public function preFillOrganizerPanel($path, &$panel, $refinerName, $refinerId,
$panel['custom__content_types_with_categories'] =
ze\ray::valuesToKeys(ze\row::getValues('content_types', 'content_type_id', ['enable_categories' => 1]));
}


//Some specific code to handle sorting items whilst searching in flat view.
//When not searching, items will be displayed in hierarchy view, and the JavaScript in the panel instance
//will need things sorted by the ordinal in each level.
//However when we are doing a search, hierarchy view will be switched off and items will be shown in flat view.
//We need to write some SQL to make sure they are sorted in the correct hierarchical order.
if (isset($_GET['_search'])) {

//Work out what the largest possible depth is in the menu.
//(N.b. this field has an index so this query should be fast.)
$largestMenuDepth = (int) ze\sql::fetchValue("SELECT MAX(separation) FROM ". DB_PREFIX. "menu_hierarchy");

//Special case:
//If there are no menu nodes that are children of other menu nodes, we can just leave the sort column
//as the ordinal and skip this step.
if ($largestMenuDepth > 0) {

//Start writing the SQL needed to sort the matched menu nodes into order hierarchically.
$join = '';
$sortCol = 'mi.ordinal';

//For every possible parent-child relationship, add a new table join,
//and then add that table join to the ordinal column.
//The logic I've written below will show all of the top level nodes first,
//then second level, and so on. After that they'll be sorted in hierarchical menu node order.
//You could use different logic than that, but only if you could work out a way to write the joins/SQL.
for ($i = 1; $i <= $largestMenuDepth; ++$i) {
$l = $i - 1;

$join .= '
LEFT JOIN '. DB_PREFIX. 'menu_nodes AS p'. $i;

if ($i == 1) {
$join .= '
ON mi.parent_id != 0
AND p'. $i. '.id = mi.parent_id';
} else {
$join .= '
ON p'. $l. '.parent_id != 0
AND p'. $i. '.id = p'. $l. '.parent_id';
}

$sortCol = 'p'. $i. '.ordinal, '. $sortCol;
}


$panel['db_items']['table'] .= $join;
$panel['columns']['ordinal']['sort_column'] = $sortCol;
}
}
}

public function fillOrganizerPanel($path, &$panel, $refinerName, $refinerId, $mode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1286,10 +1286,9 @@ zenario__content:

delete_archives:
priv: _PRIV_PUBLISH_CONTENT_ITEM
visible_if_for_all_selected_items: |
zenarioA.checkSpecificPerms(id)
visible_if_for_any_selected_items: |
item.archives_exist
item.archives_exist &&
zenarioA.checkSpecificPerms(id)
parent: action_dropdown
hide_in_select_mode: true
label: Delete archived versions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ zenario__content:
zenario_ctype_document__rescan_extract:
priv: _PRIV_EDIT_DRAFT
visible_if_for_all_selected_items: |
item.type == 'document' &&
zenarioA.checkSpecificPerms(id)
parent: action_dropdown
label: Rescan text/image extract
multiple_select: true
visible_if_for_all_selected_items: |
item.type == 'document'
ajax:
confirm:
message: |
Expand Down

0 comments on commit 72afb59

Please sign in to comment.