Attacker Value
High
(2 users assessed)
Exploitability
Low
(2 users assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
0

CVE-2019-15954: Total.js CMS 12 Widget Remote Code Execution

Disclosure Date: September 05, 2019 Last updated March 06, 2020
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Total.js is a Node.js Framework for building e-commerce applications, REST services, real-time apps, or apps for Internet of Things (IoT), etc. Total.js CMS is a Content Management System (application) that is part of the Total.js framework. A commercial version is also available, and can be seen used world-wide.

In Total.js CMS, a user with admin permission may be able to create a widget, and extend CMS functionalities for visitors. However, this can also be abused to upload JavaScript code that will be evaluated server side. As a result, it is possible to embed malicious JavaScript in the new widget, and gain remote code execution.

Add Assessment

2
Technical Analysis

CVE-2019-15954: Total.js CMS 12 Widget Remote Code Execution

Introduction

Total.js is a Node.js Framework for building e-commerce applications, REST services, real-time apps, or apps for Internet of Things (IoT), etc. Total.js CMS is a Content Management System (application) that is part of the Total.js framework. A commercial version is also available, and can be seen used world-wide.

In Total.js CMS, a user with admin permission may be able to create a widget, and extend CMS functionalities for visitors. However, this can also be abused to upload JavaScript code that will be evaluated server side. As a result, it is possible to embed malicious JavaScript in the new widget, and gain remote code execution.

Technical Analysis

In the CVE advisory, we know that the vulnerability is associated with widget creation, so this is where we start the analysis. To do this, I looked for the keyword “New widget” because that is on the widget creation page, and very quickly I found the HTML page for that, as well as the JavaScript located at:

  • cms/themes/admin/public/forms/widgets.html
  • cms/schemas/widgets.js

The widgets.html file is what you actually look at when you’re adding a new widget from the GUI. After filling out the fields, you would click on the “Save” button, which in HTML is this:

<button name="submit">@(SAVE)</button>

And the button function is handled by the following code:

exports.submit = function(com) {
  SETTER('loading', 'show');
  AJAX('POST [url]api/widgets/ REPEAT', GETR('widgets.form'), function(response) {
    SETTER('loading', 'hide', 1000);
    if (response.success) {
      SETTER('snackbar', 'success', '@(Widget has been saved successfully.)');
      EXEC('widgets/refresh');
      com.hide();
    }
  });
};

The following URI is important because it tells us the route:

AJAX('POST [url]api/widgets/ REPEAT' ...

The route map can be found in admin.js, and our code indicates we are looking at this route:

// MODEL: /schema/widgets.js
// ... Other routes ...
ROUTE('POST    #admin/api/widgets/                        *Widget --> @save');
// ... Other routes...

The JavaScript comment actually reveals which JS file is responsible for the widgets routes, so clearly we need to be looking at widgets.js. The route also indicates we should be looking at a save function, which links to setSave, which starts the saving process.

During the saving process, it goes through a refreshing stage (in the refresh function). Although there is a lot going on, the most interesting line is this:

var obj = compile(item.body); // Line 309 (widgets.js)

The compile function parses the source code for the new widget. Apparently, the JavaScript tag is a bit customized, for example, this isn’t the standard JavaScript tag prefix, it is more specific to Total.JS:

var body = html.substring(beg, end);
var beg = body.indexOf('>') + 1;
var type = body.substring(0, beg);

body = body.substring(beg);
raw = raw.replace(type + body + '</script>', '');

body = body.trim();

if (type.indexOf('html') !== -1 || type.indexOf('plain') !== -1)
  body_template = body;
else if (type.indexOf('total') !== -1 || type.indexOf('totaljs') !== -1)
  body_total = body;
else if (type.indexOf('editor') !== -1)
  body_editor = body;
else
  body_script = body;

After parsing, the code could be stored in a few different ways. Specifically we want to watch where these are going in code:

// Around line 258 in widgets.js
obj.js = body_script;
// ... code ...
obj.editor = body_editor;
// ... code ...
obj.template = body_template;
// ... code ...
obj.total = body_total;
// ... code ...

So that’s pretty much for the compile function, and back to the refresh function. Now that we have the parsed code, let’s see what refresh is doing with the object members we’re interested in watching. Well, there are some interesting ones, for example, this is what happens to obj.total:

if (obj.total) {
  var o = new WidgetInstace();
  try {
    (new Function('exports', obj.total))(o);
  } catch (e) {
    WARNING.message = 'Widget <b>{0}</b> exception: <b>{1}</b>'.format(item.name, e.message);
    ADMIN.notify(WARNING);
  }
  obj.total = o;
  rebuild = true;
}

As you can see here, if we have a JavaScript code block that starts like this:

<script total>
  // ... something ...
</script>

Then that code goes to obj.total, and that gets executed as a new function. To mimic that code execution, open up the Developer’s Tools in your browser, enter the following (which is basically what the code above is doing):

function WidgetInstance() {}
var o = new WidgetInstance();
(new Function('exports', 'console.log("Hello World!");'))(o);

And you should see that console.log is executed (which represents the user-provided script):

> function WidgetInstance() {}
var o = new WidgetInstance();
(new Function('exports', 'console.log("Hello World!");'))(o);
> VM33:3 Hello World!
1
Ratings
  • Attacker Value
    High
  • Exploitability
    Low
Technical Analysis

sdfsadf

General Information

Additional Info

Technical Analysis