Skip to content

Commit

Permalink
Add TotaJS CMS Code Injection in Widget Creation
Browse files Browse the repository at this point in the history
  • Loading branch information
wchen-r7 committed Sep 25, 2019
1 parent 4431476 commit 8dc238e
Show file tree
Hide file tree
Showing 2 changed files with 475 additions and 0 deletions.
136 changes: 136 additions & 0 deletions documentation/modules/exploit/multi/http/totaljsms_widget_exec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 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:

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

And the button function is handled by the following code:

```javascript
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:

```javascript
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:
```javascript
// 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:
```javascript
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:
```javascript
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:
```javascript
// 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`:
```javascript
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:
```javascript
<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):
```javascript
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!
```
Loading

0 comments on commit 8dc238e

Please sign in to comment.