High
CVE-2019-15954: Total.js CMS 12 Widget Remote Code Execution
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
High
(2 users assessed)Low
(2 users assessed)Unknown
Unknown
Unknown
CVE-2019-15954: Total.js CMS 12 Widget Remote Code Execution
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
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
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!
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportRatings
-
Attacker ValueHigh
-
ExploitabilityLow
Technical Analysis
sdfsadf
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportGeneral Information
References
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: